API de Integración T-Canaria - Manual Técnico
| Description | Manual técnico de la API REST para integración de sistemas externos con el Sistema de Evaluación de Transparencia T-Canaria |
| Author(s) | Comisionado de Transparencia y Acceso a la Información Pública de Canarias |
| Copyright | © 2025-2026 Comisionado de Transparencia y Acceso a la Información Pública de Canarias. Todos los derechos reservados. |
Índice de contenidos
API de Integración T-Canaria
Bienvenido a la documentación técnica de la API de Integración T-Canaria, la interfaz programática que permite a las administraciones públicas integrar sus sistemas con el Sistema de Evaluación de Transparencia del Comisionado de Transparencia de Canarias.
¿Qué es esta API?
La API de Integración permite que los sistemas internos de las administraciones públicas (portales de transparencia, registros de solicitudes, cuadros de mando) se comuniquen automáticamente con T-Canaria, sin necesidad de acceder manualmente al portal web.
¿Qué se puede hacer con la API?
| Funcionalidad | Descripción |
|---|---|
| Evaluaciones | Consultar evaluaciones activas, plazos y calendario |
| Declaraciones | Consultar estado, inicializar y obtener análisis |
| Publicidad activa | Enviar URLs del portal de transparencia (individual o masivo) |
| Cuestionarios | Responder cuestionarios de soporte web, transparencia voluntaria y derecho de acceso |
| Derecho de acceso | Enviar estadísticas de solicitudes de acceso a la información |
| Resultados | Consultar notas, ranking y descargar informes PDF |
| Documentos | Descargar informes provisionales, definitivos y justificantes |
| Usuarios | Consultar usuarios autorizados de la entidad |
| Incidencias | Crear, responder y cerrar incidencias con el Comisionado |
| Webhooks | Recibir notificaciones automáticas cuando ocurren eventos |
URL base
Flujo básico de uso
sequenceDiagram
participant E as Sistema de la Entidad
participant A as API T-Canaria
E->>A: POST /auth (API Key)
A-->>E: Token JWT (1 hora)
E->>A: GET /evaluaciones
A-->>E: Evaluaciones activas
E->>A: PUT /declaraciones/{id}/pa (URLs)
A-->>E: 200 OK
E->>A: PUT /declaraciones/{id}/cuestionarios/{id}
A-->>E: 200 OK
E->>A: GET /declaraciones/{id}/notas
A-->>E: Puntuaciones
Requisitos previos
Para utilizar la API necesita:
- API Key — proporcionada por el equipo del Comisionado de Transparencia
- IP autorizada (opcional) — si se configuró restricción de IP, solo se aceptan peticiones desde esas IPs
- Cliente HTTP — cualquier lenguaje de programación capaz de hacer peticiones HTTP (Python, Java, .NET, PHP, curl, etc.)
Primer paso
Vaya a la sección Obtener API Key para comenzar.
Características técnicas
| Aspecto | Detalle |
|---|---|
| Protocolo | HTTPS (TLS 1.2+) |
| Formato | JSON (camelCase) |
| Autenticación | API Key + JWT Bearer (1 hora) |
| Errores | RFC 7807 Problem Details |
| Paginación | ?page=0&size=20 (máximo 100) |
| Cabeceras | X-Correlation-ID, X-Api-Version: 1.0 |
| Versionado | /v1/ en la URL |
Soporte
Si necesita ayuda con la integración:
- Contacte con el equipo técnico del Comisionado de Transparencia
- Cree una incidencia desde la API (
POST /apientidades/v1/incidencias) - Consulte este manual para ejemplos detallados de cada endpoint
Primeros pasos
Primeros pasos
Para comenzar a usar la API de Integración T-Canaria necesita completar estos pasos:
- Obtener una API Key proporcionada por el equipo del Comisionado
- Autenticarse enviando la API Key para obtener un token JWT
- Usar el token en todas las peticiones subsiguientes
Diagrama de autenticación
flowchart LR
A[Admin activa API] --> B[Entidad recibe API Key]
B --> C[POST /auth con API Key]
C --> D[Recibe JWT 1 hora]
D --> E[Usa JWT en peticiones]
E --> F{¿Token expirado?}
F -->|No| E
F -->|Sí| G[POST /auth/refresh]
G --> D
Seguridad
La API implementa múltiples capas de seguridad:
| Capa | Descripción |
|---|---|
| API Key | Clave secreta única por entidad |
| JWT | Token de sesión con expiración de 1 hora |
| Restricción IP | Opcional: solo acepta peticiones desde IPs configuradas |
| HTTPS | Todas las comunicaciones van cifradas |
| Validación de datos | Todos los datos de entrada se validan antes de procesarse |
Proteja su API Key
La API Key es como una contraseña. No la incluya en código fuente público, repositorios Git ni la comparta por canales no seguros.
Obtener API Key
La API Key es la credencial que permite a su entidad autenticarse en la API de Integración T-Canaria. Es única por entidad y actúa como contraseña de acceso.
¿Cómo obtener su API Key?
La API Key la proporciona el equipo del Comisionado de Transparencia. Para solicitarla:
- Contacte con el equipo técnico del Comisionado de Transparencia
- Indique la entidad para la que necesita acceso a la API
- El equipo del Comisionado activará el acceso y le proporcionará la API Key
- Opcionalmente, comunique las IPs desde las que realizará las peticiones para configurar la restricción por IP
La clave la genera el Comisionado
A diferencia de otras APIs, usted no genera la API Key. El equipo del Comisionado se encarga de crear y proporcionarle la clave de forma segura.
Características de la API Key
- Mínimo 20 caracteres
- Mezcla de letras, números y símbolos
- Única por entidad
- Puede contener caracteres especiales (
@,&,#,%, etc.)
Ejemplo de formato
Protección de la API Key
Trate la API Key como una contraseña
- No la incluya en código fuente público ni repositorios Git
- No la comparta por canales no seguros (email sin cifrar, chat público)
- Almacénela en un gestor de secretos o variables de entorno protegidas
- Si sospecha que ha sido comprometida, contacte inmediatamente con el Comisionado
Revocación
Si necesita revocar o cambiar su API Key (por ejemplo, si ha sido comprometida), contacte con el equipo del Comisionado de Transparencia. Ellos desactivarán la clave actual y le proporcionarán una nueva.
Siguiente paso
Una vez que tenga su API Key, vaya a la sección Autenticación para aprender a obtener un token JWT.
Autenticación
Una vez que tiene la API Key, el primer paso es obtener un token JWT.
Endpoint
Sin autenticación
Este es el único endpoint que no requiere token JWT. Se envía la API Key en el body.
Petición
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
apiKey |
string | La API Key proporcionada por el Comisionado |
Respuesta exitosa (200)
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2026-03-26T15:30:00Z",
"entityId": 1001,
"entityName": "AYUNTAMIENTO DE EJEMPLO"
}
| Campo | Tipo | Descripción |
|---|---|---|
token |
string | Token JWT para usar en las peticiones siguientes |
expiresAt |
datetime | Fecha/hora de expiración del token (UTC, ISO 8601) |
entityId |
int | ID de la entidad autenticada |
entityName |
string | Nombre de la entidad |
Cómo usar el token
Incluya el token en la cabecera Authorization de todas las peticiones:
Ejemplos
# Guardar API Key en fichero (evitar problemas con caracteres especiales)
echo '{"apiKey":"mi-clave-api-secreta-ejemplo-2026"}' > /tmp/auth.json
# Obtener token
TOKEN=$(curl -s -X POST https://apientidades-pro.transparenciacanarias.org/apientidades/v1/auth \
-H "Content-Type: application/json" \
-d @/tmp/auth.json | jq -r '.token')
echo "Token: $TOKEN"
# Usar el token
curl -s https://apientidades-pro.transparenciacanarias.org/apientidades/v1/evaluaciones \
-H "Authorization: Bearer $TOKEN" | jq .
import requests
API_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
API_KEY = "mi-clave-api-secreta-ejemplo-2026"
# Obtener token
resp = requests.post(f"{API_URL}/auth", json={"apiKey": API_KEY})
token = resp.json()["token"]
# Usar el token
headers = {"Authorization": f"Bearer {token}"}
evaluaciones = requests.get(f"{API_URL}/evaluaciones", headers=headers)
print(evaluaciones.json())
using var client = new HttpClient();
var baseUrl = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1";
// Obtener token
var authResp = await client.PostAsJsonAsync($"{baseUrl}/auth",
new { apiKey = "mi-clave-api-secreta-ejemplo-2026" });
var authData = await authResp.Content.ReadFromJsonAsync<AuthResponse>();
// Usar el token
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", authData.Token);
var evaluaciones = await client.GetFromJsonAsync<object>($"{baseUrl}/evaluaciones");
<?php
$apiUrl = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1";
$apiKey = "mi-clave-api-secreta-ejemplo-2026";
// Obtener token
$ch = curl_init("$apiUrl/auth");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["apiKey" => $apiKey]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = json_decode(curl_exec($ch), true);
$token = $result["token"];
// Usar el token
$ch = curl_init("$apiUrl/evaluaciones");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$evaluaciones = json_decode(curl_exec($ch), true);
print_r($evaluaciones);
Errores posibles
| Código | Causa | Solución |
|---|---|---|
| 400 | Campo apiKey vacío o body ausente |
Envíe un JSON válido con el campo apiKey |
| 401 | API Key inválida o entidad desactivada | Verifique la clave con el equipo del Comisionado |
| 401 | IP no autorizada | Su IP no está en la lista de IPs permitidas |
Ejemplo de error (401)
{
"type": "https://api.evaluax.es/errores/no-autorizado",
"title": "No autorizado",
"status": 401,
"detail": "API Key invalida o entidad no autorizada.",
"instance": "/apientidades/v1/auth",
"traceId": "a1b2c3d4..."
}
API Key con caracteres especiales
Si su API Key contiene caracteres como &, @, #, %, asegúrese de enviarla dentro de un JSON válido. En curl, es recomendable guardar el body en un fichero (-d @/tmp/auth.json) para evitar problemas de escape en el shell.
Renovar token
El token JWT expira en 1 hora. Antes de que expire, puede renovarlo sin necesidad de enviar la API Key otra vez.
Endpoint
Sin body. Solo necesita el token actual en la cabecera.
Respuesta exitosa (200)
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2026-03-26T16:30:00Z",
"entityId": 1001,
"entityName": "AYUNTAMIENTO DE EJEMPLO"
}
Misma estructura que el login. El nuevo token tiene 1 hora de validez desde el momento de la renovación.
Ejemplo
Comportamiento importante
Validación de IP en el refresh
Si la entidad tiene IPs autorizadas configuradas, la IP también se valida en el refresh. Esto significa que un token robado no puede renovarse desde una IP no autorizada.
Token expirado
Si el token ya expiró, el refresh devuelve 401. Debe obtener un token nuevo con POST /auth usando la API Key.
Estrategia recomendada
flowchart TD
A[Inicio] --> B[POST /auth]
B --> C[Guardar token + expiresAt]
C --> D{¿Quedan > 5 min?}
D -->|Sí| E[Hacer peticiones]
E --> D
D -->|No| F[POST /auth/refresh]
F --> C
Renueve el token cuando queden menos de 5 minutos para su expiración. No espere a que expire.
Restricción por IP
La restricción por IP es una capa de seguridad opcional que limita desde qué direcciones IP se puede acceder a la API. Si está activada para su entidad, solo podrá autenticarse desde las IPs previamente autorizadas.
¿Cómo funciona?
- El Comisionado de Transparencia configura las IPs permitidas para su entidad (IPs individuales o rangos de red)
- Al autenticarse con
POST /auth, el sistema verifica que la IP de origen esté en la lista - Si la IP no está autorizada, se rechaza la petición con código
401
La validación se realiza en dos momentos:
POST /auth: al obtener el token con la API KeyPOST /auth/refresh: al renovar el token
El resto de peticiones a la API (consultar datos, enviar respuestas, etc.) no vuelven a verificar la IP. Un token robado, sin embargo, no puede renovarse desde una IP no autorizada.
Sin restricción configurada
Si el Comisionado no ha configurado IPs para su entidad, se permite el acceso desde cualquier dirección IP.
Error por IP no autorizada
Si intenta autenticarse desde una IP que no está en la lista de IPs permitidas, recibirá la siguiente respuesta:
Código HTTP: 401 Unauthorized
{
"type": "https://api.evaluax.es/errores/no-autorizado",
"title": "No autorizado",
"status": 401,
"detail": "Acceso denegado. La IP 85.123.45.67 no está autorizada para esta entidad. Contacte con el Comisionado para configurar las IPs permitidas.",
"instance": "/apientidades/v1/auth",
"traceId": "a1b2c3d4..."
}
El campo detail incluye la IP desde la que se realizó la petición, lo que facilita identificar el problema.
Causas habituales de este error
| Causa | Solución |
|---|---|
| La IP pública de su servidor ha cambiado | Contacte con el Comisionado para actualizar la IP autorizada |
| Está accediendo desde una red diferente (VPN, otra oficina) | Use la red desde la que se configuró el acceso, o solicite añadir la nueva IP |
| Su proveedor de internet asigna IPs dinámicas | Solicite al Comisionado un rango de IPs en vez de una IP fija |
¿Necesita conocer su IP pública?
Ejecute curl ifconfig.me desde el servidor que realizará las peticiones a la API. Esa es la IP que debe comunicar al Comisionado.
Solicitar cambios en las IPs autorizadas
Para añadir, modificar o eliminar IPs autorizadas, contacte con el equipo del Comisionado de Transparencia.
Evaluaciones
Evaluaciones
Las evaluaciones son los ciclos periódicos de evaluación de transparencia que organiza el Comisionado de Transparencia de Canarias. Cada evaluación tiene un período de vigencia, un calendario con fases y un conjunto de obligaciones que las entidades deben cumplir.
Conceptos clave
| Concepto | Descripción |
|---|---|
| Evaluación | Ciclo completo de evaluación (ej: "ITCanarias 2024") |
| Declaración | La participación de una entidad en una evaluación concreta |
| Calendario | Fechas clave: período evaluado, carga de datos, alegaciones, calificaciones |
| Prórroga | Extensión del plazo de carga concedida a la entidad |
| Obligaciones | Árbol de ítems de publicidad activa que la entidad debe cumplir |
Ciclo de vida de una evaluación
stateDiagram-v2
[*] --> Pendiente: Creación
Pendiente --> Abierta: Apertura
Abierta --> EnProrroga: Prórroga concedida
EnProrroga --> EvaluandoLaDeclaracion: Fin prórroga
Abierta --> EvaluandoLaDeclaracion: Fin plazo carga
EvaluandoLaDeclaracion --> Alegaciones: Publicación provisionales
Alegaciones --> EvaluandoLasAlegaciones: Fin alegaciones
EvaluandoLasAlegaciones --> Cerrada: Publicación definitivas
Cerrada --> [*]
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/evaluaciones |
Listar evaluaciones de la entidad |
GET |
/evaluaciones/{id} |
Detalle con calendario y prórrogas |
GET |
/evaluaciones/{id}/calendario |
Solo fechas clave |
GET |
/evaluaciones/{id}/obligaciones |
Árbol de obligaciones de PA |
Relación con otros recursos
erDiagram
EVALUACION ||--o{ DECLARACION : "tiene"
EVALUACION ||--o{ PRORROGA : "puede tener"
EVALUACION ||--|{ OBLIGACION : "define"
DECLARACION }o--|| ENTIDAD : "pertenece a"
Sobre la paginación
El endpoint de listar evaluaciones soporta paginación estándar con los parámetros page (base 0) y size (máximo 100). Ver formato de respuestas paginadas para más detalles.
Listar evaluaciones
Devuelve las evaluaciones en las que la entidad autenticada tiene declaración, con información resumida de cada una.
Parámetros de consulta
| Parámetro | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
page |
int | No | 0 |
Página a obtener (base 0) |
size |
int | No | 20 |
Registros por página (máximo 100) |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/evaluaciones",
headers=headers,
params={"page": 0, "size": 10}
)
data = response.json()
for ev in data["data"]:
print(f"{ev['evaluacionId']}: {ev['evaluacion']} - {ev['estado']}")
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await client.GetAsync(
$"{baseUrl}/evaluaciones?page=0&size=10");
var json = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<PaginatedResponse<EvaluacionResumen>>(json);
foreach (var ev in result.Data)
{
Console.WriteLine($"{ev.EvaluacionId}: {ev.Evaluacion} - {ev.Estado}");
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "$baseUrl/evaluaciones?page=0&size=10",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token"
]
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
foreach ($data['data'] as $ev) {
echo "{$ev['evaluacionId']}: {$ev['evaluacion']} - {$ev['estado']}\n";
}
curl_close($ch);
Respuesta exitosa (200)
{
"data": [
{
"evaluacionId": 10,
"evaluacion": "Evaluacion ITCanarias 2024",
"declaracionId": 5001,
"estado": "NoPresentada",
"estadoCodigo": 1,
"ultimaPuntuacion": 72.5,
"denominacionMapa": "ITCanarias",
"estadoEvaluacion": "Abierta",
"periodoInicio": "2024-01-01T00:00:00Z",
"periodoFin": "2024-12-31T23:59:59Z"
}
],
"total": 1,
"page": 0,
"size": 20,
"totalPages": 1
}
Campos del objeto EvaluacionResumen
| Campo | Tipo | Descripción |
|---|---|---|
evaluacionId |
int | Identificador único de la evaluación |
evaluacion |
string | Título descriptivo de la evaluación |
declaracionId |
int | ID de la declaración de la entidad en esta evaluación |
estado |
string | Estado actual de la declaración (SinAbrir, NoPresentada, PendienteRevision, etc.) |
estadoCodigo |
int | Código numérico del estado (0-6). Ver enums |
ultimaPuntuacion |
decimal | Última puntuación obtenida (0-100) |
denominacionMapa |
string | Nombre corto del indicador (ej: "ITCanarias") |
estadoEvaluacion |
string | Estado de la evaluación global (Pendiente, Abierta, Cerrada, etc.) |
periodoInicio |
datetime | Inicio del período evaluado (ISO 8601) |
periodoFin |
datetime | Fin del período evaluado (ISO 8601) |
Campos del envelope de paginación
| Campo | Tipo | Descripción |
|---|---|---|
data |
array | Registros de la página actual |
total |
int | Total de registros sin paginar |
page |
int | Página actual (base 0) |
size |
int | Registros por página |
totalPages |
int | Total de páginas disponibles |
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
Paginación
Si la entidad participa en muchas evaluaciones, use los parámetros page y size para paginar los resultados. El máximo de registros por página es 100.
Solo evaluaciones con declaración
Este endpoint solo devuelve evaluaciones en las que la entidad tiene una declaración asociada. Si no aparece una evaluación, puede que la entidad aún no haya sido incluida por el Comisionado.
Detalle de evaluación
Obtener detalle completo
Devuelve el detalle completo de una evaluación incluyendo calendario de fases y prórrogas concedidas a la entidad.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
idevaluacion |
int | ID de la evaluación |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/evaluaciones/10", headers=headers)
evaluacion = response.json()
print(f"Evaluación: {evaluacion['evaluacion']}")
print(f"Estado declaración: {evaluacion['estado']}")
print(f"Carga de datos: {evaluacion['calendario']['cargaInicio']} - {evaluacion['calendario']['cargaFin']}")
var response = await client.GetAsync($"{baseUrl}/evaluaciones/10");
var json = await response.Content.ReadAsStringAsync();
var evaluacion = JsonConvert.DeserializeObject<EvaluacionDetalle>(json);
Console.WriteLine($"Evaluación: {evaluacion.Evaluacion}");
Console.WriteLine($"Período: {evaluacion.Calendario.PeriodoInicio} - {evaluacion.Calendario.PeriodoFin}");
Respuesta exitosa (200)
{
"evaluacionId": 10,
"evaluacion": "Evaluacion ITCanarias 2024",
"declaracionId": 5001,
"estado": "NoPresentada",
"estadoCodigo": 1,
"ultimaPuntuacion": 72.5,
"denominacionMapa": "ITCanarias",
"estadoEvaluacion": "Abierta",
"calendario": {
"periodoInicio": "2024-01-01T00:00:00Z",
"periodoFin": "2024-12-31T23:59:59Z",
"cargaInicio": "2025-01-15T00:00:00Z",
"cargaFin": "2025-03-15T23:59:59Z",
"alegacionesInicio": null,
"alegacionesFin": null,
"preparacionInicio": null,
"preparacionFin": null,
"calificacionesProvisionales": null,
"calificacionesDefinitivas": null
},
"prorrogas": [
{
"id": 1,
"fechaInicio": "2025-03-16",
"fechaFin": "2025-04-15"
}
]
}
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
evaluacionId |
int | Identificador único de la evaluación |
evaluacion |
string | Título descriptivo |
declaracionId |
int | ID de la declaración de la entidad |
estado |
string | Estado de la declaración |
estadoCodigo |
int | Código numérico del estado (0-6) |
ultimaPuntuacion |
decimal | Última puntuación obtenida |
denominacionMapa |
string | Nombre corto del indicador |
estadoEvaluacion |
string | Estado de la evaluación global |
calendario |
object | Fechas clave de la evaluación |
prorrogas |
array | Prórrogas concedidas a la entidad |
Campos del objeto calendario
| Campo | Tipo | Descripción |
|---|---|---|
periodoInicio |
datetime | Inicio del período evaluado |
periodoFin |
datetime | Fin del período evaluado |
cargaInicio |
datetime | Inicio del plazo de carga de datos |
cargaFin |
datetime | Fin del plazo de carga de datos |
alegacionesInicio |
datetime? | Inicio del período de alegaciones (null si no aplica) |
alegacionesFin |
datetime? | Fin del período de alegaciones |
preparacionInicio |
datetime? | Inicio de la fase de preparación |
preparacionFin |
datetime? | Fin de la fase de preparación |
calificacionesProvisionales |
datetime? | Fecha de publicación de calificaciones provisionales |
calificacionesDefinitivas |
datetime? | Fecha de publicación de calificaciones definitivas |
Campos del objeto prorroga
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador de la prórroga |
fechaInicio |
date | Fecha de inicio de la prórroga (YYYY-MM-DD) |
fechaFin |
date | Fecha de fin de la prórroga (YYYY-MM-DD) |
Campos nulos en el calendario
Los campos del calendario que aún no tienen fecha asignada devuelven null. Esto es normal: el Comisionado va definiendo las fechas a medida que avanza la evaluación.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La evaluación no existe o la entidad no participa en ella |
Calendario de una evaluación
Devuelve únicamente las fechas clave de la evaluación, sin el resto de información del detalle. Útil para consultas rápidas del calendario.
Ejemplo de petición
Respuesta exitosa (200)
{
"periodoInicio": "2024-01-01T00:00:00Z",
"periodoFin": "2024-12-31T23:59:59Z",
"cargaInicio": "2025-01-15T00:00:00Z",
"cargaFin": "2025-03-15T23:59:59Z",
"alegacionesInicio": null,
"alegacionesFin": null,
"preparacionInicio": null,
"preparacionFin": null,
"calificacionesProvisionales": null,
"calificacionesDefinitivas": null
}
La estructura de campos es idéntica al objeto calendario del detalle de evaluación (ver tabla anterior).
Uso recomendado
Use este endpoint para verificaciones rápidas de plazos (por ejemplo, comprobar si el período de carga sigue abierto) sin necesidad de obtener todo el detalle de la evaluación.
Obligaciones de publicidad activa
Devuelve el árbol completo de obligaciones de publicidad activa definidas para la evaluación. Este árbol representa la estructura jerárquica que la entidad debe cumplimentar: tipos contienen categorías, y las categorías contienen obligaciones (ítems).
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
idevaluacion |
int | ID de la evaluación |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/evaluaciones/10/obligaciones",
headers=headers
)
arbol = response.json()
# Recorrer el árbol: tipos -> categorías -> obligaciones
for tipo in arbol:
print(f"\nTipo: {tipo['nombre']}")
for cat in tipo.get("categorias", []):
print(f" Categoría: {cat['nombre']}")
for ob in cat.get("obligaciones", []):
print(f" - [{ob['id']}] {ob['nombre']}")
var response = await client.GetAsync($"{baseUrl}/evaluaciones/10/obligaciones");
var json = await response.Content.ReadAsStringAsync();
var arbol = JsonConvert.DeserializeObject<List<TipoPA>>(json);
foreach (var tipo in arbol)
{
Console.WriteLine($"Tipo: {tipo.Nombre}");
foreach (var cat in tipo.Categorias)
{
Console.WriteLine($" Categoría: {cat.Nombre}");
foreach (var ob in cat.Obligaciones)
{
Console.WriteLine($" - [{ob.Id}] {ob.Nombre}");
}
}
}
Respuesta exitosa (200)
[
{
"id": 1,
"nombre": "Información institucional y organizativa",
"orden": 1,
"categorias": [
{
"id": 10,
"nombre": "Información sobre la institución",
"orden": 1,
"obligaciones": [
{
"id": 100,
"nombre": "Organigrama actualizado",
"descripcion": "Organigrama que identifique a los responsables...",
"orden": 1,
"criterios": [
{
"id": 500,
"criterio": "Existe",
"valorMaximo": 5
},
{
"id": 501,
"criterio": "Actualizado",
"valorMaximo": 3
}
]
},
{
"id": 101,
"nombre": "Funciones y competencias",
"descripcion": "Información sobre las funciones que desarrolla...",
"orden": 2,
"criterios": [
{
"id": 502,
"criterio": "Existe",
"valorMaximo": 5
}
]
}
]
}
]
}
]
Estructura jerárquica
graph TD
A["Árbol de obligaciones"] --> B["Tipo 1: Información institucional"]
A --> C["Tipo 2: Información económica"]
A --> D["Tipo N: ..."]
B --> E["Categoría 1.1"]
B --> F["Categoría 1.2"]
E --> G["Obligación 1.1.1"]
E --> H["Obligación 1.1.2"]
G --> I["Criterio: Existe (max 5)"]
G --> J["Criterio: Actualizado (max 3)"]
Campos del tipo
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador del tipo |
nombre |
string | Nombre del tipo de publicidad activa |
orden |
int | Orden de presentación |
categorias |
array | Categorías dentro de este tipo |
Campos de la categoría
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador de la categoría |
nombre |
string | Nombre de la categoría |
orden |
int | Orden de presentación |
obligaciones |
array | Obligaciones dentro de esta categoría |
Campos de la obligación
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador de la obligación |
nombre |
string | Nombre descriptivo de la obligación |
descripcion |
string | Descripción detallada de lo que se evalúa |
orden |
int | Orden de presentación |
criterios |
array | Criterios de evaluación aplicables |
Campos del criterio
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador del criterio |
criterio |
string | Nombre del criterio (ej: "Existe", "Actualizado") |
valorMaximo |
int | Puntuación máxima que se puede obtener en este criterio |
Uso de los IDs
Los IDs de las obligaciones de este árbol son los que se usan como referencia para entender la estructura. Sin embargo, al actualizar publicidad activa con PUT /declaraciones/{id}/pa/{id}, el obligacionId que se envía es el campo id del árbol de PA de la declaración (endpoint GET /declaraciones/{id}/pa), no de este árbol de evaluación.
Caché local
El árbol de obligaciones de una evaluación no cambia durante su vigencia. Es seguro cachearlo localmente para evitar peticiones repetidas.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La evaluación no existe o la entidad no participa en ella |
Declaraciones
Declaraciones
Las declaraciones representan la participación de una entidad en una evaluación concreta. Cada entidad tiene una declaración por evaluación, y a través de ella se remite toda la información: publicidad activa, cuestionarios, derecho de acceso y documentos.
Ciclo de vida de una declaración
Una declaración pasa por diferentes estados a lo largo del proceso de evaluación:
stateDiagram-v2
[*] --> SinAbrir: Evaluación abierta
SinAbrir --> NoPresentada: Inicializar
NoPresentada --> NoPresentada: Rellenar datos
NoPresentada --> PendienteRevision: Presentar
PendienteRevision --> Revisada: Comisionado revisa
Revisada --> AlegacionesAbiertas: Período alegaciones
AlegacionesAbiertas --> PendienteRevisionAlegaciones: Presentar alegaciones
PendienteRevisionAlegaciones --> Finalizada: Revisión final
Revisada --> Finalizada: Sin alegaciones
Finalizada --> [*]
Estados de la declaración
| Código | Estado | Descripción | Qué puede hacer la entidad |
|---|---|---|---|
0 |
SinAbrir | La entidad no ha iniciado la declaración | Inicializar |
1 |
NoPresentada | Declaración iniciada, en edición | Editar PA, cuestionarios, DA, presentar |
2 |
PendienteRevision | Presentada, esperando revisión del Comisionado | Solo lectura |
3 |
Revisada | Revisada con calificaciones provisionales | Solo lectura |
4 |
AlegacionesAbiertas | Período de alegaciones abierto | Presentar alegaciones |
5 |
PendienteRevisionAlegaciones | Alegaciones presentadas | Solo lectura |
6 |
Finalizada | Evaluación completada con notas definitivas | Solo lectura |
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/declaraciones/{id} |
Estado actual de la declaración |
POST |
/declaraciones/{id}/inicializar |
Inicializar la declaración |
GET |
/declaraciones/{id}/analisis |
Análisis de completitud |
GET |
/declaraciones/{id}/borrador |
Descargar borrador PDF |
Restricciones por estado
La mayoría de operaciones de escritura (actualizar PA, cuestionarios, DA) solo están permitidas cuando la declaración está en estado NoPresentada (1) o AlegacionesAbiertas (4). Consulte la tabla de restricciones por estado para más detalle.
Flujo típico de integración
sequenceDiagram
participant S as Sistema externo
participant API as API T-Canaria
S->>API: GET /evaluaciones
API-->>S: Lista de evaluaciones
S->>API: GET /declaraciones/{id}
API-->>S: Estado actual
alt Estado = SinAbrir
S->>API: POST /declaraciones/{id}/inicializar
API-->>S: Declaración inicializada
end
S->>API: GET /declaraciones/{id}/pa
API-->>S: Árbol de PA con IDs
S->>API: PUT /declaraciones/{id}/pa (bulk)
API-->>S: Obligaciones actualizadas
S->>API: GET /declaraciones/{id}/cuestionarios
API-->>S: Estructura cuestionarios
S->>API: PUT /declaraciones/{id}/cuestionarios/{idcuest}
API-->>S: Respuestas guardadas
S->>API: GET /declaraciones/{id}/analisis
API-->>S: Porcentajes y notas parciales
Declaración vs. evaluación
No confundir el ID de la declaración con el ID de la evaluación. La declaración es específica de cada entidad dentro de una evaluación. Use GET /evaluaciones para obtener ambos IDs.
Estado de una declaración
Devuelve el estado actual de una declaración concreta. Permite verificar en qué fase se encuentra la declaración antes de realizar operaciones sobre ella.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/declaraciones/5001", headers=headers)
declaracion = response.json()
print(f"Estado: {declaracion['estado']} ({declaracion['estadoCodigo']})")
print(f"Puntuación: {declaracion['ultimaPuntuacion']}")
Respuesta exitosa (200)
{
"declaracionId": 5001,
"evaluacionId": 10,
"evaluacion": "",
"estado": "NoPresentada",
"estadoCodigo": 1,
"ultimaPuntuacion": 0
}
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
declaracionId |
int | Identificador de la declaración |
evaluacionId |
int | Identificador de la evaluación a la que pertenece |
evaluacion |
string | Título de la evaluación (puede estar vacío) |
estado |
string | Estado descriptivo de la declaración |
estadoCodigo |
int | Código numérico del estado (0-6) |
ultimaPuntuacion |
decimal | Última puntuación obtenida (0 si no hay calificación aún) |
Tabla de estados
| Código | Estado | Permite escritura |
|---|---|---|
0 |
SinAbrir | No (solo inicializar) |
1 |
NoPresentada | Sí |
2 |
PendienteRevision | No |
3 |
Revisada | No |
4 |
AlegacionesAbiertas | Sí (alegaciones) |
5 |
PendienteRevisionAlegaciones | No |
6 |
Finalizada | No |
Verificar antes de escribir
Antes de enviar datos (PA, cuestionarios, DA), verifique que la declaración está en un estado que permite escritura. Si intenta escribir en un estado no permitido, la API devolverá un error 400.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad autenticada |
Seguridad
Solo puede consultar declaraciones de la entidad autenticada. Si intenta acceder a una declaración de otra entidad, recibirá un error 404 (no 403) para no revelar la existencia del recurso.
Inicializar declaración
Crea la estructura de datos necesaria para que la entidad pueda empezar a rellenar la declaración. Esta operación genera los registros internos de publicidad activa, cuestionarios y derecho de acceso vinculados a la declaración.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Body
No requiere body. La petición se envía sin contenido.
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(
f"{BASE_URL}/declaraciones/5001/inicializar",
headers=headers
)
if response.status_code == 200:
print("Declaración inicializada correctamente")
else:
print(f"Error: {response.json()}")
Respuesta exitosa (200)
Estados permitidos
Este endpoint solo funciona cuando la declaración está en uno de estos estados:
| Estado | Código | Resultado |
|---|---|---|
SinAbrir |
0 | Se inicializa correctamente |
NoPresentada |
1 | Se reinicializa (si no tenía estructura) |
No inicializar dos veces
Si la declaración ya fue inicializada previamente (por ejemplo, desde el portal web de entidades), el endpoint devolverá un error 500. Esto es un comportamiento esperado: la declaración ya tiene estructura y está lista para ser cumplimentada. Verifique el estado con GET /declaraciones/{id} antes de llamar a este endpoint.
Flujo recomendado
sequenceDiagram
participant S as Sistema externo
participant API as API T-Canaria
S->>API: GET /declaraciones/{id}
API-->>S: estadoCodigo: 0 (SinAbrir)
S->>API: POST /declaraciones/{id}/inicializar
API-->>S: "Declaración inicializada"
S->>API: GET /declaraciones/{id}
API-->>S: estadoCodigo: 1 (NoPresentada)
Note over S: Ahora puede rellenar PA, cuestionarios, DA
Errores posibles
| Código | Descripción |
|---|---|
400 |
La declaración está en un estado que no permite inicialización (ej: ya presentada) |
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
500 |
La declaración ya fue inicializada previamente |
Buena práctica
Siempre verifique el estado de la declaración con GET /declaraciones/{id} antes de intentar inicializar. Solo necesita inicializar si el estadoCodigo es 0 (SinAbrir).
Análisis de completitud
Devuelve los porcentajes de cumplimentación y las notas parciales actuales de la declaración. Permite a la entidad conocer su progreso antes de presentar.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/declaraciones/5001/analisis", headers=headers)
analisis = response.json()
print(f"PA completada: {analisis['porcentajePublicidadActiva']}%")
print(f"DA completado: {analisis['porcentajeDerechoAcceso']}%")
print(f"Nota final estimada: {analisis['notaFinal']}")
var response = await client.GetAsync($"{baseUrl}/declaraciones/5001/analisis");
var json = await response.Content.ReadAsStringAsync();
var analisis = JsonConvert.DeserializeObject<AnalisisCompletitud>(json);
Console.WriteLine($"PA: {analisis.PorcentajePublicidadActiva}%");
Console.WriteLine($"Nota final: {analisis.NotaFinal}");
Respuesta exitosa (200)
{
"declaracionId": 5001,
"estado": "NoPresentada",
"porcentajePublicidadActiva": 45.5,
"porcentajeDerechoAcceso": 0,
"notaPublicidadActiva": 32.1,
"notaIcio": 28.7,
"notaIcs": 0,
"notaItv": 0,
"notaFinal": 15.2
}
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
declaracionId |
int | Identificador de la declaración |
estado |
string | Estado actual de la declaración |
porcentajePublicidadActiva |
decimal | Porcentaje de obligaciones de PA cumplimentadas (0-100) |
porcentajeDerechoAcceso |
decimal | Porcentaje de datos de DA cumplimentados (0-100) |
notaPublicidadActiva |
decimal | Nota parcial de publicidad activa |
notaIcio |
decimal | Nota del índice ICIO (cumplimiento de obligaciones de información) |
notaIcs |
decimal | Nota del índice ICS (cuestionario de servicio) |
notaItv |
decimal | Nota del índice ITV (transparencia voluntaria) |
notaFinal |
decimal | Nota final estimada (ponderación de todos los índices) |
Componentes de la nota final
pie title Componentes de la nota final
"ICIO (Publicidad Activa)" : 60
"ICS (Cuestionarios)" : 20
"ITV (Transparencia Voluntaria)" : 20
Notas estimadas
Las notas que devuelve este endpoint son estimaciones basadas en los datos actuales. Las notas definitivas se calculan tras la revisión por parte del Comisionado y pueden diferir de estas estimaciones.
Índices de evaluación
- ICIO: Índice de Cumplimiento de Obligaciones de Información. Se calcula a partir del cumplimiento de las obligaciones de publicidad activa.
- ICS: Índice de Calidad del Soporte Web. Se calcula a partir del cuestionario de soporte web y accesibilidad del portal de transparencia.
- ITV: Índice de Transparencia Voluntaria. Se calcula a partir de la publicación proactiva de información adicional no obligatoria.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Monitoreo de progreso
Use este endpoint periódicamente durante la fase de carga para monitorizar el progreso de cumplimentación de la declaración y estimar la nota final.
Descargar borrador PDF
Descarga el borrador de presentación de la declaración en formato PDF. Este documento resume el estado actual de la declaración con toda la información remitida.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/declaraciones/5001/borrador",
headers=headers
)
if response.status_code == 200:
filename = "borrador_declaracion_5001.pdf"
with open(filename, "wb") as f:
f.write(response.content)
print(f"Borrador guardado en: {filename}")
else:
print(f"Error: {response.status_code}")
Respuesta exitosa (200)
La respuesta es un fichero PDF binario, no JSON:
Content-Type: application/pdf
Content-Disposition: attachment; filename="Borrador_AYUNTAMIENTO_DE_EJEMPLO_5001.pdf"
| Cabecera | Descripción |
|---|---|
Content-Type |
application/pdf |
Content-Disposition |
Nombre sugerido para el fichero descargado |
Respuesta binaria
Este endpoint devuelve un fichero PDF, no JSON. Asegúrese de manejar la respuesta como datos binarios, no como texto.
Contenido del borrador
El PDF incluye:
- Datos de la entidad y la evaluación
- Resumen del estado de publicidad activa
- Resumen de cuestionarios cumplimentados
- Datos de derecho de acceso
- Notas estimadas
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Uso del borrador
El borrador es útil para revisión interna antes de la presentación formal. Permite verificar que toda la información cargada es correcta y completa.
Notas y puntuaciones
Una vez que el Comisionado de Transparencia publica las calificaciones provisionales (estado AlegacionesAbiertas, código 4), la entidad puede consultar sus notas a través de dos endpoints:
GET /notas— resumen de puntuaciones (nota final, ranking, medias)GET /notas/detalle— desglose completo por criterio, sección y pregunta
Disponibilidad
Estos endpoints solo están disponibles a partir del estado AlegacionesAbiertas (4). En estados anteriores devuelven 400.
Consultar resumen de notas
Devuelve un resumen con la nota final, desglose por índice (ICIO, ICS, ITV), posición en el ranking y medias comparativas.
Respuesta exitosa (200)
{
"entidad": "AYUNTAMIENTO DE EJEMPLO",
"evaluacion": "Evaluación 2024",
"estado": "AlegacionesAbiertas",
"notas": {
"notaFinal": 7.85,
"notaIcio": 8.20,
"notaIcs": 7.40,
"notaItv": 7.50
},
"ranking": {
"posicion": 12,
"total": 88
},
"notasMedias": {
"mediaCanaria": 6.95,
"mediaNacional": 6.40
},
"notasAnteriores": [
{ "evaluacion": "Evaluación 2023", "notaFinal": 7.10 },
{ "evaluacion": "Evaluación 2022", "notaFinal": 6.80 }
]
}
| Campo | Tipo | Descripción |
|---|---|---|
entidad |
string | Nombre de la entidad |
evaluacion |
string | Título de la evaluación |
estado |
string | Estado actual de la declaración |
notas.notaFinal |
decimal | Nota global (0–10) |
notas.notaIcio |
decimal | Índice de Contenidos de Información Obligatoria (ICIO) |
notas.notaIcs |
decimal | Índice de Cuestionarios Sectoriales (ICS) |
notas.notaItv |
decimal | Índice de Transparencia en la Valoración (ITV) |
ranking.posicion |
int | Posición de la entidad entre todas las evaluadas |
ranking.total |
int | Total de entidades evaluadas |
notasMedias |
object | Medias comparativas (canaria y nacional) |
notasAnteriores |
array | Notas de evaluaciones anteriores (historial) |
Notas provisionales vs. definitivas
- Estados 4 y 5 (
AlegacionesAbiertas,PendRevAlegaciones): las notas son provisionales. - Estado 6 (
Finalizada): las notas son definitivas.
El campo estado de la respuesta indica en qué fase se encuentran.
Consultar desglose detallado
Devuelve la estructura completa con las notas desglosadas por sección, criterio y pregunta. Útil para identificar qué áreas han obtenido mejor o peor puntuación.
La respuesta incluye los mismos datos del resumen más los arrays detallados:
ics[]— secciones de cuestionarios con sus preguntas y puntuaciones individualesitv[]— ítems de transparencia en la valoraciónicio[]— obligaciones de publicidad activa agrupadas por tipo, con puntuación por criterio
Ejemplo de uso
Errores posibles
| Código | Tipo | Descripción |
|---|---|---|
400 |
peticion-incorrecta |
La declaración no está en un estado con notas disponibles (estado < 4) |
401 |
no-autorizado |
Token ausente, expirado o inválido |
404 |
recurso-no-encontrado |
La declaración no existe o no pertenece a la entidad |
{
"type": "https://api.evaluax.es/errores/peticion-incorrecta",
"title": "Peticion incorrecta",
"status": 400,
"detail": "Las notas no están disponibles en estado 'NoPresentada'. Las notas se publican a partir del periodo de alegaciones (estado 4).",
"instance": "/apientidades/v1/declaraciones/5001/notas"
}
Flujo recomendado
Consulte el estado de la declaración con GET /declaraciones/{id} antes de intentar ver las notas. Si estadoCodigo es menor que 4, las notas aún no están publicadas.
Informes PDF
Los informes son documentos PDF oficiales generados por el Comisionado de Transparencia: justificantes de presentación, informes provisionales e informes definitivos con la calificación final.
Diferencia con documentos
Este endpoint (/declaraciones/{id}/informes) devuelve los informes oficiales generados por el Comisionado. Es distinto de GET /documentos/declaracion/{id}, que lista los documentos de la declaración en general.
Disponibilidad
Los informes solo son accesibles cuando se cumple alguna de estas condiciones:
- La declaración está en estado
Finalizada(6): siempre disponibles. - La evaluación está en fase
EvaluandoLaDeclaracion(3),EvaluandoLasAlegaciones(5) oCerrada(6): disponibles independientemente del estado de la declaración.
En cualquier otro caso el endpoint devuelve 400.
Listar informes disponibles
Respuesta exitosa (200)
[
{
"id": 14,
"tipo": 3,
"tipoDescripcion": "Justificante de presentación de declaración",
"registro": "DECL-2024-1001",
"descripcion": "Justificante de presentación",
"tamanoBytes": 145280,
"fechaSubida": "2024-03-15T18:42:00Z"
},
{
"id": 27,
"tipo": 0,
"tipoDescripcion": "Informe provisional",
"registro": "EVAL-2024-1001-PROV",
"descripcion": "Informe provisional de evaluación",
"tamanoBytes": 512640,
"fechaSubida": "2024-09-20T10:15:00Z"
}
]
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del informe. Usar en GET /informes/{id} para descargarlo |
tipo |
int | Tipo de documento (ver tabla de tipos) |
tipoDescripcion |
string | Descripción legible del tipo |
registro |
string | Número de registro oficial |
descripcion |
string | Descripción del documento |
tamanoBytes |
int | Tamaño del fichero en bytes |
fechaSubida |
datetime | Fecha y hora de publicación (UTC) |
Tipos de informe
| Código | Descripción | Cuándo aparece |
|---|---|---|
0 |
Informe provisional | Tras la primera revisión del Comisionado |
1 |
Informe definitivo | Tras cerrar la evaluación |
2 |
Informe definitivo detallado | Junto al informe definitivo |
3 |
Justificante de presentación de declaración | Inmediatamente tras presentar |
4 |
Justificante de presentación de alegaciones | Inmediatamente tras presentar alegaciones |
Si aún no hay informes disponibles para la declaración, la respuesta es un array vacío [].
Descargar informe
Descarga el fichero PDF del informe indicado.
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
id |
int | ID del informe (obtenido de GET /informes) |
Respuesta exitosa (200)
Content-Type: application/pdf
Content-Disposition: attachment; filename="Informe_provisional_2024.pdf"
El cuerpo de la respuesta es el fichero PDF en binario.
Firma digital
Los informes definitivos incluyen sello electrónico PAdES del Comisionado de Transparencia. Puede verificar la firma con Adobe Acrobat Reader o cualquier validador de firma PDF.
Ejemplo de descarga
Errores posibles
| Código | Tipo | Descripción |
|---|---|---|
400 |
peticion-incorrecta |
La declaración/evaluación no está en un estado que permita ver informes |
401 |
no-autorizado |
Token ausente, expirado o inválido |
404 |
recurso-no-encontrado |
La declaración o el informe no existe, o no pertenece a la entidad |
Flujo recomendado
- Llame a
GET /declaraciones/{id}/informespara obtener la lista de informes disponibles y sus IDs. - Use el
idde cada informe para descargarlo conGET /declaraciones/{id}/informes/{id}.
Publicidad activa
Publicidad activa
La publicidad activa (PA) es el núcleo de la evaluación de transparencia. Comprende todas las obligaciones de información que las entidades deben publicar proactivamente en sus portales de transparencia.
Estructura del árbol de PA
La información de publicidad activa se organiza en una jerarquía de tres niveles:
graph TD
A["Declaración"] --> B["Tipo 1: Información institucional"]
A --> C["Tipo 2: Información económica"]
A --> D["Tipo 3: Información jurídica"]
B --> E["Categoría 1.1: Organización"]
B --> F["Categoría 1.2: Planificación"]
E --> G["Obligación: Organigrama"]
E --> H["Obligación: Funciones y competencias"]
G --> I["Enlaces publicados"]
G --> J["Aclaraciones"]
G --> K["Autoevaluación por criterio"]
| Nivel | Descripción | Ejemplo |
|---|---|---|
| Tipo | Agrupación principal de obligaciones | "Información institucional y organizativa" |
| Categoría | Subagrupación temática | "Información sobre la institución" |
| Obligación | Ítem individual que la entidad debe cumplir | "Organigrama actualizado" |
Qué contiene cada obligación
Cada obligación en el árbol de PA contiene:
- Enlaces: URLs donde la entidad publica la información (máximo 10)
- Aclaraciones: Texto libre con explicaciones de la entidad
- Opción de publicación: Indica si publica, no publica por tipo de actividad, otra razón, etc.
- Autoevaluación: Valores por criterio de evaluación (puntuación que la entidad se asigna)
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/declaraciones/{id}/pa |
Obtener árbol completo de PA |
PUT |
/declaraciones/{id}/pa/{id} |
Actualizar una obligación |
PUT |
/declaraciones/{id}/pa |
Actualización masiva (hasta 500 ítems) |
Opciones de publicación
| Código | Descripción |
|---|---|
0 |
Publica información — La entidad publica esta obligación y proporciona enlaces |
1 |
No publica por tipo de actividad — La obligación no aplica al tipo de entidad |
2 |
No publica por otra razón — No publica pero por una razón diferente |
3 |
No publica por carecer de la información — No dispone de la información |
Flujo típico
sequenceDiagram
participant S as Sistema externo
participant API as API T-Canaria
S->>API: GET /declaraciones/{id}/pa
API-->>S: Árbol completo con IDs y estado actual
Note over S: Recorrer árbol, obtener IDs de obligaciones
S->>API: PUT /declaraciones/{id}/pa (bulk)
Note right of S: Enviar hasta 500 ítems
API-->>S: N obligaciones actualizadas
S->>API: GET /declaraciones/{id}/analisis
API-->>S: Porcentaje PA y nota estimada
IDs de obligaciones
Al actualizar PA, use el campo id de cada obligación del árbol devuelto por GET /declaraciones/{id}/pa. No confundir con el idobligacion que es el ID del catálogo de obligaciones de la evaluación.
Autoevaluación
La autoevaluación es opcional. Si se proporcionan valores, deben corresponder a criterios válidos de la obligación y estar dentro del rango 0 al valor máximo del criterio. Ver autoevaluación para más detalle.
Consultar publicidad activa
Devuelve el árbol jerárquico completo de publicidad activa de la declaración, incluyendo tipos, categorías y obligaciones con el estado actual de cada una (URLs proporcionadas, aclaraciones, autoevaluación).
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/declaraciones/5001/pa", headers=headers)
arbol = response.json()
# Recorrer el árbol y contar obligaciones cumplimentadas
total = 0
cumplimentadas = 0
for tipo in arbol:
for cat in tipo.get("categorias", []):
for ob in cat.get("obligaciones", []):
total += 1
if ob.get("enlaces") and len(ob["enlaces"]) > 0:
cumplimentadas += 1
print(f"Obligaciones cumplimentadas: {cumplimentadas}/{total}")
var response = await client.GetAsync($"{baseUrl}/declaraciones/5001/pa");
var json = await response.Content.ReadAsStringAsync();
var arbol = JsonConvert.DeserializeObject<List<TipoPADto>>(json);
foreach (var tipo in arbol)
{
Console.WriteLine($"Tipo: {tipo.tipo}");
foreach (var cat in tipo.categorias)
{
foreach (var ob in cat.obligaciones)
{
var estado = ob.enlaces?.Count > 0 ? "Con datos" : "Sin datos";
Console.WriteLine($" [{ob.id}] {ob.obligacion}: {estado}");
}
}
}
Respuesta exitosa (200)
[
{
"idtipo": 1,
"tipo": "Información institucional y organizativa",
"peso": 30,
"color": "#1e40af",
"porcentajecumplimentado": 45.5,
"autoevaluacion": 6.2,
"categorias": [
{
"idcategoria": 10,
"categoria": "Información sobre la institución",
"peso": 50,
"porcentajecumplimentado": 45.5,
"obligaciones": [
{
"id": 7001,
"idobligacion": 100,
"obligacion": "Organigrama actualizado",
"descripcion": "Organigrama que identifique a los responsables...",
"peso": 5,
"aclaracionesentidad": "Publicado en la sección de organización",
"nocumpleobligacion": false,
"opcionpublicacion": 0,
"notaautoevaluacion": 6.5,
"enlaces": [
{
"id": 1234,
"url": "https://portal.entidad.es/transparencia/organigrama",
"valida": true,
"aceptado": false,
"orden": 0
}
],
"autoevaluacion": [
{
"id": 949162,
"idcriterio": 1,
"criterio": "Contenido",
"valor": 5,
"valorinicial": 0
},
{
"id": 949163,
"idcriterio": 2,
"criterio": "Actualización",
"valor": 0,
"valorinicial": 0
}
]
}
]
}
]
}
]
Campos del nivel Tipo
| Campo | Tipo | Descripción |
|---|---|---|
idtipo |
int | ID del tipo de publicidad activa |
tipo |
string | Nombre descriptivo del tipo |
peso |
int | Peso en la nota total (%) |
color |
string | Color para presentación visual (hex) |
porcentajecumplimentado |
decimal | Porcentaje de obligaciones cumplimentadas |
autoevaluacion |
decimal | Nota media de autoevaluación del tipo (escalar) |
categorias |
array | Categorías del tipo |
Campos del nivel Categoría
| Campo | Tipo | Descripción |
|---|---|---|
idcategoria |
int | ID de la categoría |
categoria |
string | Nombre de la categoría |
peso |
int | Peso dentro del tipo (%) |
porcentajecumplimentado |
decimal | Porcentaje de obligaciones cumplimentadas |
obligaciones |
array | Obligaciones de la categoría |
Campos de la obligación
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID para usar en PUT (ID de la remisión PA en esta declaración) |
idobligacion |
int | ID del catálogo de obligaciones (referencia, no usar en PUT) |
obligacion |
string | Nombre descriptivo de la obligación |
descripcion |
string | Descripción detallada |
peso |
int | Peso dentro de la categoría |
aclaracionesentidad |
string | Texto de aclaraciones introducido por la entidad |
nocumpleobligacion |
bool | Si la entidad ha declarado no cumplir esta obligación |
opcionpublicacion |
int | Opción de publicación actual (0-3) |
notaautoevaluacion |
decimal | Nota de autoevaluación calculada para esta obligación |
enlaces |
array | URLs publicadas (ver campos de enlace) |
autoevaluacion |
array | Valores de autoevaluación por criterio (ver campos de autoevaluación) |
Campos de un enlace
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del enlace (solo lectura) |
url |
string | URL completa donde se publica la información |
valida |
bool | Si la URL ha sido verificada como accesible |
aceptado |
bool | Si el evaluador del Comisionado ha aceptado el enlace (solo lectura) |
orden |
int | Orden de presentación (0 = primera) |
Campos de un ítem de autoevaluación
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del ítem a usar en el PUT (ID en declaracionesevaluacionespavalores) |
idcriterio |
int | ID del criterio de evaluación (referencia) |
criterio |
string | Nombre del criterio (ej: "Contenido", "Actualización") |
valor |
int | Valor actual asignado por la entidad (0-100) |
valorinicial |
int | Valor inicial antes de modificaciones |
ID correcto para actualizar
Al hacer PUT, use el campo id de la obligación (ej: 7001), NO el campo idobligacion (ej: 100). El id es el identificador único dentro de esta declaración concreta. El idobligacion es solo una referencia al catálogo.
ID de autoevaluación en el PUT
Al enviar autoevaluación en el PUT, use el campo id de cada ítem del array autoevaluacion[] (ej: 949162). No use idcriterio. El id identifica el registro concreto de este criterio en esta declaración.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Actualizar una obligación
Actualiza los datos de una obligación individual de publicidad activa: enlaces, aclaraciones, opción de publicación y autoevaluación.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
idobligacion |
int | ID de la obligación (campo id del árbol PA, NO idobligacion) |
Body de la petición
{
"obligacionId": 7001,
"enlaces": [
{
"url": "https://portal.entidad.es/transparencia/presupuestos",
"valida": true,
"orden": 0
},
{
"url": "https://sede.entidad.es/presupuestos-2024",
"valida": true,
"orden": 1
}
],
"aclaraciones": "Publicado en ambos portales",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": [
{ "id": 456, "valor": 3 }
]
}
Tabla de campos del body
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
obligacionId |
int | Sí | ID del ítem PA (campo id del GET /pa) |
enlaces |
array | No | URLs donde se publica la información |
enlaces[].url |
string | Sí | URL completa (debe empezar por http:// o https://) |
enlaces[].valida |
bool | No | Si la URL ha sido validada como accesible (default: false) |
enlaces[].orden |
int | No | Orden de presentación, de 0 a 99 (default: 0) |
aclaraciones |
string | No | Texto libre de aclaraciones (máximo 5.000 caracteres) |
opcionPublicacion |
int | Sí | Opción de publicación (0-3, ver tabla) |
noCumple |
bool | No | Indica que la entidad declara no cumplir (default: false) |
autoevaluacion |
array | No | Valores de autoevaluación por criterio |
autoevaluacion[].id |
int | Sí | ID del ítem de autoevaluación (campo id del array autoevaluacion[] del GET /pa, ej: 949162) |
autoevaluacion[].valor |
int | Sí | Valor discreto para el criterio (0 a 100; ver tabla de valores admitidos) |
Opciones de publicación
| Código | Descripción | Requiere enlaces |
|---|---|---|
0 |
Publica información | Sí (al menos 1 URL) |
1 |
No publica por tipo de actividad | No |
2 |
No publica por otra razón | No |
3 |
No publica por carecer de la información | No |
Ejemplo de petición
curl -s -X PUT \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/pa/7001" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"obligacionId": 7001,
"enlaces": [
{
"url": "https://portal.entidad.es/transparencia/presupuestos",
"valida": true,
"orden": 0
}
],
"aclaraciones": "Presupuestos publicados en el portal",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": [
{ "id": 456, "valor": 5 },
{ "id": 457, "valor": 2 }
]
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"obligacionId": 7001,
"enlaces": [
{
"url": "https://portal.entidad.es/transparencia/presupuestos",
"valida": True,
"orden": 0
}
],
"aclaraciones": "Presupuestos publicados en el portal",
"opcionPublicacion": 0,
"noCumple": False,
"autoevaluacion": [
{"id": 456, "valor": 5},
{"id": 457, "valor": 2}
]
}
response = requests.put(
f"{BASE_URL}/declaraciones/5001/pa/7001",
headers=headers,
json=payload
)
print(response.json())
var payload = new
{
obligacionId = 7001,
enlaces = new[]
{
new { url = "https://portal.entidad.es/transparencia/presupuestos", valida = true, orden = 0 }
},
aclaraciones = "Presupuestos publicados en el portal",
opcionPublicacion = 0,
noCumple = false,
autoevaluacion = new[]
{
new { id = 456, valor = 5 },
new { id = 457, valor = 2 }
}
};
var content = new StringContent(
JsonConvert.SerializeObject(payload),
Encoding.UTF8,
"application/json"
);
var response = await client.PutAsync(
$"{baseUrl}/declaraciones/5001/pa/7001", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Respuesta exitosa (200)
Validaciones
| Validación | Límite | Error |
|---|---|---|
obligacionId existe en la declaración |
Verificado contra árbol PA real | 404 |
opcionPublicacion en rango |
0-3 | 400 |
| Cada enlace tiene URL | No vacía | 400 |
| URL bien formada | Empieza por http:// o https:// |
400 |
| Longitud URL | Max 2.048 caracteres | 400 |
| Orden de enlace | 0-99 | 400 |
| Cantidad de enlaces | Max 10 por obligación | 400 |
| Aclaraciones | Max 5.000 caracteres | 400 |
| Valor autoevaluación | 0 a 100 | 400 |
| ID autoevaluación | > 0 | 400 |
| Cantidad criterios autoevaluación | Max 10 | 400 |
Estado de la declaración
Este endpoint solo funciona cuando la declaración está en estado NoPresentada (1) o AlegacionesAbiertas (4). En otros estados, devuelve error 400.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida (ver tabla de validaciones) |
401 |
Token ausente, expirado o inválido |
404 |
La declaración u obligación no existe o no pertenece a la entidad |
Actualización masiva
Si necesita actualizar muchas obligaciones a la vez, use el endpoint bulk que permite enviar hasta 500 ítems en una sola petición transaccional.
Actualización masiva (bulk)
Permite actualizar múltiples obligaciones de publicidad activa en una sola petición. La operación es transaccional: si un solo ítem falla la validación, no se modifica ninguno.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Body de la petición
{
"items": [
{
"obligacionId": 7001,
"enlaces": [
{
"url": "https://portal.entidad.es/transparencia/presupuestos",
"valida": true,
"orden": 0
}
],
"aclaraciones": "",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": []
},
{
"obligacionId": 7002,
"enlaces": [
{
"url": "https://portal.entidad.es/transparencia/normativa",
"valida": true,
"orden": 0
}
],
"aclaraciones": "Normativa publicada en sección específica",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": []
},
{
"obligacionId": 7003,
"enlaces": [],
"aclaraciones": "No aplica a nuestra tipología de entidad",
"opcionPublicacion": 1,
"noCumple": false,
"autoevaluacion": []
}
]
}
Campos del body
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
items |
array | Sí | Lista de obligaciones a actualizar (1-500) |
Cada ítem dentro de items tiene la misma estructura que el body del PUT individual. Consulte esa página para el detalle de cada campo.
Ejemplo completo de flujo
# 1. Obtener el árbol PA para conocer los IDs
ARBOL=$(curl -s "https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/pa" \
-H "Authorization: Bearer $TOKEN")
# 2. Enviar actualización masiva
curl -s -X PUT \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/pa" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"obligacionId": 7001,
"enlaces": [{"url": "https://portal.es/presupuestos", "valida": true, "orden": 0}],
"aclaraciones": "",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": []
},
{
"obligacionId": 7002,
"enlaces": [{"url": "https://portal.es/normativa", "valida": true, "orden": 0}],
"aclaraciones": "",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": []
}
]
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 1. Obtener árbol PA
arbol = requests.get(
f"{BASE_URL}/declaraciones/5001/pa",
headers=headers
).json()
# 2. Construir la lista de ítems a actualizar
items = []
for tipo in arbol:
for cat in tipo.get("categorias", []):
for ob in cat.get("obligaciones", []):
items.append({
"obligacionId": ob["id"],
"enlaces": [{"url": "https://portal.es/datos", "valida": True, "orden": 0}],
"aclaraciones": "",
"opcionPublicacion": 0,
"noCumple": False,
"autoevaluacion": []
})
# 3. Enviar en lotes de 500
for i in range(0, len(items), 500):
lote = items[i:i+500]
response = requests.put(
f"{BASE_URL}/declaraciones/5001/pa",
headers=headers,
json={"items": lote}
)
print(f"Lote {i//500 + 1}: {response.json()}")
// Construir la lista de ítems
var items = new List<object>();
foreach (var tipo in arbol)
{
foreach (var cat in tipo.Categorias)
{
foreach (var ob in cat.Obligaciones)
{
items.Add(new
{
obligacionId = ob.Id,
enlaces = new[] { new { url = "https://portal.es/datos", valida = true, orden = 0 } },
aclaraciones = "",
opcionPublicacion = 0,
noCumple = false,
autoevaluacion = Array.Empty<object>()
});
}
}
}
// Enviar en lotes de 500
foreach (var lote in items.Chunk(500))
{
var content = new StringContent(
JsonConvert.SerializeObject(new { items = lote }),
Encoding.UTF8, "application/json");
var response = await client.PutAsync(
$"{baseUrl}/declaraciones/5001/pa", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
Respuesta exitosa (200)
| Campo | Tipo | Descripción |
|---|---|---|
message |
string | Mensaje descriptivo |
count |
int | Número de obligaciones actualizadas |
Comportamiento transaccional
flowchart LR
A[Recibir ítems] --> B{Validar TODOS}
B -->|Todos válidos| C[Guardar datos]
B -->|Alguno inválido| D[Rechazar TODO]
C --> E[200 OK]
D --> F[400 con errores]
Todo o nada
La operación bulk es transaccional. Si un solo ítem tiene un error de validación (URL malformada, obligación inexistente, etc.), no se modifica ninguno y se devuelve un error 400 con el detalle de los errores encontrados (hasta los 10 primeros).
Límites
| Límite | Valor |
|---|---|
| Ítems por petición | 1 - 500 |
| Enlaces por obligación | Max 10 |
| Longitud URL | Max 2.048 caracteres |
| Longitud aclaraciones | Max 5.000 caracteres |
| Criterios autoevaluación | Max 10 por obligación |
Lotes grandes
Si tiene más de 500 obligaciones que actualizar, divida la lista en lotes de 500 y envíe cada lote como una petición separada.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida en uno o más ítems, o lista vacía/excede 500 |
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Estado de la declaración
Solo funciona cuando la declaración está en estado NoPresentada (1) o AlegacionesAbiertas (4).
Autoevaluación
La autoevaluación permite a la entidad asignar una puntuación a cada criterio de evaluación de una obligación de publicidad activa. Es un campo opcional que complementa la información de enlaces y aclaraciones.
Cómo funciona
Cada obligación del árbol de publicidad activa contiene un array autoevaluacion[]. Cada ítem del array representa un criterio de evaluación ya vinculado a esa obligación en esta declaración concreta. La entidad actualiza el campo valor de cada ítem para indicar su autoevaluación.
No existe un array criterios[] separado
La información de los criterios (nombre, ID de criterio) ya viene incluida dentro del propio array autoevaluacion[] de cada obligación. No hay un campo criterios[] independiente ni un campo valorMaximo por criterio.
graph LR
A["Obligación: Organigrama"] --> B["autoevaluacion[0]\nid: 949162\nidcriterio: 1\ncriterio: Contenido\nvalor: 0 ← modificar"]
A --> C["autoevaluacion[1]\nid: 949163\nidcriterio: 2\ncriterio: Actualización\nvalor: 0 ← modificar"]
Obtener los criterios de una obligación
Los criterios de cada obligación se obtienen al consultar el árbol PA. Vienen dentro del campo autoevaluacion[] de la propia obligación:
curl -s "https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/pa" \
-H "Authorization: Bearer $TOKEN" | jq '.[0].categorias[0].obligaciones[0].autoevaluacion'
Respuesta:
[
{
"id": 949162,
"idcriterio": 1,
"criterio": "Contenido",
"valor": 0,
"valorinicial": 0
},
{
"id": 949163,
"idcriterio": 2,
"criterio": "Actualización",
"valor": 0,
"valorinicial": 0
}
]
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del ítem a usar en el PUT (identifica el registro en esta declaración) |
idcriterio |
int | ID del criterio de evaluación (solo referencia) |
criterio |
string | Nombre del criterio |
valor |
int | Valor actual asignado (0-100) |
valorinicial |
int | Valor antes de cualquier modificación |
Enviar autoevaluación
La autoevaluación se envía como parte del PUT de actualización de la obligación. En el array autoevaluacion[] del body se usa el campo id del ítem (no idcriterio):
curl -s -X PUT \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/pa/7001" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"obligacionId": 7001,
"enlaces": [
{"url": "https://portal.entidad.es/organigrama", "valida": true, "orden": 0}
],
"aclaraciones": "",
"opcionPublicacion": 0,
"noCumple": false,
"autoevaluacion": [
{"id": 949162, "valor": 5},
{"id": 949163, "valor": 3}
]
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Obtener el árbol PA
arbol = requests.get(f"{BASE_URL}/declaraciones/5001/pa", headers=headers).json()
# Recorrer para encontrar la obligación y construir la autoevaluación
for tipo in arbol:
for cat in tipo.get("categorias", []):
for ob in cat.get("obligaciones", []):
if ob["id"] == 7001:
# Usar el 'id' de cada ítem de autoevaluación (no idcriterio)
autoevaluacion = [
{"id": item["id"], "valor": 5} # valor máximo de ejemplo
for item in ob.get("autoevaluacion", [])
]
payload = {
"obligacionId": ob["id"],
"enlaces": [{"url": "https://portal.es/organigrama", "valida": True, "orden": 0}],
"aclaraciones": "",
"opcionPublicacion": 0,
"noCumple": False,
"autoevaluacion": autoevaluacion
}
response = requests.put(
f"{BASE_URL}/declaraciones/5001/pa/{ob['id']}",
headers=headers,
json=payload
)
print(response.json())
Formato del array autoevaluación (body del PUT)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id |
int | Sí | id del ítem del GET (campo autoevaluacion[].id, ej: 949162) |
valor |
int | Sí | Valor discreto para el criterio (ver tabla de criterios y valores admitidos) |
Validaciones
| Validación | Límite | Error |
|---|---|---|
| ID del ítem | Debe ser > 0 y existir en la declaración | 400/500 |
| Valor | 0 a 100 | 400 "debe ser 0-100" |
| Cantidad de ítems | Max 10 por obligación | 400 "Máximo 10 criterios" |
Solo se admiten valores discretos — la API los valida
La API rechaza valores que no estén en el catálogo de esa evaluación concreta con un error 400. Antes de enviar autoevaluación, consulte GET /pa/criterios para obtener los valores admitidos exactos para su declaración.
Criterios de autoevaluación
Los criterios son los mismos en todas las evaluaciones, aunque los valores discretos admitidos pueden variar entre evaluaciones. Los pesos también pueden ajustarse por evaluación.
A continuación se muestran los criterios y sus valores para la evaluación vigente (entidades públicas 2025):
Criterio 1 — Contenido (peso: 40 %)
| Valor | Descripción |
|---|---|
0 |
Nada |
15 |
Casi nada |
50 |
Parcial |
85 |
Casi todo |
100 |
Total |
Criterio 2 — Última fecha de actualización publicada en la URL (peso: 30 %)
| Valor | Descripción |
|---|---|
0 |
No hay fecha visible en el portal |
50 |
Anterior al 1 de octubre de 2025 |
100 |
Último trimestre de 2025 o posterior |
Criterio 3 — Reutilización (peso: 20 %)
| Valor | Descripción |
|---|---|
0 |
Formato no reutilizable (PDF escaneado, JPG, PNG…) |
25 |
Formato poco reutilizable (PDF editable, HTML) |
50 |
Formato reutilizable propietario (XLS, XLSX, DOC, PPT…) |
100 |
Formato reutilizable abierto (ODS, ODT, XML, JSON, CSV, TXT…) |
Criterio 4 — Forma de publicación (peso: 5 %)
| Valor | Descripción |
|---|---|
0 |
No se puede acceder desde el portal o requiere conocimientos específicos |
50 |
Indirecta (el portal enlaza a otra web/plataforma donde hay que seguir buscando) |
100 |
Directa (publicado en el portal o enlace a la página concreta de la información) |
Criterio 5 — Accesibilidad (peso: 5 %)
| Valor | Descripción |
|---|---|
0 |
Más de 12 clics desde la portada del portal de transparencia |
10 |
12 clics |
20 |
11 clics |
30 |
10 clics |
40 |
9 clics |
50 |
8 clics |
60 |
7 clics |
70 |
6 clics |
80 |
5 clics |
90 |
4 clics |
100 |
Entre 1 y 3 clics |
Valores por evaluación
Los valores exactos admitidos y sus descripciones pueden diferir entre evaluaciones. Los mostrados corresponden a «Evaluación de entidades públicas 2025». Para evaluaciones anteriores u otras tipologías (entidades privadas, órganos de relevancia estatutaria), los valores de Contenido pueden ser solo 0 / 50 / 100 en lugar de los cinco niveles de la tabla anterior.
Autoevaluación opcional
La autoevaluación es completamente opcional. Si no se proporcionan valores, la obligación se actualiza igualmente con los enlaces, aclaraciones y opción de publicación. Puede enviar un array vacío [] o simplemente omitir el campo.
Autoevaluación vs. calificación del Comisionado
La autoevaluación es la puntuación que la entidad se asigna a sí misma. La calificación final la realiza el Comisionado de Transparencia durante la fase de revisión y puede diferir de la autoevaluación.
Estrategia recomendada
Consulte primero el árbol PA con GET /pa para obtener los id de los ítems de autoevaluación. Esos IDs (ej: 949162) son los que debe enviar en el PUT. Si envía un id que no existe en la declaración, la API devolverá un error 500.
Criterios y valores admitidos
Devuelve los criterios de autoevaluación con sus valores discretos admitidos para la evaluación concreta a la que pertenece la declaración.
Los valores discretos pueden variar entre evaluaciones. Este endpoint es la fuente de verdad: consulte siempre los valores aquí antes de enviarlos en el PUT.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
criterios = requests.get(f"{BASE_URL}/declaraciones/5001/pa/criterios", headers=headers).json()
for c in criterios:
valores = [f"{v['valor']} ({v['descripcion']})" for v in c['valores']]
print(f"[{c['idcriterio']}] {c['criterio']} (peso {c['peso']}%): {', '.join(valores)}")
var response = await client.GetAsync($"{baseUrl}/declaraciones/5001/pa/criterios");
var json = await response.Content.ReadAsStringAsync();
var criterios = JsonConvert.DeserializeObject<List<PaCriterioDto>>(json);
foreach (var c in criterios)
{
var vals = string.Join(", ", c.Valores.Select(v => $"{v.Valor} ({v.Descripcion})"));
Console.WriteLine($"[{c.Idcriterio}] {c.Criterio} (peso {c.Peso}%): {vals}");
}
Respuesta exitosa (200)
[
{
"idcriterio": 1,
"criterio": "Contenido",
"peso": 40,
"valores": [
{ "valor": 0, "descripcion": "Nada" },
{ "valor": 15, "descripcion": "Casi nada" },
{ "valor": 50, "descripcion": "Parcial" },
{ "valor": 85, "descripcion": "Casi todo" },
{ "valor": 100, "descripcion": "Total" }
]
},
{
"idcriterio": 3,
"criterio": "Última fecha de actualización publicada en la URL",
"peso": 30,
"valores": [
{ "valor": 0, "descripcion": "No hay fecha visible en el portal" },
{ "valor": 50, "descripcion": "Anterior al 1 de octubre de 2025" },
{ "valor": 100, "descripcion": "Último trimestre de 2025 o posterior" }
]
},
{
"idcriterio": 7,
"criterio": "Reutilización",
"peso": 20,
"valores": [
{ "valor": 0, "descripcion": "Formato no reutilizable (PDF escaneado, JPG, PNG...)" },
{ "valor": 25, "descripcion": "Formato poco reutilizable (PDF editable, HTML)" },
{ "valor": 50, "descripcion": "Formato reutilizable propietario (XLS, XLSX, DOC, PPT...)" },
{ "valor": 100, "descripcion": "Formato reutilizable abierto (ODS, ODT, XML, JSON, CSV, TXT...)" }
]
},
{
"idcriterio": 2,
"criterio": "Forma de publicación",
"peso": 5,
"valores": [
{ "valor": 0, "descripcion": "No se puede acceder desde el portal o requiere conocimientos específicos" },
{ "valor": 50, "descripcion": "Indirecta (el portal enlaza a otra web donde hay que seguir buscando)" },
{ "valor": 100, "descripcion": "Directa (publicado en el portal o enlace a la página concreta)" }
]
},
{
"idcriterio": 4,
"criterio": "Accesibilidad",
"peso": 5,
"valores": [
{ "valor": 0, "descripcion": "Más de 12 clics desde la portada del portal de transparencia" },
{ "valor": 10, "descripcion": "12 clics" },
{ "valor": 20, "descripcion": "11 clics" },
{ "valor": 30, "descripcion": "10 clics" },
{ "valor": 40, "descripcion": "9 clics" },
{ "valor": 50, "descripcion": "8 clics" },
{ "valor": 60, "descripcion": "7 clics" },
{ "valor": 70, "descripcion": "6 clics" },
{ "valor": 80, "descripcion": "5 clics" },
{ "valor": 90, "descripcion": "4 clics" },
{ "valor": 100, "descripcion": "Entre 1 y 3 clics" }
]
}
]
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
idcriterio |
int | ID del criterio (usar como referencia) |
criterio |
string | Nombre del criterio |
peso |
int | Peso en la nota de autoevaluación (%) |
valores |
array | Lista de valores discretos admitidos, ordenados de menor a mayor |
valores[].valor |
int | Valor numérico a enviar en el PUT |
valores[].descripcion |
string | Descripción legible del nivel |
Solo se admiten los valores de este endpoint
El procedimiento de actualización valida que cada valor enviado en el PUT sea exactamente uno de los valores de este listado para esa evaluación. Si se envía un valor no listado (aunque esté entre 0 y 100), la API devolverá un error 400 indicando los valores admitidos.
Flujo recomendado
- Llame a
GET /pa/criteriospara obtener los criterios y sus valores admitidos. - Para cada obligación, obtenga los
idde los ítems de autoevaluación desdeGET /pa(campoautoevaluacion[].id). - Envíe el PUT con los pares
{id, valor}usando solo los valores obtenidos en el paso 1.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Cuestionarios
Cuestionarios
Los cuestionarios son formularios estructurados que las entidades deben cumplimentar como parte de la evaluación de transparencia. Evalúan aspectos como las obligaciones de publicidad activa (ICIO), el soporte web (ICS), la transparencia voluntaria (ITV) y el derecho de acceso.
Estructura de un cuestionario
graph TD
A["Cuestionario"] --> B["Sección / Grupo 1"]
A --> C["Sección / Grupo 2"]
B --> D["Pregunta 1"]
B --> E["Pregunta 2"]
D --> F["Componente: checkbox"]
D --> G["Componente: texto (URL)"]
E --> H["Componente: selector"]
| Nivel | Descripción |
|---|---|
| Cuestionario | Formulario completo (ej: "Cuestionario ICS") |
| Sección / Grupo | Agrupación temática de preguntas |
| Pregunta | Pregunta individual que la entidad responde |
| Componente | Campo de respuesta tipado (checkbox, texto, número, fecha, selector, etc.) |
Tipos de cuestionarios
Los cuestionarios más habituales en una evaluación son:
| Tipo | Sigla | Descripción |
|---|---|---|
| Obligaciones de Publicidad Activa | ICIO | Evalúa el cumplimiento de las obligaciones de publicidad activa |
| Soporte Web | ICS | Evalúa la calidad del soporte web y accesibilidad del portal de transparencia |
| Transparencia Voluntaria | ITV | Evalúa la publicación proactiva de información adicional no obligatoria |
| Derecho de Acceso | — | Evalúa los mecanismos y datos de solicitudes de acceso a la información pública |
Tipos de componentes de respuesta
Cada pregunta contiene uno o más componentes con un tipo específico:
| Tipo | Valor esperado | Ejemplo |
|---|---|---|
checkbox |
true / false |
Marcar si cumple |
texto |
String (URL, texto corto) | "https://portal.es/datos" |
entero |
Número entero | 42 |
decimal |
Número decimal | 75.5 |
fecha |
String ISO (YYYY-MM-DD) | "2024-06-15" |
memo |
String largo (multilínea) | "Texto extenso..." |
selector |
Entero (opción seleccionada) | 1 |
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/declaraciones/{id}/cuestionarios |
Listar todos los cuestionarios |
GET |
/declaraciones/{id}/cuestionarios/{idcuest} |
Obtener un cuestionario |
PUT |
/declaraciones/{id}/cuestionarios/{idcuest} |
Enviar respuestas |
Flujo de trabajo
sequenceDiagram
participant S as Sistema externo
participant API as API T-Canaria
S->>API: GET /declaraciones/{id}/cuestionarios
API-->>S: Lista de cuestionarios con estructura
Note over S: Identificar cuestionario a rellenar
S->>API: GET /declaraciones/{id}/cuestionarios/{idcuest}
API-->>S: Estructura completa con preguntas
Note over S: Modificar valorcampo de cada componente
S->>API: PUT /declaraciones/{id}/cuestionarios/{idcuest}
API-->>S: N respuestas actualizadas
Formato de respuesta
Las respuestas se envían como un array JSONB por pregunta. La API acepta tanto el formato completo (todos los campos del componente) como el formato compacto {id, valor, validado}. Ver enviar respuestas para detalles.
Recálculo automático de notas
Al enviar respuestas, las notas parciales (ICS, ITV) se recalculan automáticamente. Puede consultar las nuevas notas con GET /declaraciones/{id}/analisis.
Listar cuestionarios
Todos los cuestionarios
Devuelve todos los cuestionarios de la declaración con sus secciones, grupos, preguntas y respuestas actuales.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/declaraciones/5001/cuestionarios",
headers=headers
)
cuestionarios = response.json()
for cuest in cuestionarios:
print(f"Cuestionario {cuest['idcuestionario']}: {cuest['titulo']}")
for seccion in cuest.get("secciones", []):
print(f" Sección: {seccion['seccion']}")
for grupo in seccion.get("grupos", []):
print(f" Grupo {grupo['idgrupo']} ({grupo['grupo']}): {len(grupo.get('preguntas', []))} preguntas")
Respuesta exitosa (200)
[
{
"idcuestionario": 20,
"titulo": "Cuestionario de Soporte Web (ICS)",
"tipo": 1,
"descripcion": "Cuestionario relativo al soporte web utilizado para presentar la información a la ciudadanía",
"porcentajecumplimentado": 45.5,
"aclaracionesentidad": "",
"secciones": [
{
"seccion": "Lugar de publicación",
"descripcion": "",
"porcentajecumplimentado": 50.0,
"grupos": [
{
"idgrupo": 10,
"grupo": "Accesibilidad web",
"preguntas": [
{
"id": 150,
"idpregunta": 301,
"codigo": "ICS-01",
"titulo": "Dispone de formulario de quejas y sugerencias",
"descripcion": "",
"respuesta": [
{
"id": 1,
"tipo": "checkbox",
"nombre": "Sí/No",
"valores": null,
"validado": false,
"valorcampo": false
}
],
"totalrespuestas": 1,
"respuestascontestadas": 0,
"alegacionhabilitada": false,
"alegacionnota": 0,
"alegacionvalorada": false,
"comentariorevision": ""
},
{
"id": 151,
"idpregunta": 302,
"codigo": "ICS-02",
"titulo": "URL del formulario de quejas",
"descripcion": "",
"respuesta": [
{
"id": 1,
"tipo": "texto",
"nombre": "URL",
"valores": null,
"validado": false,
"valorcampo": ""
}
],
"totalrespuestas": 1,
"respuestascontestadas": 0,
"alegacionhabilitada": false,
"alegacionnota": 0,
"alegacionvalorada": false,
"comentariorevision": ""
}
]
}
]
}
]
}
]
Campos del cuestionario
| Campo | Tipo | Descripción |
|---|---|---|
idcuestionario |
int | ID del cuestionario (usar en GET /cuestionarios/{id} y PUT) |
titulo |
string | Nombre descriptivo del cuestionario |
tipo |
int | Tipo de cuestionario |
descripcion |
string | Descripción del cuestionario |
porcentajecumplimentado |
decimal | Porcentaje de preguntas respondidas |
aclaracionesentidad |
string | Texto de aclaraciones de la entidad |
secciones |
array | Secciones del cuestionario |
Campos de la sección
| Campo | Tipo | Descripción |
|---|---|---|
seccion |
string | Nombre de la sección |
descripcion |
string | Descripción de la sección |
porcentajecumplimentado |
decimal | Porcentaje de preguntas respondidas en esta sección |
grupos |
array | Grupos de preguntas dentro de la sección |
Campos del grupo
| Campo | Tipo | Descripción |
|---|---|---|
idgrupo |
int | ID del grupo |
grupo |
string | Nombre del grupo |
preguntas |
array | Preguntas del grupo |
Campos de la pregunta
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID de la pregunta en el catálogo |
idpregunta |
int | ID para usar en el PUT (ID del registro de remisión) |
codigo |
string | Código visual de la pregunta (puede estar vacío) |
titulo |
string | Texto de la pregunta |
descripcion |
string | Descripción adicional de la pregunta |
respuesta |
array (JSONB) | Componentes de respuesta con valores actuales (ver tipos de componentes) |
totalrespuestas |
int | Número total de componentes de respuesta |
respuestascontestadas |
int | Número de componentes con valor no vacío |
alegacionhabilitada |
bool | Si la entidad puede presentar alegación en esta pregunta |
alegacionnota |
decimal | Nota de alegación asignada por el revisor |
alegacionvalorada |
bool | Si la alegación ha sido valorada |
comentariorevision |
string | Comentario del revisor sobre la respuesta |
ID correcto para el PUT: idpregunta, no id
Al enviar respuestas con PUT, el campo id del body debe contener el valor de idpregunta del GET (ID del registro de remisión). El campo id del GET (ID del catálogo) NO se usa en el PUT.
Cuestionario individual
Devuelve un cuestionario específico con todas sus preguntas y las respuestas actuales.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
idcuestionario |
int | ID del cuestionario (campo idcuestionario del listado) |
Ejemplo de petición
La respuesta tiene la misma estructura que un elemento del array del listado general.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración o cuestionario no existe o no pertenece a la entidad |
Enviar respuestas
Actualiza las respuestas de un cuestionario completo. Las notas parciales (ICS, ITV) se recalculan automáticamente tras cada envío.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
idcuestionario |
int | ID del cuestionario (campo idcuestionario del GET) |
Flujo completo: GET estructura, modificar, PUT
sequenceDiagram
participant S as Sistema externo
participant API as API T-Canaria
S->>API: GET /declaraciones/5001/cuestionarios/20
API-->>S: Estructura con secciones, grupos y preguntas
Note over S: Recorrer secciones → grupos → preguntas
Note over S: Para cada pregunta, modificar valorcampo
Note over S: de cada componente en el array respuesta
S->>API: PUT /declaraciones/5001/cuestionarios/20
API-->>S: "N respuestas actualizadas"
Paso 1: Obtener la estructura
curl -s "https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/cuestionarios/20" \
-H "Authorization: Bearer $TOKEN" | jq '.secciones[0].grupos[0].preguntas[0]'
Respuesta (una pregunta de ejemplo):
{
"id": 150,
"idpregunta": 301,
"codigo": "ICS-01",
"titulo": "Dispone de formulario de quejas y sugerencias",
"descripcion": "",
"respuesta": [
{
"id": 1,
"tipo": "checkbox",
"nombre": "Sí/No",
"valores": null,
"validado": false,
"valorcampo": false
}
],
"totalrespuestas": 1,
"respuestascontestadas": 0,
"alegacionhabilitada": false,
"alegacionnota": 0,
"alegacionvalorada": false,
"comentariorevision": ""
}
Paso 2: Modificar valorcampo
Para cada componente del array respuesta, modificar el campo valorcampo según su tipo:
| Tipo | Antes | Después |
|---|---|---|
checkbox |
false |
true |
texto |
"" |
"https://portal.es/quejas" |
entero |
0 |
42 |
decimal |
0 |
75.5 |
fecha |
"" |
"2024-06-15" |
memo |
"" |
"Texto explicativo largo..." |
selector |
0 |
1 |
Paso 3: Enviar con PUT
En el body del PUT, el campo id de cada pregunta debe contener el valor de idpregunta del GET (no el id):
Formato compacto
La API transforma automáticamente el formato compacto {id, valor, validado} al formato interno. No es necesario enviar todos los campos del componente. Solo id y valorcampo (o valor) son imprescindibles.
Mapeo GET → PUT
| Campo en GET | Campo en PUT | Descripción |
|---|---|---|
secciones[].grupos[].idgrupo |
grupos[].grupoId |
ID del grupo |
secciones[].grupos[].preguntas[].idpregunta |
preguntas[].id |
ID del registro de remisión — este es el que valida y guarda la API |
secciones[].grupos[].preguntas[].id |
preguntas[].preguntaId |
ID del catálogo (referencia) |
respuesta[].valorcampo |
respuesta[].valorcampo o respuesta[].valor |
Valor de la respuesta |
Ejemplo completo
curl -s -X PUT \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/cuestionarios/20" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"grupos": [
{
"grupoId": 10,
"preguntas": [
{
"id": 301,
"preguntaId": 150,
"respuesta": [
{"id": 1, "valor": true, "validado": false}
]
}
]
}
],
"aclaraciones": ""
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 1. Obtener estructura del cuestionario
cuestionario = requests.get(
f"{BASE_URL}/declaraciones/5001/cuestionarios/20",
headers=headers
).json()
# 2. Construir el body del PUT recorriendo la estructura real
grupos = []
for seccion in cuestionario.get("secciones", []):
for grupo in seccion.get("grupos", []):
preguntas_put = []
for pregunta in grupo.get("preguntas", []):
respuesta_modificada = []
for comp in pregunta.get("respuesta", []):
comp_put = dict(comp)
if comp["tipo"] == "checkbox":
comp_put["valorcampo"] = True
elif comp["tipo"] == "texto":
comp_put["valorcampo"] = "https://portal.entidad.es/datos"
respuesta_modificada.append(comp_put)
preguntas_put.append({
"id": pregunta["idpregunta"], # idpregunta del GET → id en el PUT
"preguntaId": pregunta["id"], # id del GET → preguntaId en el PUT
"respuesta": respuesta_modificada
})
grupos.append({
"grupoId": grupo["idgrupo"], # idgrupo del GET → grupoId en el PUT
"preguntas": preguntas_put
})
# 3. Enviar respuestas
response = requests.put(
f"{BASE_URL}/declaraciones/5001/cuestionarios/20",
headers=headers,
json={"grupos": grupos, "aclaraciones": ""}
)
print(response.json())
// 1. Obtener estructura
var getResponse = await client.GetAsync($"{baseUrl}/declaraciones/5001/cuestionarios/20");
var cuestionario = JsonConvert.DeserializeObject<CuestionarioDto>(
await getResponse.Content.ReadAsStringAsync());
// 2. Construir body del PUT
var grupos = new List<object>();
foreach (var seccion in cuestionario.Secciones)
{
foreach (var grupo in seccion.Grupos)
{
var preguntas = grupo.Preguntas.Select(p => new
{
id = p.Idpregunta, // idpregunta del GET → id en el PUT
preguntaId = p.Id, // id del GET → preguntaId en el PUT
respuesta = p.Respuesta.Select(c => new
{
id = c.Id,
valor = c.Tipo == "checkbox" ? (object)true : "https://portal.es",
validado = false
}).ToArray()
}).ToArray();
grupos.Add(new { grupoId = grupo.Idgrupo, preguntas });
}
}
// 3. Enviar
var payload = new { grupos, aclaraciones = "" };
var content = new StringContent(
JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
var response = await client.PutAsync(
$"{baseUrl}/declaraciones/5001/cuestionarios/20", content);
Respuesta exitosa (200)
Campos del body
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
grupos |
array | Sí | Grupos del cuestionario con sus preguntas |
grupos[].grupoId |
int | Sí | idgrupo del GET |
grupos[].preguntas |
array | Sí | Preguntas con respuestas |
grupos[].preguntas[].id |
int | Sí | idpregunta del GET (ID del registro de remisión) |
grupos[].preguntas[].preguntaId |
int | No | id del GET (ID del catálogo, solo referencia) |
grupos[].preguntas[].respuesta |
array | Sí | Array de componentes con valores |
aclaraciones |
string | No | Texto de aclaraciones (máximo 5.000 caracteres) |
Validaciones
| Validación | Límite | Error |
|---|---|---|
id de pregunta existe en el cuestionario |
Verificado contra estructura real | 400 |
Componente tiene campo id |
Obligatorio | 400 |
Tipo checkbox |
Valor debe ser boolean | 400 |
Tipo entero |
Valor debe ser integer | 400 |
Tipo decimal |
Valor debe ser número | 400 |
Tipo texto/memo |
Valor string, max 10.000 chars | 400 |
Tipo fecha |
Valor ISO parseable | 400 |
Tipo selector |
Valor debe ser integer o string | 400 |
| Grupos | Max 100 por petición | 400 |
| Preguntas totales | Max 500 por petición | 400 |
| Respuesta | Al menos 1 componente | 400 |
| Aclaraciones | Max 5.000 caracteres | 400 |
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida (ver tabla) |
401 |
Token ausente, expirado o inválido |
404 |
La declaración o cuestionario no existe o no pertenece a la entidad |
Estado de la declaración
Solo funciona cuando la declaración está en estado NoPresentada (1) o AlegacionesAbiertas (4).
Tipos de componentes
Cada pregunta de un cuestionario contiene uno o más componentes de respuesta dentro del array respuesta. Cada componente tiene un tipo que determina el formato del valor esperado y los metadatos de configuración disponibles.
Estructura de un componente
Todos los componentes comparten estos campos:
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID único del componente dentro de la pregunta |
tipo |
string | Tipo de componente (ver tabla de tipos) |
nombre |
string | Etiqueta visible para el usuario |
descripcion |
string | Descripción o texto de ayuda del componente |
valores |
array | Metadatos de configuración (ver abajo). Nunca es null |
validado |
bool | Si el componente ya ha sido respondido y validado |
valorcampo |
any | Valor actual introducido por la entidad |
valorinicial |
any | Valor guardado en la última remisión aceptada (referencia histórica) |
El array valores
El campo valores es un array de objetos {nombre, valor} que contiene la configuración del componente. Todos los tipos tienen este array; nunca llega como null.
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva", "valor": 0 }
]
Las entradas del array valores presentes según el tipo:
Nombre en valores |
Tipos que lo tienen | Descripción |
|---|---|---|
campoBoolVinculado |
Todos | ID de pregunta checkbox que activa/desactiva este campo. 0 = siempre activo |
SelectorVinculado |
Todos | ID de pregunta selector que condiciona este campo. 0 = sin vinculación |
OpcionSelectorActiva |
Todos | Valor del selector que activa este campo. 0 = sin vinculación |
obligatorio |
texto, memo, entero, decimal, fecha, selector | Si el campo es obligatorio |
largo |
texto | Longitud máxima en caracteres (0 = sin límite) |
esUrl |
texto | Si el valor debe ser una URL válida |
esEmail |
texto | Si el valor debe ser un email válido |
esTelefono |
texto | Si el valor debe ser un teléfono válido |
min |
entero, decimal | Valor mínimo admitido (0 = sin límite) |
max |
entero, decimal | Valor máximo admitido (0 = sin límite) |
fechahoy |
fecha | Si se inicializa con la fecha actual |
valores |
selector | Opciones del desplegable (JSON serializado, ver tipo selector) |
Campos condicionales
Cuando campoBoolVinculado > 0, el campo se habilita solo si la pregunta checkbox con ese ID tiene valor true. Cuando SelectorVinculado > 0 y OpcionSelectorActiva > 0, el campo se habilita solo si el selector indicado tiene ese valor seleccionado. Un campo desactivado por estas vinculaciones debe ignorarse (no es obligatorio responderlo).
Tabla de tipos
| Tipo | Valor esperado | Formato valorcampo |
Ejemplo |
|---|---|---|---|
checkbox |
Boolean | true / false |
true |
texto |
String | Texto corto o URL | "https://portal.es/datos" |
entero |
Integer | Número entero | 42 |
decimal |
Number | Número con decimales | 75.5 |
fecha |
String ISO | Formato YYYY-MM-DD |
"2024-06-15" |
memo |
String | Texto largo multilínea | "Texto extenso..." |
selector |
Integer | Valor numérico de la opción elegida | 2 |
Detalle de cada tipo
checkbox
Pregunta de Sí/No. El valor es un booleano. La entrada valores contiene solo los campos de vinculación.
{
"id": 1,
"tipo": "checkbox",
"nombre": "Sí/No",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 }
],
"validado": false,
"valorcampo": false,
"valorinicial": false
}
Valores válidos
Solo se aceptan true o false. Los valores 0, 1, "si", "no" no son válidos y generan error 400.
texto
Campo de texto corto. Puede estar configurado para validar URL, email o teléfono.
{
"id": 1,
"tipo": "texto",
"nombre": "URL del portal",
"descripcion": "Introduzca la URL completa incluyendo https://",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": true },
{ "nombre": "largo", "valor": 255 },
{ "nombre": "esUrl", "valor": true },
{ "nombre": "esEmail", "valor": false },
{ "nombre": "esTelefono", "valor": false }
],
"validado": false,
"valorcampo": "https://portal.entidad.es/transparencia",
"valorinicial": ""
}
| Validación | Límite |
|---|---|
| Longitud máxima | Según largo en valores (si largo = 0, sin límite; máximo absoluto 10.000) |
| Tipo | Debe ser string |
| Formato URL | Si esUrl = true, debe ser una URL válida |
entero
Número entero sin decimales.
{
"id": 1,
"tipo": "entero",
"nombre": "Número de solicitudes",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": false },
{ "nombre": "min", "valor": 0 },
{ "nombre": "max", "valor": 0 }
],
"validado": false,
"valorcampo": 42,
"valorinicial": 0
}
| Validación | Límite |
|---|---|
| Tipo | Debe ser integer |
No enviar como string
El valor debe ser un número, no un string. 42 es correcto, "42" no.
decimal
Número con decimales. Se usa para porcentajes, importes, etc.
{
"id": 1,
"tipo": "decimal",
"nombre": "Porcentaje de cumplimiento",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": false },
{ "nombre": "min", "valor": 0 },
{ "nombre": "max", "valor": 0 }
],
"validado": false,
"valorcampo": 75.5,
"valorinicial": 0
}
| Validación | Límite |
|---|---|
| Tipo | Debe ser numérico (entero o decimal) |
Separador decimal
Use punto (.) como separador decimal, no coma. 75.5 es correcto, 75,5 no.
fecha
Campo de fecha en formato ISO 8601.
{
"id": 1,
"tipo": "fecha",
"nombre": "Fecha de publicación",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": false },
{ "nombre": "fechahoy", "valor": false }
],
"validado": false,
"valorcampo": "2024-06-15T00:00:00.000Z",
"valorinicial": null
}
| Validación | Límite |
|---|---|
| Formato | String ISO parseable (YYYY-MM-DD o YYYY-MM-DDTHH:MM:SS.mmmZ) |
memo
Campo de texto largo. Para descripciones, justificaciones, textos extensos.
{
"id": 1,
"tipo": "memo",
"nombre": "Justificación",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": false }
],
"validado": false,
"valorcampo": "La entidad ha implementado un sistema de gestión de calidad...",
"valorinicial": ""
}
| Validación | Límite |
|---|---|
| Longitud máxima | 10.000 caracteres |
| Tipo | Debe ser string |
selector
Selección de una opción entre varias predefinidas. El valor es el número correspondiente a la opción elegida.
Las opciones se obtienen de la entrada {nombre: "valores"} del array valores. Su contenido es un JSON serializado que contiene un array de {texto, valor}.
{
"id": 1,
"tipo": "selector",
"nombre": "Frecuencia de actualización",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": false },
{
"nombre": "valores",
"valor": "[{\"texto\":\"No aplica\",\"valor\":0},{\"texto\":\"Anual\",\"valor\":1},{\"texto\":\"Semestral\",\"valor\":2},{\"texto\":\"Trimestral\",\"valor\":3},{\"texto\":\"Mensual\",\"valor\":4}]"
}
],
"validado": false,
"valorcampo": 2,
"valorinicial": 0
}
| Validación | Límite |
|---|---|
| Tipo | Debe ser integer |
| Valor | Debe corresponder a un valor de la lista de opciones |
Cómo leer las opciones
Las opciones del selector se obtienen localizando valores.find(x => x.nombre === "valores")?.valor y parseando el string resultante como JSON. El array resultante tiene la estructura [{texto: string, valor: number}]. No confundir el valor de cada opción con la clave nombre y valor del propio array de metadatos.
Estructura de las opciones: {texto, valor} — NO {id, nombre}
La estructura de cada opción es {"texto": "...", "valor": 0}. El campo numérico se llama valor (no id) y el texto visible se llama texto (no nombre). Envíe el campo valor de la opción elegida como valorcampo en el PUT.
Ejemplo de una pregunta con múltiples componentes
Algunas preguntas combinan varios componentes. En este ejemplo, el campo texto URL solo se habilita si el checkbox está marcado (campoBoolVinculado apunta al ID de la pregunta que contiene el checkbox):
{
"id": 150,
"idpregunta": 301,
"codigo": "ICS-01",
"titulo": "Publicación de datos de contratación pública",
"descripcion": "Indique si la entidad publica datos de contratación y proporcione la URL",
"respuesta": [
{
"id": 1,
"tipo": "checkbox",
"nombre": "Publica datos",
"descripcion": "",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 0 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 }
],
"validado": false,
"valorcampo": true,
"valorinicial": false
},
{
"id": 2,
"tipo": "texto",
"nombre": "URL de los datos",
"descripcion": "URL donde se publican los datos de contratación",
"valores": [
{ "nombre": "campoBoolVinculado", "valor": 150 },
{ "nombre": "SelectorVinculado", "valor": 0 },
{ "nombre": "OpcionSelectorActiva","valor": 0 },
{ "nombre": "obligatorio", "valor": true },
{ "nombre": "largo", "valor": 500 },
{ "nombre": "esUrl", "valor": true },
{ "nombre": "esEmail", "valor": false },
{ "nombre": "esTelefono", "valor": false }
],
"validado": false,
"valorcampo": "https://portal.entidad.es/contratacion",
"valorinicial": ""
}
],
"totalrespuestas": 2,
"respuestascontestadas": 2,
"alegacionhabilitada": false,
"alegacionnota": 0,
"alegacionvalorada": false,
"comentariorevision": ""
}
En este ejemplo, el campo URL de los datos (id: 2) tiene campoBoolVinculado: 150, que apunta a la pregunta con idpregunta: 150 (la pregunta contenedora del checkbox con id: 1). Si el checkbox vale false, el campo texto queda desactivado y no es necesario responderlo.
Enviar todos los componentes
Al enviar respuestas con PUT, incluya todos los componentes de cada pregunta, incluso los que no haya modificado. La API espera el array completo de componentes.
Campo valorinicial
El campo valorinicial refleja el valor que tenía el componente en la última remisión. Úselo solo como referencia informativa. Al enviar el PUT, incluya siempre valorcampo con el valor actual deseado.
Derecho de acceso
Derecho de acceso
El derecho de acceso a la información pública es un derecho reconocido por la Ley 19/2013, de 9 de diciembre, de transparencia. Las entidades deben reportar datos estadísticos sobre las solicitudes de acceso que reciben durante cada ejercicio: cantidad de solicitudes recibidas, cómo se resolvieron (estimadas/desestimadas), por qué vía llegaron, etc.
Qué se reporta
| Categoría | Descripción |
|---|---|
| Datos generales | Si existe formulario de solicitud, URL, plazo medio de resolución y aclaraciones |
| Tabla de solicitudes | Cuadrante bidireccional: filas = conceptos de solicitud, columnas = tipo de resolución |
Estructura de la tabla
La tabla de solicitudes es un cuadrante con dos ejes:
- Columnas (categorías): tipos de resolución o estado de las solicitudes (ej: "Presentadas", "Admitidas", "Resueltas estimadas").
- Filas (tipos/conceptos): los distintos conceptos por los que se puede solicitar información (ej: "Acceso integral", "Expediente personal", etc.).
graph LR
A["GET /tabla"] --> B["Ejercicio 2024\nidperiodo: 5"]
B --> C["Categorías (columnas)\nPresentadas / Admitidas / Resueltas..."]
B --> D["Filas (tipos/conceptos)\nTotal · Subtotales · Conceptos editables"]
D --> E["Celdas con id, idcategoria, valor\n↑ id = clave para el PUT"]
Hay tres tipos de filas (campo idtiporejilla):
| Valor | Tipo de fila | Editable |
|---|---|---|
0 |
Concepto individual (solicitud concreta) | Sí — usar su id en el PUT |
1 |
Subtotal de grupo | No (calculado) |
2 |
Total general del grupo | No (calculado) |
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/declaraciones/{id}/derecho-acceso |
Datos generales (formulario, URL, plazo, aclaraciones) |
GET |
/declaraciones/{id}/derecho-acceso/tabla |
Tabla completa de solicitudes con IDs para el PUT |
PUT |
/declaraciones/{id}/derecho-acceso |
Enviar valores estadísticos de la tabla |
PUT |
/declaraciones/{id}/derecho-acceso/general |
Actualizar datos generales |
Flujo de trabajo recomendado
sequenceDiagram
participant S as Sistema externo
participant API as API T-Canaria
S->>API: GET /declaraciones/{id}/derecho-acceso/tabla
API-->>S: Estructura completa con ejercicios, categorías,<br/>conceptos y valores actuales (con IDs)
Note over S: Para cada celda editable (idtiporejilla=0),<br/>tomar el 'id' y el valor nuevo
S->>API: PUT /declaraciones/{id}/derecho-acceso/general
API-->>S: Datos generales actualizados
S->>API: PUT /declaraciones/{id}/derecho-acceso
API-->>S: "N valores actualizados"
Ejercicios
Cada declaración puede tener uno o varios ejercicios (períodos anuales). La tabla devuelve un elemento por ejercicio. Cada ejercicio tiene sus propios IDs de celdas.
Solo ítems editables
Solo las celdas con idtiporejilla=0 son editables. Los valores con id=99999 son totales de fila calculados por el sistema — no enviarlos en el PUT.
Consultar datos generales
Devuelve los datos generales de derecho de acceso de la entidad vinculados a la declaración: existencia de formulario de solicitud, URL, plazo medio de resolución y texto de aclaraciones.
Para obtener la tabla de solicitudes
Este endpoint devuelve solo los datos generales (formulario, URL, plazo, aclaraciones). Para obtener la estructura completa del cuadrante de solicitudes con todos los IDs necesarios para el PUT, use GET /derecho-acceso/tabla.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/declaraciones/5001/derecho-acceso",
headers=headers
)
general = response.json()
print(f"Formulario: {'Sí' if general.get('existeFormulario') else 'No'}")
if general.get("urlFormulario"):
print(f"URL: {general['urlFormulario']}")
print(f"Plazo medio: {general.get('plazoMedioDias', 0)} días")
var response = await client.GetAsync($"{baseUrl}/declaraciones/5001/derecho-acceso");
var json = await response.Content.ReadAsStringAsync();
var general = JsonConvert.DeserializeObject<DaGeneralDto>(json);
Console.WriteLine($"Formulario: {(general.ExisteFormulario ? "Sí" : "No")}");
Console.WriteLine($"Plazo medio: {general.PlazoMedioDias} días");
Respuesta exitosa (200)
{
"existeFormulario": true,
"urlFormulario": "https://sede.entidad.es/derecho-acceso",
"plazoMedioDias": 15,
"aclaraciones": "Formulario disponible en sede electrónica desde 2023"
}
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
existeFormulario |
bool | Si la entidad tiene formulario de solicitud de acceso disponible |
urlFormulario |
string | URL del formulario de solicitud (vacío si existeFormulario es false) |
plazoMedioDias |
int | Plazo medio de resolución de solicitudes en días (0 = no informado) |
aclaraciones |
string | Texto libre de aclaraciones sobre derecho de acceso |
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Tabla de solicitudes
Devuelve la tabla completa de solicitudes de derecho de acceso, organizada por ejercicios (períodos). Cada ejercicio contiene las categorías (columnas) y los conceptos (filas) con sus valores actuales. Los IDs de las celdas son los que se usan en el PUT /derecho-acceso para enviar datos.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/declaraciones/5001/derecho-acceso/tabla",
headers=headers
)
ejercicios = response.json()
for ejercicio in ejercicios:
print(f"\nEjercicio {ejercicio['titulo']} (id: {ejercicio['idperiodo']})")
print(f"Categorías: {[c['nombre'] for c in ejercicio['categorias']]}")
for tipo in ejercicio['tipos']:
if tipo['idtiporejilla'] == 0: # Solo filas editables
print(f" Concepto '{tipo['nombre']}':")
for valor in tipo['valores']:
if valor['idcategoria'] != 99999: # Excluir totales
print(f" id={valor['id']}, idcategoria={valor['idcategoria']}, valor={valor['valor']}")
var response = await client.GetAsync($"{baseUrl}/declaraciones/5001/derecho-acceso/tabla");
var json = await response.Content.ReadAsStringAsync();
var ejercicios = JsonConvert.DeserializeObject<List<DaTablaDto>>(json);
foreach (var ejercicio in ejercicios)
{
Console.WriteLine($"Ejercicio {ejercicio.Titulo} (id: {ejercicio.Idperiodo})");
var editables = ejercicio.Tipos.Where(t => t.Idtiporejilla == 0);
foreach (var tipo in editables)
{
Console.WriteLine($" Concepto: {tipo.Nombre}");
foreach (var valor in tipo.Valores.Where(v => v.Idcategoria != 99999))
Console.WriteLine($" id={valor.Id}, valor={valor.Valor}");
}
}
Respuesta exitosa (200)
Array con un elemento por ejercicio vinculado a la evaluación de la declaración.
[
{
"idperiodo": 5,
"titulo": "Ejercicio 2024",
"inicio": "2024-01-01",
"fin": "2024-12-31",
"categorias": [
{ "idcategoria": 1, "nombre": "Presentadas" },
{ "idcategoria": 2, "nombre": "Admitidas" },
{ "idcategoria": 3, "nombre": "Resueltas estimadas" },
{ "idcategoria": 4, "nombre": "Resueltas desestimadas" },
{ "idcategoria": 99999, "nombre": "Total" }
],
"tipos": [
{
"idtipo": 0,
"nombre": "Total solicitudes a resolver",
"desglosada": false,
"idtiporejilla": 2,
"valores": [
{ "id": 99999, "idcategoria": 1, "valor": 80 },
{ "id": 99999, "idcategoria": 2, "valor": 72 },
{ "id": 99999, "idcategoria": 3, "valor": 50 },
{ "id": 99999, "idcategoria": 4, "valor": 22 },
{ "id": 99999, "idcategoria": 99999, "valor": 224 }
]
},
{
"idtipo": 1,
"nombre": "Acceso a documentos",
"desglosada": false,
"idtiporejilla": 1,
"valores": [
{ "id": 99999, "idcategoria": 1, "valor": 45 },
{ "id": 99999, "idcategoria": 2, "valor": 40 },
{ "id": 99999, "idcategoria": 3, "valor": 28 },
{ "id": 99999, "idcategoria": 4, "valor": 12 },
{ "id": 99999, "idcategoria": 99999, "valor": 125 }
]
},
{
"idtipo": 10,
"nombre": "Acceso integral",
"desglosada": false,
"idtiporejilla": 0,
"valores": [
{ "id": 1001, "idcategoria": 1, "valor": 30 },
{ "id": 1002, "idcategoria": 2, "valor": 27 },
{ "id": 1003, "idcategoria": 3, "valor": 19 },
{ "id": 1004, "idcategoria": 4, "valor": 8 },
{ "id": 99999, "idcategoria": 99999, "valor": 84 }
]
},
{
"idtipo": 11,
"nombre": "Expediente personal",
"desglosada": false,
"idtiporejilla": 0,
"valores": [
{ "id": 1005, "idcategoria": 1, "valor": 15 },
{ "id": 1006, "idcategoria": 2, "valor": 13 },
{ "id": 1007, "idcategoria": 3, "valor": 9 },
{ "id": 1008, "idcategoria": 4, "valor": 4 },
{ "id": 99999, "idcategoria": 99999, "valor": 41 }
]
}
]
}
]
Estructura del ejercicio
| Campo | Tipo | Descripción |
|---|---|---|
idperiodo |
int | ID del ejercicio/período |
titulo |
string | Título descriptivo del ejercicio (ej: "Ejercicio 2024") |
inicio |
string | Fecha de inicio del ejercicio (ISO 8601, formato YYYY-MM-DD) |
fin |
string | Fecha de fin del ejercicio (ISO 8601, formato YYYY-MM-DD) |
categorias |
array | Columnas del cuadrante (tipos de resolución) |
tipos |
array | Filas del cuadrante (conceptos de solicitud y subtotales) |
Campos de las categorías
| Campo | Tipo | Descripción |
|---|---|---|
idcategoria |
int | ID de la categoría. El valor 99999 corresponde a la columna Total |
nombre |
string | Nombre de la categoría (ej: "Presentadas", "Resueltas estimadas") |
Campos de los tipos (filas)
| Campo | Tipo | Descripción |
|---|---|---|
idtipo |
int | ID del tipo o concepto |
nombre |
string | Nombre del concepto o subtotal |
desglosada |
bool | Si los conceptos de este grupo tienen desglose adicional |
idtiporejilla |
int | Tipo de fila: 0 = editable, 1 = subtotal de grupo, 2 = total general |
valores |
array | Celdas de esta fila, una por categoría más el total |
Campos de los valores (celdas)
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del registro — usar este valor como clave en el PUT. El valor 99999 indica una celda de total (no editar) |
idcategoria |
int | ID de la categoría (columna) a la que pertenece esta celda. El valor 99999 es el total de fila |
valor |
int | Número de solicitudes en esta celda |
Tipos de fila (idtiporejilla)
| Valor | Significado | Editable | Qué hacer |
|---|---|---|---|
0 |
Concepto individual | Sí | Tomar el id de cada celda y enviarlo en el PUT |
1 |
Subtotal de grupo | No | Ignorar — calculado por el sistema |
2 |
Total general | No | Ignorar — calculado por el sistema |
Solo editar celdas con idtiporejilla=0 e id≠99999
Las celdas con id=99999 son totales calculados. Las filas con idtiporejilla=1 o idtiporejilla=2 son subtotales. Enviar estos valores en el PUT no tiene efecto (se ignoran silenciosamente).
Flujo: GET tabla → PUT valores
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 1. Obtener la tabla con todos los IDs
ejercicios = requests.get(
f"{BASE_URL}/declaraciones/5001/derecho-acceso/tabla",
headers=headers
).json()
# 2. Recopilar IDs editables y asignar nuevos valores
# (aquí el sistema externo cruza con sus propios datos)
valores = {}
for ejercicio in ejercicios:
for tipo in ejercicio["tipos"]:
if tipo["idtiporejilla"] == 0: # Solo filas editables
for valor in tipo["valores"]:
if valor["idcategoria"] != 99999: # Excluir columna Total
# ← aquí cruzar con el valor real de vuestro sistema
valores[valor["id"]] = valor["valor"] # o el nuevo valor
# 3. Enviar los valores actualizados
response = requests.put(
f"{BASE_URL}/declaraciones/5001/derecho-acceso",
headers=headers,
json={"valores": valores}
)
print(response.json())
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe, no pertenece a la entidad, o no tiene remisión activa |
Enviar datos estadísticos
Actualiza los valores numéricos de las celdas de la tabla de solicitudes de derecho de acceso. El body es un diccionario donde cada clave es el id de una celda obtenido del GET /derecho-acceso/tabla y el valor es el número de solicitudes.
Obtener los IDs primero
Antes de enviar datos, consulte GET /derecho-acceso/tabla para obtener la estructura completa con los IDs de cada celda editable (idtiporejilla=0, id≠99999).
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Body de la petición
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
valores |
object | Sí | Diccionario id_celda → valor_numerico. Las claves son strings que representan el id de cada celda del GET |
Ejemplo completo: GET tabla → PUT valores
# 1. Obtener la tabla con los IDs
curl -s "https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/derecho-acceso/tabla" \
-H "Authorization: Bearer $TOKEN" | jq '.[]'
# 2. Enviar valores (usando los IDs obtenidos del GET)
curl -s -X PUT \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/derecho-acceso" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"valores": {
"1001": 30,
"1002": 27,
"1003": 19,
"1004": 8
}
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 1. Obtener la tabla con todos los IDs y valores actuales
ejercicios = requests.get(
f"{BASE_URL}/declaraciones/5001/derecho-acceso/tabla",
headers=headers
).json()
# 2. Construir el diccionario de valores a enviar
# Solo celdas editables: idtiporejilla=0 e idcategoria≠99999
valores = {}
for ejercicio in ejercicios:
for tipo in ejercicio["tipos"]:
if tipo["idtiporejilla"] == 0:
for celda in tipo["valores"]:
if celda["idcategoria"] != 99999:
# Aquí asignar el valor real de vuestro sistema
valores[str(celda["id"])] = celda["valor"]
# 3. Enviar
response = requests.put(
f"{BASE_URL}/declaraciones/5001/derecho-acceso",
headers=headers,
json={"valores": valores}
)
print(response.json())
// 1. Obtener la tabla
var getResponse = await client.GetAsync($"{baseUrl}/declaraciones/5001/derecho-acceso/tabla");
var ejercicios = JsonConvert.DeserializeObject<List<DaTablaDto>>(
await getResponse.Content.ReadAsStringAsync());
// 2. Construir el diccionario de valores editables
var valores = new Dictionary<int, int>();
foreach (var ejercicio in ejercicios)
foreach (var tipo in ejercicio.Tipos.Where(t => t.Idtiporejilla == 0))
foreach (var celda in tipo.Valores.Where(v => v.Idcategoria != 99999))
valores[celda.Id] = celda.Valor; // o el nuevo valor de vuestro sistema
// 3. Enviar
var payload = new { valores };
var content = new StringContent(
JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
var putResponse = await client.PutAsync(
$"{baseUrl}/declaraciones/5001/derecho-acceso", content);
Console.WriteLine(await putResponse.Content.ReadAsStringAsync());
Respuesta exitosa (200)
| Campo | Tipo | Descripción |
|---|---|---|
message |
string | Mensaje descriptivo con el número de registros actualizados |
count |
int | Número de celdas actualizadas |
Validaciones
| Validación | Límite | Error |
|---|---|---|
id de celda |
Debe ser > 0 | 400 "ID de item inválido" |
| Valor no negativo | >= 0 | 400 "valor no puede ser negativo" |
| Valor máximo | <= 999.999 | 400 "excede el máximo razonable" |
| Cantidad de ítems | Máx 2.000 por petición | 400 "Máximo 2000 ítems" |
valores vacío |
Al menos 1 entrada | 400 "Debe proporcionar al menos un valor" |
| Celdas de otra remisión | Se ignoran silenciosamente | Sin error, count no las incluye |
Ítems ignorados
Si se envía un id que no pertenece a la declaración o remisión activa, se ignora silenciosamente (no genera error). Esto permite enviar un volcado completo sin necesidad de filtrar previamente por ejercicio.
Rendimiento
La actualización ejecuta un UPDATE individual por celda. Para declaraciones con muchos ejercicios (>1.000 celdas), la petición puede tardar varios segundos. Se recomienda enviar solo las celdas cuyos valores hayan cambiado.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida (ver tabla) |
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe, no pertenece a la entidad, o no tiene remisión activa |
Estado de la declaración
Solo funciona cuando la declaración está en estado NoPresentada (1) o AlegacionesAbiertas (4).
Actualizar datos generales
Actualiza los datos generales de derecho de acceso: existencia de formulario de solicitud, URL, plazo medio de resolución y aclaraciones.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Body de la petición
{
"existeFormulario": true,
"urlFormulario": "https://sede.entidad.es/derecho-acceso",
"plazoMedioDias": 15,
"aclaraciones": "Formulario disponible en sede electrónica desde 2023"
}
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
existeFormulario |
bool | Sí | Si la entidad tiene formulario de solicitud de acceso disponible |
urlFormulario |
string | Condicional | URL del formulario. Obligatoria si existeFormulario es true |
plazoMedioDias |
int | No | Plazo medio de resolución en días (0–9.999) |
aclaraciones |
string | No | Texto libre de aclaraciones (máx. 5.000 caracteres) |
Ejemplo de petición
curl -s -X PUT \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/declaraciones/5001/derecho-acceso/general" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"existeFormulario": true,
"urlFormulario": "https://sede.entidad.es/derecho-acceso",
"plazoMedioDias": 15,
"aclaraciones": "Formulario disponible en sede electrónica desde 2023"
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"existeFormulario": True,
"urlFormulario": "https://sede.entidad.es/derecho-acceso",
"plazoMedioDias": 15,
"aclaraciones": "Formulario disponible en sede electrónica desde 2023"
}
response = requests.put(
f"{BASE_URL}/declaraciones/5001/derecho-acceso/general",
headers=headers,
json=payload
)
print(response.json())
var payload = new
{
existeFormulario = true,
urlFormulario = "https://sede.entidad.es/derecho-acceso",
plazoMedioDias = 15,
aclaraciones = "Formulario disponible en sede electrónica desde 2023"
};
var content = new StringContent(
JsonConvert.SerializeObject(payload),
Encoding.UTF8,
"application/json"
);
var response = await client.PutAsync(
$"{baseUrl}/declaraciones/5001/derecho-acceso/general", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Respuesta exitosa (200)
Validaciones
| Validación | Límite | Error |
|---|---|---|
| URL obligatoria con formulario | Si existeFormulario=true, urlFormulario no puede estar vacía |
400 |
| Formato URL | Debe empezar por http:// o https://, máx. 750 chars |
400 |
| Plazo medio | Entre 0 y 9.999 días | 400 |
| Aclaraciones | Máx. 5.000 caracteres | 400 |
URL obligatoria con formulario
Si existeFormulario es true, el campo urlFormulario es obligatorio. Si existeFormulario es false, la URL puede omitirse o enviarse vacía.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida (ver tabla) |
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Estado de la declaración
Solo funciona cuando la declaración está en estado NoPresentada (1) o AlegacionesAbiertas (4).
Documentos
Documentos
Los documentos son ficheros PDF asociados a una declaración: informes de evaluación provisionales y definitivos, justificantes de presentación, etc. Estos documentos son generados por el Comisionado de Transparencia a lo largo del proceso de evaluación.
Tipos de documentos
| Tipo | Código | Descripción |
|---|---|---|
| Informe provisional | 0 |
Informe de evaluación con calificaciones provisionales |
| Informe definitivo | 1 |
Informe de evaluación con calificaciones definitivas |
| Informe definitivo detallado | 2 |
Versión detallada del informe definitivo |
| Justificante de presentación | 3 |
Justificante de la presentación de la declaración |
| Justificante de alegaciones | 4 |
Justificante de la presentación de alegaciones |
Cuándo están disponibles
gantt
title Disponibilidad de documentos
dateFormat X
axisFormat %s
section Documentos
Justificante presentación :done, j1, 0, 1
Informe provisional :done, ip, 1, 2
Justificante alegaciones :done, j2, 2, 3
Informe definitivo :done, id, 3, 4
Informe definitivo detallado :done, idd, 3, 4
| Documento | Disponible cuando... |
|---|---|
| Justificante de presentación | La entidad presenta la declaración |
| Informe provisional | El Comisionado publica calificaciones provisionales |
| Justificante de alegaciones | La entidad presenta alegaciones |
| Informe definitivo | El Comisionado publica calificaciones definitivas |
| Informe definitivo detallado | Junto con el informe definitivo |
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/documentos/declaracion/{id} |
Listar documentos disponibles |
GET |
/documentos/{iddeclaracion}/{id} |
Descargar un documento |
Documentos PDF
Todos los documentos se descargan en formato PDF. Algunos documentos incluyen firma digital electrónica (sello PAdES) del Comisionado de Transparencia.
Listar documentos
Devuelve la lista de documentos disponibles para una declaración: informes de evaluación, justificantes de presentación, etc.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{BASE_URL}/documentos/declaracion/5001",
headers=headers
)
documentos = response.json()
for doc in documentos:
tamano_kb = doc["tamanoBytes"] / 1024
print(f"[{doc['tipoDescripcion']}] {doc['descripcion']} ({tamano_kb:.0f} KB)")
var response = await client.GetAsync($"{baseUrl}/documentos/declaracion/5001");
var json = await response.Content.ReadAsStringAsync();
var documentos = JsonConvert.DeserializeObject<List<DocumentoDto>>(json);
foreach (var doc in documentos)
{
Console.WriteLine($"[{doc.TipoDescripcion}] {doc.Descripcion} ({doc.TamanoBytes / 1024} KB)");
}
Respuesta exitosa (200)
[
{
"id": 1,
"tipo": 0,
"tipoDescripcion": "Informe provisional",
"registro": "REG-2024-001",
"descripcion": "Informe de evaluación provisional",
"tamanoBytes": 245760,
"fechaSubida": "2025-06-15T10:30:00Z"
},
{
"id": 2,
"tipo": 1,
"tipoDescripcion": "Informe definitivo",
"registro": "REG-2024-002",
"descripcion": "Informe de evaluación definitivo",
"tamanoBytes": 312000,
"fechaSubida": "2025-09-01T14:00:00Z"
},
{
"id": 3,
"tipo": 3,
"tipoDescripcion": "Justificante de presentación de declaración",
"registro": "REG-2024-003",
"descripcion": "Justificante de presentación",
"tamanoBytes": 45200,
"fechaSubida": "2025-03-10T09:15:00Z"
}
]
Campos de cada documento
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador único del documento |
tipo |
int | Código del tipo de documento (0-4) |
tipoDescripcion |
string | Descripción legible del tipo |
registro |
string | Número de registro del documento |
descripcion |
string | Descripción del documento |
tamanoBytes |
int | Tamaño del fichero en bytes |
fechaSubida |
datetime | Fecha de creación del documento (ISO 8601) |
Tipos de documento
| Tipo | Descripción |
|---|---|
0 |
Informe provisional |
1 |
Informe definitivo |
2 |
Informe definitivo detallado |
3 |
Justificante de presentación de declaración |
4 |
Justificante de presentación de alegaciones |
Lista vacía
Si la declaración aún no tiene documentos generados (por ejemplo, si la evaluación no ha llegado a la fase de calificaciones), la respuesta será un array vacío [].
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
La declaración no existe o no pertenece a la entidad |
Descargar documento
Descarga un documento específico en formato PDF. El fichero se devuelve como respuesta binaria.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
iddeclaracion |
int | ID de la declaración |
id |
int | ID del documento (obtenido del listado) |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
# Primero listar documentos
docs = requests.get(
f"{BASE_URL}/documentos/declaracion/5001",
headers=headers
).json()
# Descargar cada documento
for doc in docs:
response = requests.get(
f"{BASE_URL}/documentos/5001/{doc['id']}",
headers=headers
)
if response.status_code == 200:
filename = f"{doc['tipoDescripcion'].replace(' ', '_')}_{doc['id']}.pdf"
with open(filename, "wb") as f:
f.write(response.content)
print(f"Descargado: {filename} ({len(response.content) / 1024:.0f} KB)")
var response = await client.GetAsync($"{baseUrl}/documentos/5001/1");
if (response.IsSuccessStatusCode)
{
var bytes = await response.Content.ReadAsByteArrayAsync();
var filename = "informe_provisional.pdf";
await File.WriteAllBytesAsync(filename, bytes);
Console.WriteLine($"Descargado: {filename} ({bytes.Length / 1024} KB)");
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "$baseUrl/documentos/5001/1",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token"
]
]);
$content = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode === 200) {
file_put_contents("informe_provisional.pdf", $content);
echo "Informe descargado\n";
}
curl_close($ch);
Respuesta exitosa (200)
La respuesta es un fichero PDF binario:
Content-Type: application/pdf
Content-Disposition: attachment; filename="Informe_provisional_5001.pdf"
| Cabecera | Descripción |
|---|---|
Content-Type |
application/pdf |
Content-Disposition |
Nombre sugerido para el fichero |
Respuesta binaria
Este endpoint devuelve un fichero PDF, no JSON. Trate la respuesta como datos binarios y guárdela directamente en un fichero.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
404 |
El documento o la declaración no existe o no pertenece a la entidad |
Firma digital
Algunos documentos (informes provisionales y definitivos) incluyen firma digital electrónica (sello PAdES) del Comisionado de Transparencia. Puede verificar la firma con cualquier visor PDF compatible con firmas digitales.
Usuarios
Usuarios
Los usuarios son las personas autorizadas de la entidad para interactuar con el sistema de evaluación. Cada usuario tiene un tipo de acceso que determina qué operaciones puede realizar.
Tipos de usuario
| Código | Tipo | Descripción |
|---|---|---|
0 |
Consultivo | Solo puede consultar datos, sin capacidad de edición |
1 |
Carga | Puede introducir y modificar datos de la declaración |
2 |
Total | Acceso completo, incluyendo presentación de la declaración |
Para qué se necesitan los usuarios
Los IDs de usuarios se usan principalmente en el sistema de incidencias:
- Al crear una incidencia, el campo
usuarioIdidentifica quién reporta - Al comentar en una incidencia, el campo
usuarioIdidentifica quién comenta - Solo se aceptan IDs de usuarios activos de la entidad autenticada
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/usuarios |
Listar usuarios activos de la entidad |
Seguridad
Solo se exponen los campos no sensibles de los usuarios: ID, nombre, estado activo y tipo. No se exponen contraseñas, CIF, emails ni otros datos personales. Los IDs de usuarios de otras entidades no son aceptados en las operaciones de escritura.
Listar usuarios
Devuelve los usuarios activos autorizados de la entidad autenticada. Use estos IDs para el campo usuarioId al crear incidencias o comentar.
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/usuarios", headers=headers)
usuarios = response.json()
for u in usuarios:
print(f"ID: {u['id']} - {u['nombre']} ({u['tipoDescripcion']})")
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "$baseUrl/usuarios",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token"
]
]);
$response = curl_exec($ch);
$usuarios = json_decode($response, true);
foreach ($usuarios as $u) {
echo "ID: {$u['id']} - {$u['nombre']} ({$u['tipoDescripcion']})\n";
}
curl_close($ch);
Respuesta exitosa (200)
[
{
"id": 501,
"nombre": "ANA TORRES MEDINA",
"activo": true,
"tipo": 2,
"tipoDescripcion": "Total"
},
{
"id": 502,
"nombre": "CARLOS DELGADO NAVARRO",
"activo": true,
"tipo": 1,
"tipoDescripcion": "Carga"
}
]
Campos de cada usuario
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | Identificador único del usuario. Usar en usuarioId de incidencias |
nombre |
string | Nombre completo del usuario |
activo |
bool | Si el usuario está activo (siempre true en esta respuesta) |
tipo |
int | Tipo de acceso: 0=Consultivo, 1=Carga, 2=Total |
tipoDescripcion |
string | Descripción legible del tipo de acceso |
Tipos de acceso
| Código | Tipo | Puede consultar | Puede editar | Puede presentar |
|---|---|---|---|---|
0 |
Consultivo | Sí | No | No |
1 |
Carga | Sí | Sí | No |
2 |
Total | Sí | Sí | Sí |
Solo usuarios activos
Este endpoint solo devuelve usuarios activos. Los usuarios desactivados no aparecen en la lista.
Uso en incidencias
Al crear una incidencia o añadir un comentario, proporcione el id de uno de estos usuarios en el campo usuarioId. Si no se proporciona, se usa automáticamente el primer usuario de la entidad.
Errores posibles
| Código | Descripción |
|---|---|
401 |
Token ausente, expirado o inválido |
Incidencias
Incidencias
El sistema de incidencias permite a las entidades comunicarse con el Comisionado de Transparencia para reportar problemas técnicos, solicitar aclaraciones o comunicar cualquier incidencia durante el proceso de evaluación.
Flujo de una incidencia
stateDiagram-v2
[*] --> Abierta: Entidad crea
Abierta --> EnProceso: Comisionado asigna responsable
Abierta --> Abierta: Entidad o Comisionado comenta
EnProceso --> EnProceso: Comentarios cruzados
Abierta --> Cerrada: Entidad o Comisionado cierra
EnProceso --> Cerrada: Entidad o Comisionado cierra
Cerrada --> Abierta: Entidad reabre
Cerrada --> [*]
Estados
| Código | Estado | Descripción |
|---|---|---|
0 |
Abierta | Incidencia reportada, pendiente de atención |
1 |
EnProceso | El Comisionado la ha asignado a un responsable |
2 |
Cerrada | Resuelta o descartada. Puede reabrirse |
Tipos de incidencia
| Código | Tipo | Descripción |
|---|---|---|
0 |
Otros | Incidencias generales no clasificadas |
1 |
PublicidadActiva | Dudas o problemas relacionados con obligaciones de publicidad activa |
2 |
SoporteWeb | Problemas con el portal de transparencia de la entidad |
3 |
Técnicas | Errores técnicos en la plataforma (acceso, rendimiento, bugs) |
4 |
Usuarios | Gestión de usuarios, permisos y credenciales |
5 |
TransparenciaVoluntaria | Consultas sobre el régimen voluntario |
6 |
DerechoAcceso | Dudas sobre solicitudes de derecho de acceso |
Prioridad
| Código | Prioridad |
|---|---|
0 |
Baja (por defecto al crear) |
1 |
Media |
2 |
Alta |
La prioridad la asigna el Comisionado durante la revisión; las entidades no pueden cambiarla directamente.
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/incidencias |
Listar incidencias (paginado, filtros) |
GET |
/incidencias/{id} |
Detalle completo de una incidencia |
POST |
/incidencias |
Crear nueva incidencia |
POST |
/incidencias/{id}/comentario |
Añadir comentario |
POST |
/incidencias/{id}/cerrar |
Cerrar incidencia |
POST |
/incidencias/{id}/reabrir |
Reabrir incidencia cerrada |
POST |
/incidencias/{id}/adjuntos |
Subir adjuntos a un mensaje |
GET |
/incidencias/adjuntos/{idAdjunto} |
Descargar un adjunto |
Flujo típico
sequenceDiagram
participant E as Entidad (API)
participant C as Comisionado
E->>C: POST /incidencias (crear)
Note over E: Respuesta incluye id + idhistorico
E->>C: POST /incidencias/{id}/adjuntos (opcional)
Note over C: Comisionado recibe notificación SignalR
C->>E: Responde
Note over E: Webhook incidencia.respuesta
E->>C: POST /incidencias/{id}/comentario
C->>E: Responde de nuevo
E->>C: POST /incidencias/{id}/cerrar
Note over E: Puede reabrirse con POST .../reabrir
Campo usuarioId obligatorio
Al crear una incidencia o añadir un comentario es obligatorio indicar el usuarioId del usuario que reporta. Debe ser un ID válido de un usuario de la entidad autenticada (obtener con GET /usuarios). Esta validación garantiza la trazabilidad: cada acción queda asociada a un humano concreto, no a un usuario genérico.
Si no se envía usuarioId, la API devuelve 400 Bad Request con el mensaje "usuarioId es obligatorio".
Notificaciones por webhook
Si tiene webhooks registrados con los eventos incidencia.respuesta e incidencia.cerrada, recibirá notificaciones automáticas cuando el Comisionado responda o cierre una incidencia.
Notificaciones en tiempo real al Comisionado
Al crear una incidencia, añadir un comentario o reabrir una cerrada, el Comisionado recibe una notificación en tiempo real (campanita SignalR en el panel de administración). No es necesario que refresque la pantalla para ver la novedad.
Listar incidencias
Lista las incidencias de la entidad autenticada con paginación real en base de datos y filtros opcionales. Devuelve un DTO ligero sin histórico ni adjuntos: el detalle completo se obtiene con GET /incidencias/{id}.
Parámetros de query
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
page |
int | No | Número de página base 0 (por defecto 0) |
size |
int | No | Elementos por página, entre 1 y 100 (por defecto 20) |
estado |
int | No | Filtro: 0=Abierta, 1=EnProceso, 2=Cerrada |
tipo |
int | No | Filtro por tipo de incidencia, valor entre 0 y 6 (ver index) |
prioridad |
int | No | Filtro: 0=Baja, 1=Media, 2=Alta |
idDeclaracion |
int | No | Solo incidencias vinculadas a esta declaración |
Los filtros se combinan con AND. Las incidencias se devuelven ordenadas por fecha de creación descendente (más recientes primero).
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
# Pollear solo incidencias abiertas de alta prioridad
params = {
"estado": 0, # Abierta
"prioridad": 2, # Alta
"page": 0,
"size": 50
}
response = requests.get(f"{BASE_URL}/incidencias", headers=headers, params=params)
result = response.json()
print(f"Total: {result['total']} incidencias en {result['totalPages']} páginas")
for inc in result["data"]:
print(f" [{inc['id']}] {inc['titulo']} ({inc['nummensajes']} mensajes)")
var url = $"{baseUrl}/incidencias?estado=0&prioridad=2&size=50";
var response = await client.GetAsync(url);
var json = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<dynamic>(json);
Console.WriteLine($"Total: {result.total}");
foreach (var inc in result.data)
{
Console.WriteLine($" [{inc.id}] {inc.titulo}");
}
Respuesta exitosa (200 OK)
{
"data": [
{
"id": 201,
"titulo": "Error al acceder al portal",
"descripcion": "Al intentar acceder a la sección de presupuestos aparece un error 500. El problema ocurre...",
"estado": 0,
"tipo": 3,
"prioridad": 2,
"fechaalta": "2026-04-06T09:15:00",
"fechaactualizacion": "2026-04-06T14:30:00",
"iddeclaracion": 5001,
"nummensajes": 4,
"abierto": true
},
{
"id": 198,
"titulo": "Duda sobre criterio de publicidad activa",
"descripcion": "Necesitamos aclaración sobre el criterio...",
"estado": 1,
"tipo": 1,
"prioridad": 1,
"fechaalta": "2026-04-05T10:00:00",
"fechaactualizacion": "2026-04-05T16:45:00",
"iddeclaracion": null,
"nummensajes": 2,
"abierto": false
}
],
"total": 47,
"page": 0,
"size": 50,
"totalPages": 1
}
Campos de cada incidencia
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID único de la incidencia |
titulo |
string | Título |
descripcion |
string | Truncada a 300 caracteres. Para el texto completo use GET /incidencias/{id} |
estado |
int | 0=Abierta, 1=EnProceso, 2=Cerrada |
tipo |
int | Tipo, 0-6 (ver tabla en index) |
prioridad |
int | 0=Baja, 1=Media, 2=Alta |
fechaalta |
datetime | Momento de creación |
fechaactualizacion |
datetime | Último cambio o mensaje |
iddeclaracion |
int? | Declaración vinculada (o null) |
nummensajes |
int | Número total de mensajes en el histórico |
abierto |
bool | true si hay novedades sin leer por la entidad |
Campos de paginación
| Campo | Tipo | Descripción |
|---|---|---|
total |
int | Total de incidencias que cumplen los filtros (en BD) |
page |
int | Página devuelta (base 0) |
size |
int | Tamaño de página efectivo |
totalPages |
int | Número total de páginas |
Listado eficiente para polling
Este endpoint está diseñado para que clientes M2M lo consulten frecuentemente: la paginación se hace en BD con LIMIT/OFFSET y el total se obtiene en la misma query con COUNT(*) OVER(). No carga ni histórico ni adjuntos, por lo que es barato incluso con cientos de incidencias. Use GET /incidencias/{id} solo cuando necesite el detalle completo de una incidencia concreta.
Detección de novedades
Para detectar incidencias con respuestas nuevas del Comisionado, filtre por abierto=true a nivel cliente o combine con un webhook incidencia.respuesta.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Algún filtro tiene un valor fuera de rango (ej. tipo=7, prioridad=5) |
401 |
Token ausente, expirado o inválido |
Crear incidencia
Crea una nueva incidencia para comunicar un problema o solicitud al Comisionado de Transparencia.
Body de la petición
{
"titulo": "Error al acceder al portal",
"descripcion": "Al intentar acceder al portal de transparencia aparece un error 500. El problema ocurre desde las 10:00 de hoy.",
"tipo": 3,
"declaracionId": 5001,
"usuarioId": 501
}
Campos del body
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
titulo |
string | Sí | Título breve de la incidencia (máx 500 caracteres) |
descripcion |
string | Sí | Descripción detallada del problema (máx 10 000 caracteres) |
tipo |
int | No | Tipo de incidencia, valor entre 0 y 6 (ver tabla). Por defecto 0 (Otros) |
declaracionId |
int | No | ID de la declaración vinculada. Omitir si la incidencia es general |
usuarioId |
int | Sí | ID del usuario que reporta. Debe ser un usuario activo de la entidad autenticada (obtener con GET /usuarios) |
usuarioId ahora es obligatorio
A partir de la versión 3.0.1 el campo usuarioId es obligatorio al crear una incidencia. Antes existía un fallback al "primer usuario activo de la entidad", pero atribuía autoría a un usuario aleatorio y generaba trazas confusas. Ahora el cliente debe indicar explícitamente qué usuario reporta la incidencia.
Valores de tipo
| Valor | Tipo |
|---|---|
0 |
Otros |
1 |
PublicidadActiva |
2 |
SoporteWeb |
3 |
Técnicas |
4 |
Usuarios |
5 |
TransparenciaVoluntaria |
6 |
DerechoAcceso |
Ejemplo de petición
# Paso 1: obtener un usuarioId válido de la entidad
USUARIO_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/usuarios" \
| jq -r '.data[0].id')
# Paso 2: crear la incidencia
curl -s -X POST \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/incidencias" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"titulo\": \"Error al acceder al portal de transparencia\",
\"descripcion\": \"Al intentar acceder a la sección de presupuestos aparece un error 500. El problema ocurre desde las 10:00 de hoy.\",
\"tipo\": 3,
\"declaracionId\": 5001,
\"usuarioId\": $USUARIO_ID
}" | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Paso 1: obtener un usuarioId valido de la entidad (obligatorio)
usuarios = requests.get(f"{BASE_URL}/usuarios", headers=headers).json()
if not usuarios.get("data"):
raise RuntimeError("La entidad no tiene usuarios activos")
usuario_id = usuarios["data"][0]["id"]
# Paso 2: crear la incidencia
payload = {
"titulo": "Error al acceder al portal de transparencia",
"descripcion": "Al intentar acceder a la sección de presupuestos aparece un error 500.",
"tipo": 3,
"declaracionId": 5001,
"usuarioId": usuario_id
}
response = requests.post(f"{BASE_URL}/incidencias", headers=headers, json=payload)
if response.status_code == 201:
result = response.json()
print(f"Incidencia creada: id={result['id']} idhistorico={result['idhistorico']}")
# Guardar idhistorico si vas a subir adjuntos al mensaje inicial
else:
print(f"Error {response.status_code}: {response.json()}")
// Paso 1: obtener un usuarioId valido de la entidad
var usuariosResp = await client.GetAsync($"{baseUrl}/usuarios");
var usuariosJson = JsonConvert.DeserializeObject<dynamic>(
await usuariosResp.Content.ReadAsStringAsync());
int usuarioId = (int)usuariosJson.data[0].id;
// Paso 2: crear la incidencia
var payload = new
{
titulo = "Error al acceder al portal de transparencia",
descripcion = "Al intentar acceder a la sección de presupuestos aparece un error 500.",
tipo = 3,
declaracionId = 5001,
usuarioId = usuarioId
};
var content = new StringContent(
JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{baseUrl}/incidencias", content);
if (response.StatusCode == HttpStatusCode.Created)
{
var json = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<dynamic>(json);
Console.WriteLine($"Incidencia creada: id={result.id} idhistorico={result.idhistorico}");
}
Respuesta exitosa (201 Created)
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID de la incidencia creada |
idhistorico |
int | ID del mensaje inicial del histórico. Úselo para adjuntar ficheros al mensaje de apertura con POST /incidencias/{id}/adjuntos |
message |
string | Mensaje de confirmación |
Validaciones
| Validación | Límite | Error |
|---|---|---|
| Título | Obligatorio, máx 500 chars | 400 |
| Descripción | Obligatoria, máx 10 000 chars | 400 |
| Tipo | 0-6 | 400 "Tipo de incidencia debe ser 0-6" |
declaracionId |
Debe pertenecer a la entidad | 400 "no pertenece a esta entidad" |
usuarioId |
Obligatorio y debe ser un usuario activo de la entidad | 400 "usuarioId es obligatorio" o "no es un usuario autorizado" |
| Sanitización HTML | Tags HTML eliminados | Prevención XSS |
Sanitización HTML
El título y la descripción se sanitizan automáticamente: se eliminan todos los tags HTML para prevenir ataques XSS. Envíe texto plano, no HTML.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida (ver tabla) |
401 |
Token ausente, expirado o inválido |
Responder, cerrar y reabrir una incidencia
Añadir comentario
Añade un comentario a una incidencia. Útil para proporcionar información adicional o responder al Comisionado.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
id |
int | ID de la incidencia |
Body de la petición
{
"comentario": "Hemos verificado y el problema persiste. Adjuntamos captura de pantalla en el portal.",
"usuarioId": 501
}
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
comentario |
string | Sí | Texto del comentario (máx 10 000 caracteres) |
usuarioId |
int | Sí | ID del usuario que comenta. Debe ser un usuario activo de la entidad (GET /usuarios) |
usuarioId obligatorio
Al igual que en la creación, el campo usuarioId es obligatorio al añadir comentarios. Sirve para trazar qué usuario respondió concretamente.
Ejemplo de petición
response = requests.post(
f"{BASE_URL}/incidencias/201/comentario",
headers=headers,
json={
"comentario": "Hemos verificado y el problema persiste.",
"usuarioId": 501
}
)
result = response.json()
print(f"Comentario creado: id={result['id']}")
# Guardar result['id'] si vas a subir adjuntos a este mensaje
var payload = new
{
comentario = "Hemos verificado y el problema persiste.",
usuarioId = 501
};
var content = new StringContent(
JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
var response = await client.PostAsync(
$"{baseUrl}/incidencias/201/comentario", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Respuesta exitosa (200)
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del mensaje creado en el histórico. Úselo para adjuntar ficheros con POST /incidencias/{id}/adjuntos |
message |
string | Mensaje de confirmación |
Validaciones
| Validación | Límite | Error |
|---|---|---|
| Comentario | Obligatorio, máx 10 000 chars | 400 |
usuarioId |
Obligatorio y debe ser un usuario activo de la entidad | 400 |
| Sanitización HTML | Tags HTML eliminados | Prevención XSS |
Cerrar incidencia
Cierra una incidencia. La incidencia no se elimina, solo cambia su estado a Cerrada (2). Puede volver a abrirse con el endpoint de reabrir.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
id |
int | ID de la incidencia |
Body
No requiere body.
Ejemplo de petición
Respuesta exitosa (200)
Reabrir incidencia
Reabre una incidencia previamente cerrada. La incidencia vuelve al estado Abierta (0) y el Comisionado recibe una notificación en tiempo real de la reapertura.
Idempotente: si la incidencia ya estaba abierta, la petición devuelve 200 OK sin hacer ningún cambio.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
id |
int | ID de la incidencia a reabrir |
Body
No requiere body.
Ejemplo de petición
Respuesta exitosa (200)
Buenas prácticas para reabrir
Reabra una incidencia cuando el problema por el que se cerró vuelva a ocurrir o cuando necesite aportar información posterior al cierre. Para problemas nuevos no relacionados, cree una incidencia nueva en lugar de reabrir una antigua: facilita la trazabilidad del historial.
Errores posibles (todos los endpoints)
| Código | Descripción |
|---|---|
400 |
Validación fallida (comentario vacío, usuarioId ausente, etc.) |
401 |
Token ausente, expirado o inválido |
404 |
La incidencia no existe o no pertenece a la entidad |
Consulta sin alterar el estado de lectura
Por defecto, GET /incidencias/{id} no marca la incidencia como leída, para que los clientes M2M puedan pollear sin alterar el estado que ve el administrador. Si desea marcarla como leída al consultarla, use GET /incidencias/{id}?marcarLeida=true.
Flujo completo
Consulte el detalle de la incidencia (GET /incidencias/{id}) para ver todo el historial de conversación, incluyendo los comentarios del Comisionado y los adjuntos.
Adjuntos en incidencias
Las entidades pueden adjuntar ficheros a cualquier mensaje del histórico de una incidencia (el mensaje inicial al crearla, o cualquier comentario posterior). Los adjuntos quedan ligados al mensaje y se pueden descargar posteriormente.
Subir adjuntos
Sube uno o varios ficheros vinculados a un mensaje concreto del histórico de una incidencia. La petición debe ser multipart/form-data.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
id |
int | ID de la incidencia |
Campos del formulario
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
idhistorico |
int | Sí | ID del mensaje al que se adjuntan los ficheros. Se obtiene al crear la incidencia (idhistorico en la respuesta de POST /incidencias) o al añadir un comentario (id en la respuesta de POST /incidencias/{id}/comentario) |
archivos |
file[] | Sí | Uno o varios ficheros a subir (máximo 3 por petición) |
Límites
| Límite | Valor |
|---|---|
| Número máximo de archivos por petición | 3 |
| Tamaño máximo por archivo | 5 MB |
| Tipos MIME permitidos | image/jpeg, image/png, image/gif, application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
En lenguaje humano: imágenes JPG/PNG/GIF, PDF, Word (.doc, .docx) y Excel (.xls, .xlsx).
Ejemplo de petición
# Crear una incidencia y adjuntar un fichero al mensaje inicial
RESPUESTA=$(curl -s -X POST \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/incidencias" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"titulo\": \"Error con captura\",
\"descripcion\": \"Ver captura adjunta\",
\"tipo\": 3,
\"usuarioId\": 501
}")
ID=$(echo $RESPUESTA | jq -r '.id')
IDHISTORICO=$(echo $RESPUESTA | jq -r '.idhistorico')
# Subir captura al mensaje inicial
curl -s -X POST \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/incidencias/$ID/adjuntos" \
-H "Authorization: Bearer $TOKEN" \
-F "idhistorico=$IDHISTORICO" \
-F "archivos=@captura.png" \
-F "archivos=@log_error.pdf" | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
# 1. Crear la incidencia
payload = {
"titulo": "Error con captura",
"descripcion": "Ver captura adjunta",
"tipo": 3,
"usuarioId": 501
}
resp = requests.post(f"{BASE_URL}/incidencias", headers=headers, json=payload)
incidencia = resp.json()
# 2. Adjuntar ficheros al mensaje inicial
files = [
("archivos", ("captura.png", open("captura.png", "rb"), "image/png")),
("archivos", ("log.pdf", open("log.pdf", "rb"), "application/pdf"))
]
data = {"idhistorico": incidencia["idhistorico"]}
resp = requests.post(
f"{BASE_URL}/incidencias/{incidencia['id']}/adjuntos",
headers=headers,
files=files,
data=data
)
for adj in resp.json():
print(f"Adjuntado id={adj['id']} nombre={adj['nombre']}")
using var form = new MultipartFormDataContent();
form.Add(new StringContent(idHistorico.ToString()), "idhistorico");
var captura = await File.ReadAllBytesAsync("captura.png");
var capturaContent = new ByteArrayContent(captura);
capturaContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
form.Add(capturaContent, "archivos", "captura.png");
var response = await client.PostAsync(
$"{baseUrl}/incidencias/{idIncidencia}/adjuntos", form);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
Respuesta exitosa (200 OK)
[
{
"id": 451,
"idhistorico": 987,
"nombre": "captura.png",
"tamano": 124532,
"tipomime": "image/png",
"fecha": "2026-04-06T12:34:56"
},
{
"id": 452,
"idhistorico": 987,
"nombre": "log_error.pdf",
"tamano": 54321,
"tipomime": "application/pdf",
"fecha": "2026-04-06T12:34:57"
}
]
Array con un elemento por cada fichero subido. El campo id es el que se usará después para descargar cada adjunto.
Validaciones
| Validación | Error |
|---|---|
idhistorico pertenece a la incidencia indicada y a la entidad autenticada |
400 |
| Número de archivos entre 1 y 3 | 400 |
| Cada archivo no vacío | 400 |
| Cada archivo ≤ 5 MB | 400 |
| Tipo MIME en la lista de permitidos | 400 |
Doble validación de pertenencia
El servidor comprueba que idhistorico → idincidencia → identidad coincide con la entidad autenticada. Esto impide que un cliente malicioso suba adjuntos a una incidencia de otra entidad usando un idhistorico obtenido por otros medios.
Descargar un adjunto
Descarga el fichero binario de un adjunto. El servidor verifica que el adjunto pertenece a una incidencia de la entidad autenticada antes de servir el contenido.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
idAdjunto |
int | ID del adjunto (campo id devuelto en la respuesta de subir, o en el array historico[].adjuntos[] del detalle de la incidencia) |
Ejemplo de petición
Respuesta exitosa (200 OK)
El cuerpo de la respuesta es el fichero binario. Las cabeceras relevantes:
| Cabecera | Valor |
|---|---|
Content-Type |
Tipo MIME original del fichero (ej. image/png, application/pdf) |
Content-Disposition |
attachment; filename="<nombre original>" |
Errores posibles (ambos endpoints)
| Código | Descripción |
|---|---|
400 |
Validación fallida en subida (tipo MIME no permitido, >3 archivos, fichero vacío, idhistorico no pertenece a la incidencia) |
401 |
Token ausente, expirado o inválido |
404 |
Incidencia no encontrada, no pertenece a la entidad, o el adjunto no pertenece a ninguna incidencia de la entidad |
Consultar adjuntos existentes
Para ver qué adjuntos tiene una incidencia, use GET /incidencias/{id}: el detalle devuelve el array historico[] donde cada mensaje tiene su propia lista adjuntos[] con el id, nombre, tamano, tipomime y fecha de cada fichero. Use ese id para descargar.
Webhooks
Webhooks
Los webhooks permiten recibir notificaciones push automáticas cuando ocurren eventos relevantes en el sistema de evaluación. En lugar de hacer polling periódico a la API, el sistema envía una petición HTTP POST a la URL que la entidad configure.
Cómo funcionan
sequenceDiagram
participant E as Entidad
participant API as API T-Canaria
participant WH as Endpoint Entidad
E->>API: POST /webhooks (registrar)
API-->>E: Webhook creado (id: 3)
Note over API: Tiempo después...
API->>WH: POST (evaluacion.abierta)
Note over WH: Cabeceras:<br/>X-Webhook-Event<br/>X-Webhook-Signature
WH-->>API: 200 OK
Características
| Característica | Valor |
|---|---|
| Protocolo | HTTPS obligatorio |
| Método | POST |
| Formato payload | JSON |
| Firma | HMAC-SHA256 (opcional, vía secreto compartido) |
| Max webhooks por entidad | 5 |
| Eventos disponibles | 11 |
| Cabecera de evento | X-Webhook-Event |
| Cabecera de firma | X-Webhook-Signature |
Endpoints disponibles
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/webhooks/eventos |
Listar eventos disponibles |
POST |
/webhooks |
Registrar nuevo webhook |
GET |
/webhooks |
Listar webhooks registrados |
DELETE |
/webhooks/{id} |
Eliminar webhook |
POST |
/webhooks/test |
Probar un webhook |
Formato del payload recibido
Cuando se dispara un evento, la entidad recibe un POST con este formato:
POST https://mi-entidad.es/api/webhooks/tcanaria
Content-Type: application/json
X-Webhook-Event: evaluacion.abierta
X-Webhook-Signature: a1b2c3d4e5f6...
{
"evento": "evaluacion.abierta",
"timestamp": "2026-03-25T10:00:00Z",
"data": {
"evaluacionId": 11,
"titulo": "Evaluacion ITCanarias 2025"
}
}
URL HTTPS obligatoria
La URL del webhook debe usar HTTPS. No se aceptan URLs HTTP ni IPs privadas (protección anti-SSRF).
Verificación de firma
Si configura un secreto compartido al registrar el webhook, todas las peticiones incluirán la cabecera X-Webhook-Signature con un HMAC-SHA256 del body. Verifique esta firma para asegurar que la petición proviene del sistema T-Canaria. Ver verificar firma.
Registrar webhook
Registra un nuevo webhook para recibir notificaciones push de eventos del sistema de evaluación.
Body de la petición
{
"url": "https://mi-entidad.es/api/webhooks/tcanaria",
"eventos": ["evaluacion.abierta", "resultados.definitivos", "documento.disponible"],
"secreto": "mi-secreto-compartido-seguro-12345"
}
Campos del body
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
url |
string | Sí | URL HTTPS del endpoint que recibirá las notificaciones (POST) |
eventos |
array | Sí | Lista de eventos a los que suscribirse (mínimo 1) |
secreto |
string | No | Secreto compartido para firmar los payloads con HMAC-SHA256 (max 512 chars) |
Ejemplo de petición
curl -s -X POST \
"https://apientidades-pro.transparenciacanarias.org/apientidades/v1/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-entidad.es/api/webhooks/tcanaria",
"eventos": [
"evaluacion.abierta",
"resultados.provisionales",
"resultados.definitivos",
"documento.disponible",
"incidencia.respuesta"
],
"secreto": "mi-secreto-compartido-seguro-12345"
}' | jq .
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"url": "https://mi-entidad.es/api/webhooks/tcanaria",
"eventos": [
"evaluacion.abierta",
"resultados.definitivos",
"documento.disponible"
],
"secreto": "mi-secreto-compartido-seguro-12345"
}
response = requests.post(
f"{BASE_URL}/webhooks",
headers=headers,
json=payload
)
if response.status_code == 201:
webhook = response.json()
print(f"Webhook creado: ID {webhook['id']}")
else:
print(f"Error: {response.json()}")
var payload = new
{
url = "https://mi-entidad.es/api/webhooks/tcanaria",
eventos = new[] { "evaluacion.abierta", "resultados.definitivos", "documento.disponible" },
secreto = "mi-secreto-compartido-seguro-12345"
};
var content = new StringContent(
JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{baseUrl}/webhooks", content);
if (response.StatusCode == HttpStatusCode.Created)
{
var webhook = JsonConvert.DeserializeObject<dynamic>(
await response.Content.ReadAsStringAsync());
Console.WriteLine($"Webhook creado: {webhook.id}");
}
Respuesta exitosa (201 Created)
{
"id": 3,
"url": "https://mi-entidad.es/api/webhooks/tcanaria",
"eventos": ["evaluacion.abierta", "resultados.definitivos", "documento.disponible"],
"activo": true,
"fechaAlta": "2026-03-25T14:30:00Z"
}
| Campo | Tipo | Descripción |
|---|---|---|
id |
int | ID del webhook creado |
url |
string | URL registrada |
eventos |
array | Eventos suscritos |
activo |
bool | Si el webhook está activo |
fechaAlta |
datetime | Fecha de creación (ISO 8601) |
Validaciones
| Validación | Límite | Error |
|---|---|---|
| URL obligatoria | No vacía | 400 |
| URL HTTPS | Debe empezar por https:// |
400 |
| URL longitud | Max 2.000 caracteres | 400 |
| URL no privada | No IPs privadas (10.x, 172.16-31.x, 192.168.x, localhost) | 400 |
| Eventos | Al menos 1 | 400 |
| Eventos válidos | Solo de la lista de 11 eventos | 400 "Eventos no válidos: ..." |
| Secreto | Max 512 caracteres | 400 |
| Max webhooks | 5 por entidad | 400 "Máximo 5 webhooks" |
Protección SSRF
La API valida que la URL no apunte a redes privadas ni a localhost. Esto previene ataques Server-Side Request Forgery (SSRF). Las siguientes URLs se rechazan:
https://localhost/...https://127.0.0.1/...https://10.0.0.1/...https://192.168.1.1/...https://172.16.0.1/...
Secreto compartido
Se recomienda encarecidamente proporcionar un secreto para poder verificar la autenticidad de las notificaciones. Ver verificar firma.
Listar webhooks registrados
[
{
"id": 3,
"url": "https://mi-entidad.es/api/webhooks/tcanaria",
"eventos": ["evaluacion.abierta", "resultados.definitivos"],
"activo": true,
"fechaAlta": "2026-03-25T14:30:00Z",
"ultimaEjecucion": "2026-03-25T18:00:00Z",
"ultimoEstado": 200,
"ultimoError": ""
}
]
| Campo | Tipo | Descripción |
|---|---|---|
ultimaEjecucion |
datetime? | Fecha de la última ejecución del webhook |
ultimoEstado |
int? | Código HTTP de la última respuesta |
ultimoError |
string | Mensaje de error de la última ejecución (vacío si fue exitosa) |
Eliminar webhook
Errores posibles
| Código | Descripción |
|---|---|
400 |
Validación fallida (ver tabla) |
401 |
Token ausente, expirado o inválido |
404 |
El webhook no existe o no pertenece a la entidad (DELETE) |
Eventos disponibles
Devuelve la lista de eventos a los que se puede suscribir un webhook.
Ejemplo de peticion
Respuesta exitosa (200)
{
"eventos": [
"evaluacion.abierta",
"evaluacion.prorroga",
"evaluacion.cierre_proximo",
"evaluacion.cerrada",
"resultados.provisionales",
"resultados.definitivos",
"alegaciones.periodo_abierto",
"alegaciones.periodo_cierre",
"incidencia.respuesta",
"incidencia.cerrada",
"documento.disponible"
]
}
Tabla completa de eventos
| Evento | Cuando se dispara | Datos incluidos |
|---|---|---|
evaluacion.abierta |
Se abre una nueva evaluacion en la que participa la entidad | evaluacionId, titulo |
evaluacion.prorroga |
Se concede una prorroga del plazo de carga | evaluacionId, fechaInicio, fechaFin |
evaluacion.cierre_proximo |
Faltan pocos dias para el cierre del plazo de carga | evaluacionId, fechaCierre, diasRestantes |
evaluacion.cerrada |
La evaluacion se cierra y no se aceptan mas datos | evaluacionId, titulo |
resultados.provisionales |
Se publican las calificaciones provisionales | evaluacionId, declaracionId, notaFinal |
resultados.definitivos |
Se publican las calificaciones definitivas | evaluacionId, declaracionId, notaFinal |
alegaciones.periodo_abierto |
Se abre el periodo de alegaciones | evaluacionId, declaracionId, fechaFin |
alegaciones.periodo_cierre |
Se cierra el periodo de alegaciones | evaluacionId, declaracionId |
incidencia.respuesta |
El Comisionado responde a una incidencia de la entidad | incidenciaId, titulo |
incidencia.cerrada |
Se cierra una incidencia de la entidad | incidenciaId, titulo |
documento.disponible |
Hay un nuevo informe/documento disponible para descargar | documentoId, declaracionId, tipo, descripcion |
Ejemplo de payload por evento
evaluacion.abierta
{
"evento": "evaluacion.abierta",
"timestamp": "2026-01-15T10:00:00Z",
"data": {
"evaluacionId": 11,
"titulo": "Evaluacion ITCanarias 2025"
}
}
evaluacion.prorroga
{
"evento": "evaluacion.prorroga",
"timestamp": "2026-03-16T08:00:00Z",
"data": {
"evaluacionId": 11,
"fechaInicio": "2026-03-16",
"fechaFin": "2026-04-15"
}
}
evaluacion.cierre_proximo
{
"evento": "evaluacion.cierre_proximo",
"timestamp": "2026-03-10T08:00:00Z",
"data": {
"evaluacionId": 11,
"fechaCierre": "2026-03-15T23:59:59Z",
"diasRestantes": 5
}
}
resultados.provisionales
{
"evento": "resultados.provisionales",
"timestamp": "2026-06-01T12:00:00Z",
"data": {
"evaluacionId": 11,
"declaracionId": 5002,
"notaFinal": 75.9
}
}
resultados.definitivos
{
"evento": "resultados.definitivos",
"timestamp": "2026-09-01T12:00:00Z",
"data": {
"evaluacionId": 11,
"declaracionId": 5002,
"notaFinal": 78.2
}
}
incidencia.respuesta
{
"evento": "incidencia.respuesta",
"timestamp": "2026-03-25T15:30:00Z",
"data": {
"incidenciaId": 201,
"titulo": "Error al acceder al portal"
}
}
documento.disponible
{
"evento": "documento.disponible",
"timestamp": "2026-06-15T10:30:00Z",
"data": {
"documentoId": 1,
"declaracionId": 5002,
"tipo": 0,
"descripcion": "Informe de evaluacion provisional"
}
}
Eventos recomendados
Eventos minimos
Para una integración básica, se recomienda suscribirse al menos a:
evaluacion.abierta— para saber cuándo empieza una nueva evaluaciónresultados.provisionales— para recibir las calificacionesresultados.definitivos— para las calificaciones finalesdocumento.disponible— para descargar informes automáticamenteincidencia.respuesta— para saber cuándo el Comisionado responde
Todos los eventos
Puede suscribirse a todos los eventos de una sola vez enviando el array completo de 11 eventos al registrar el webhook.
Probar webhook
Dispara un evento de prueba a todos los webhooks activos de la entidad que estén suscritos al evento indicado. Permite verificar que los endpoints receptores están accesibles y responden correctamente antes de pasar a producción.
Comportamiento
Este endpoint no prueba un webhook concreto por ID. Envía el evento a todos los webhooks activos suscritos al evento indicado. Si quiere probar solo uno, regístrelo con ese único evento antes de lanzar la prueba.
Body de la petición
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
evento |
string | Sí | Nombre del evento a simular. Debe ser uno de los 11 eventos válidos (ver GET /webhooks/eventos) |
datosExtra |
object | No | Datos adicionales que se incluirán en el campo datos del payload enviado |
Ejemplo de petición
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.post(
f"{BASE_URL}/webhooks/test",
headers=headers,
json={"evento": "evaluacion.abierta"}
)
print(response.json())
Payload de prueba enviado a los webhooks
El sistema envía un POST a la URL de cada webhook suscrito al evento indicado:
POST https://mi-entidad.es/api/webhooks/tcanaria
Content-Type: application/json
X-Webhook-Event: evaluacion.abierta
X-Webhook-Signature: abc123...
{
"evento": "evaluacion.abierta",
"timestamp": "2026-03-25T14:30:00Z",
"data": {
"test": true,
"entidadId": 1001,
"entidadNombre": "AYUNTAMIENTO DE EJEMPLO",
"mensaje": "Evento de prueba 'evaluacion.abierta' disparado desde la API de integracion.",
"datos": {}
}
}
El campo test: true permite que su receptor distinga los eventos de prueba de los reales.
Respuesta exitosa (200)
{
"message": "Evento de prueba 'evaluacion.abierta' encolado para 2 webhook(s).",
"webhooks": [
{ "id": 1, "url": "https://mi-entidad.es/api/webhooks/tcanaria" },
{ "id": 2, "url": "https://mi-entidad.es/api/webhooks/backup" }
]
}
| Campo | Tipo | Descripción |
|---|---|---|
message |
string | Confirmación con el número de webhooks a los que se ha encolado el evento |
webhooks |
array | Lista de webhooks (id y url) que recibirán la notificación |
Envío asíncrono
El evento se encola y se envía de forma asíncrona. La respuesta 200 confirma que el envío fue programado, no que el endpoint de su entidad lo recibió correctamente. Revise los logs de su servidor para confirmar la recepción.
Errores posibles
| Código | Descripción |
|---|---|
400 |
Campo evento ausente o vacío |
400 |
Evento no válido (no está en la lista de eventos disponibles) |
401 |
Token ausente, expirado o inválido |
404 |
No hay webhooks activos suscritos al evento indicado. Registre uno primero con POST /webhooks |
Verificar antes de producción
Use este endpoint después de registrar un webhook para confirmar que su endpoint está accesible. Si no recibe la notificación, verifique:
- Que la URL es accesible desde internet (no solo desde su red interna)
- Que el certificado SSL es válido
- Que el endpoint acepta peticiones POST con
Content-Type: application/json - Que devuelve un código
2xx
Firma incluida
El payload de prueba incluye la firma HMAC-SHA256 en la cabecera X-Webhook-Signature si configuró un secreto al registrar el webhook. Aproveche la prueba para verificar su lógica de validación de firma.
Verificar firma HMAC-SHA256
Cuando se registra un webhook con un secreto compartido, todas las notificaciones incluyen la cabecera X-Webhook-Signature con un HMAC-SHA256 del body. Verificar esta firma garantiza que la notificación proviene del sistema T-Canaria y no ha sido manipulada.
Cabeceras del webhook
POST https://mi-entidad.es/api/webhooks/tcanaria
Content-Type: application/json
X-Webhook-Event: evaluacion.abierta
X-Webhook-Signature: 5d41402abc4b2a76b9719d911017c592ae...
Algoritmo de verificación
- Leer el body completo de la petición como string (sin parsear JSON)
- Calcular el HMAC-SHA256 del body usando el secreto compartido
- Convertir el resultado a hexadecimal en minusculas
- Comparar con el valor de la cabecera
X-Webhook-Signature - Si coinciden, la petición es auténtica
flowchart LR
A[Body de la peticion] --> B["HMAC-SHA256(body, secreto)"]
B --> C[Hex en minusculas]
C --> D{Coincide con<br/>X-Webhook-Signature?}
D -->|Sí| E[Petición auténtica]
D -->|No| F[Rechazar petición]
Ejemplos de verificación
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "mi-secreto-compartido-seguro-12345"
@app.route("/api/webhooks/tcanaria", methods=["POST"])
def recibir_webhook():
# 1. Obtener la firma de la cabecera
firma_recibida = request.headers.get("X-Webhook-Signature", "")
# 2. Calcular la firma del body
body = request.get_data() # bytes del body sin parsear
firma_calculada = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
body,
hashlib.sha256
).hexdigest()
# 3. Comparar de forma segura (timing-safe)
if not hmac.compare_digest(firma_calculada, firma_recibida):
abort(401, "Firma invalida")
# 4. Procesar el evento
evento = request.headers.get("X-Webhook-Event")
data = request.get_json()
print(f"Evento recibido: {evento}")
print(f"Datos: {data}")
# Procesar segun el tipo de evento
if evento == "resultados.provisionales":
nota = data["data"]["notaFinal"]
print(f"Nota provisional: {nota}")
elif evento == "documento.disponible":
doc_id = data["data"]["documentoId"]
print(f"Nuevo documento disponible: {doc_id}")
return "OK", 200
const crypto = require('crypto');
const express = require('express');
const app = express();
const WEBHOOK_SECRET = 'mi-secreto-compartido-seguro-12345';
// Importante: necesitamos el body como string sin parsear
app.use('/api/webhooks/tcanaria',
express.raw({ type: 'application/json' }));
app.post('/api/webhooks/tcanaria', (req, res) => {
// 1. Obtener la firma de la cabecera
const firmaRecibida = req.headers['x-webhook-signature'] || '';
// 2. Calcular la firma del body
const firmaCalculada = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body) // body como Buffer
.digest('hex');
// 3. Comparar de forma segura (timing-safe)
if (!crypto.timingSafeEqual(
Buffer.from(firmaCalculada),
Buffer.from(firmaRecibida)
)) {
return res.status(401).json({ error: 'Firma invalida' });
}
// 4. Procesar el evento
const evento = req.headers['x-webhook-event'];
const data = JSON.parse(req.body);
console.log(`Evento: ${evento}`);
console.log('Datos:', data);
switch (evento) {
case 'resultados.provisionales':
console.log(`Nota provisional: ${data.data.notaFinal}`);
break;
case 'documento.disponible':
console.log(`Nuevo documento: ${data.data.documentoId}`);
break;
}
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Webhook listener en puerto 3000'));
<?php
$WEBHOOK_SECRET = 'mi-secreto-compartido-seguro-12345';
// 1. Obtener la firma de la cabecera
$firmaRecibida = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
// 2. Leer el body sin parsear
$body = file_get_contents('php://input');
// 3. Calcular la firma
$firmaCalculada = hash_hmac('sha256', $body, $WEBHOOK_SECRET);
// 4. Comparar de forma segura (timing-safe)
if (!hash_equals($firmaCalculada, $firmaRecibida)) {
http_response_code(401);
echo json_encode(['error' => 'Firma invalida']);
exit;
}
// 5. Procesar el evento
$evento = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';
$data = json_decode($body, true);
error_log("Evento recibido: $evento");
switch ($evento) {
case 'resultados.provisionales':
$nota = $data['data']['notaFinal'];
error_log("Nota provisional: $nota");
break;
case 'documento.disponible':
$docId = $data['data']['documentoId'];
error_log("Nuevo documento: $docId");
break;
}
http_response_code(200);
echo 'OK';
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/webhooks/tcanaria")]
public class WebhookController : ControllerBase
{
private const string WebhookSecret = "mi-secreto-compartido-seguro-12345";
[HttpPost]
public async Task<IActionResult> RecibirWebhook()
{
// 1. Leer el body como string
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
// 2. Obtener la firma de la cabecera
var firmaRecibida = Request.Headers["X-Webhook-Signature"]
.FirstOrDefault() ?? "";
// 3. Calcular la firma
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(WebhookSecret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
var firmaCalculada = Convert.ToHexString(hash).ToLowerInvariant();
// 4. Comparar
if (!CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(firmaCalculada),
Encoding.UTF8.GetBytes(firmaRecibida)))
{
return Unauthorized("Firma invalida");
}
// 5. Procesar el evento
var evento = Request.Headers["X-Webhook-Event"].FirstOrDefault();
var data = JsonConvert.DeserializeObject<dynamic>(body);
Console.WriteLine($"Evento: {evento}");
return Ok();
}
}
Buenas prácticas de seguridad
Comparación timing-safe
Siempre use funciones de comparación timing-safe para evitar ataques de timing:
- Python:
hmac.compare_digest() - Node.js:
crypto.timingSafeEqual() - PHP:
hash_equals() - C#:
CryptographicOperations.FixedTimeEquals()
Nunca compare strings directamente con == ya que es vulnerable a timing attacks.
Leer body sin parsear
Para calcular la firma correctamente, debe leer el body de la petición como string/bytes antes de parsearlo como JSON. Si el framework parsea el JSON automáticamente y luego lo re-serializa, los bytes pueden diferir y la firma no coincidirá.
Seguridad
Seguridad
La API de integración T-Canaria implementa múltiples capas de seguridad para proteger los datos de las entidades evaluadas y garantizar la integridad de las operaciones.
Capas de seguridad
graph TD
A["Petición entrante"] --> B["TLS/HTTPS"]
B --> C["JWT Bearer"]
C --> D["Verificación de entidad"]
D --> E["Validación de estado"]
E --> F["Validación de datos"]
F --> G["Sanitización"]
G --> H["Operación ejecutada"]
| Capa | Descripción |
|---|---|
| TLS/HTTPS | Todas las comunicaciones van cifradas con TLS |
| JWT Bearer | Token de 1 hora obtenido con API Key |
| Verificación de entidad | Cada recurso se verifica contra la entidad del token |
| Validación de estado | Las operaciones de escritura solo se permiten en estados válidos |
| Validación de datos | Todos los datos de entrada se validan antes de procesarse |
| Sanitización | Textos sanitizados contra XSS (eliminación de tags HTML) |
Autenticación
La API usa un flujo de dos pasos:
- Intercambio de API Key por JWT:
POST /authcon la API Key - Uso del JWT en cada petición: Cabecera
Authorization: Bearer <token>
El token JWT expira en 1 hora. Puede renovarse con POST /auth/refresh antes de que expire.
Protección del token
El token JWT contiene el ID de la entidad y se usa para autorizar todas las operaciones. Protéjalo como una credencial sensible. No lo incluya en logs, URLs o código fuente.
Aislamiento de datos
Cada entidad solo puede acceder a sus propios datos:
- Las declaraciones de otras entidades devuelven
404(no403) - Los IDs de usuarios de otras entidades se rechazan en escritura
- Los documentos de otras declaraciones no son accesibles
Secciones de seguridad
| Sección | Descripción |
|---|---|
| Validaciones por endpoint | Tabla completa de validaciones |
| Restricciones por estado | Qué se puede hacer en cada estado |
| Códigos de error | Formato RFC 7807 y todos los códigos |
Validaciones por endpoint
La API implementa validación exhaustiva en todos los endpoints de escritura. Los datos se validan antes de procesarse. Si hay errores, la petición se rechaza con 400 Bad Request y un mensaje detallado.
Principio general
Validar todo, escribir nada
En operaciones bulk (PA, cuestionarios), se validan TODOS los ítems antes de escribir cualquiera. Si un solo ítem tiene error, no se modifica nada y se devuelve el listado de errores (hasta los 10 primeros).
Publicidad activa
| Validación | Límite | Error si falla |
|---|---|---|
obligacionId existe en la declaración |
Verificado contra el árbol PA real | 404 "obligación X no existe en esta declaración" |
opcionPublicacion en rango |
0-3 | 400 "opcionPublicacion debe ser 0-3" |
| Cada enlace tiene URL | No vacía | 400 "Cada enlace debe tener una URL" |
| URL bien formada | Empieza por http:// o https:// |
400 con detalle |
| Longitud URL | Max 2.048 caracteres | 400 "URL demasiado larga" |
| Orden de enlace | 0-99 | 400 "Orden debe ser 0-99" |
| Cantidad de enlaces | Max 10 por obligación | 400 "Máximo 10 enlaces" |
| Aclaraciones | Max 5.000 caracteres | 400 "no puede superar 5000 caracteres" |
| Autoevaluación valor | 0-100 | 400 "debe ser 0-100" |
| Autoevaluación ID | > 0 | 400 "ID inválido" |
| Autoevaluación cantidad | Max 10 criterios | 400 "Máximo 10 criterios" |
| Bulk: ítems | 1-500 | 400 si vacío o excede |
| Bulk: validación previa | Se validan TODOS antes de escribir | Si 1 falla, no se escribe ninguno |
Cuestionarios
| Validación | Límite | Error si falla |
|---|---|---|
id de pregunta pertenece al cuestionario |
Verificado contra la estructura real | 400 "no pertenece al cuestionario X" |
Cada componente tiene campo id |
Obligatorio | 400 "componente sin campo id" |
| Tipo checkbox | Valor debe ser boolean | 400 "checkbox espera boolean" |
| Tipo entero | Valor debe ser integer | 400 "entero espera integer" |
| Tipo decimal | Valor debe ser número | 400 "decimal espera número" |
| Tipo texto/memo | Valor debe ser string, max 10.000 chars | 400 con detalle |
| Tipo fecha | Valor debe ser string ISO parseable | 400 "fecha inválida" |
| Tipo selector | Valor debe ser integer | 400 "selector espera integer" |
| Grupos | Max 100 | 400 "Máximo 100 grupos" |
| Preguntas totales | Max 500 | 400 "Máximo 500 preguntas" |
| Respuesta no vacía | Al menos 1 componente | 400 "respuesta no puede estar vacía" |
Derecho de acceso
| Validación | Límite | Error si falla |
|---|---|---|
| ID de ítem | > 0 | 400 "ID inválido" |
| Valor no negativo | >= 0 | 400 "no puede ser negativo" |
| Valor no absurdo | <= 999.999 | 400 "excede el máximo razonable" |
| Cantidad de ítems | Max 2.000 | 400 "Máximo 2000 ítems" |
| Ítems de otra remisión | Se ignoran silenciosamente | Sin efecto |
| URL formulario | Obligatoria si existeFormulario=true |
400 "URL es obligatoria" |
| URL formulario formato | http:// o https://, max 750 chars |
400 con detalle |
| Plazo medio | 0-9.999 días | 400 "debe ser entre 0 y 9999" |
| Aclaraciones | Max 5.000 caracteres | 400 "no puede superar 5000" |
Incidencias
| Validación | Límite | Error si falla |
|---|---|---|
| Título | Obligatorio, max 500 chars | 400 con detalle |
| Descripción | Obligatoria, max 10.000 chars | 400 con detalle |
| Tipo | 0-3 (Otros, Técnico, Funcional, Datos) | 400 "debe ser 0-3" |
declaracionId |
Si proporcionado, verifica pertenencia a la entidad | 400 "no pertenece a esta entidad" |
usuarioId |
Si proporcionado, verifica pertenencia a la entidad | 400 "no pertenece a esta entidad" |
| Comentario | Obligatorio, max 10.000 chars | 400 con detalle |
| Sanitización HTML | Tags HTML eliminados de título, descripción y comentarios | Prevención XSS almacenado |
Webhooks
| Validación | Límite | Error si falla |
|---|---|---|
| URL | Obligatoria, HTTPS, max 2.000 chars | 400 con detalle |
| URL no privada | No localhost, 10.x, 172.16-31.x, 192.168.x | 400 "URL no permitida" |
| Eventos | Al menos 1, solo eventos válidos | 400 "Eventos no válidos: ..." |
| Secreto | Max 512 chars | 400 "Secreto demasiado largo" |
| Cantidad | Max 5 webhooks por entidad | 400 "Máximo 5 webhooks" |
Autenticación
| Validación | Error si falla |
|---|---|
| API Key ausente o vacía | 400 "Campo apiKey vacío o ausente" |
| API Key inválida | 401 "API Key inválida" |
| Entidad desactivada | 401 "Entidad desactivada" |
| Token JWT ausente | 401 "Token requerido" |
| Token JWT expirado | 401 "Token expirado" |
| Token JWT inválido (firma incorrecta) | 401 "Token inválido" |
Restricciones por estado
Las operaciones permitidas dependen del estado actual de la declaración. Esta tabla muestra qué puede hacer la entidad a través de la API en cada estado.
Tabla de permisos
| Operación | SinAbrir (0) | NoPresentada (1) | PendienteRevision (2) | Revisada (3) | AlegacionesAbiertas (4) | PendRevAlegaciones (5) | Finalizada (6) |
|---|---|---|---|---|---|---|---|
| Consultar estado | Sí | Sí | Sí | Sí | Sí | Sí | Sí |
| Inicializar | Sí | Sí | No | No | No | No | No |
| Ver PA | No | Sí | Sí | Sí | Sí | Sí | Sí |
| Actualizar PA | No | Sí | No | No | Sí | No | No |
| Ver cuestionarios | No | Sí | Sí | Sí | Sí | Sí | Sí |
| Enviar respuestas | No | Sí | No | No | Sí | No | No |
| Ver DA | No | Sí | Sí | Sí | Sí | Sí | Sí |
| Actualizar DA | No | Sí | No | No | Sí | No | No |
| Actualizar DA general | No | Sí | No | No | Sí | No | No |
| Ver análisis | No | Sí | Sí | Sí | Sí | Sí | Sí |
| Descargar borrador | No | Sí | Sí | Sí | Sí | Sí | Sí |
| Ver notas | No | No | No | No | Sí | Sí | Sí |
| Descargar informes (¹) | No | No | Cond. | Cond. | Cond. | Cond. | Sí |
| Ver documentos | No | Sí | Sí | Sí | Sí | Sí | Sí |
| Descargar documentos | No | Sí | Sí | Sí | Sí | Sí | Sí |
Estados que permiten escritura
Solo dos estados permiten operaciones de escritura (actualizar PA, cuestionarios, DA):
stateDiagram-v2
state "Permite escritura" as escritura {
NoPresentada: NoPresentada (1)
AlegacionesAbiertas: AlegacionesAbiertas (4)
}
state "Solo lectura" as lectura {
SinAbrir: SinAbrir (0)
PendienteRevision: PendienteRevision (2)
Revisada: Revisada (3)
PendRevAlegaciones: PendRevAlegaciones (5)
Finalizada: Finalizada (6)
}
| Estado | Escritura | Motivo |
|---|---|---|
SinAbrir (0) |
No | La declaración no está inicializada, no hay estructura donde escribir |
NoPresentada (1) |
Sí | Fase de carga de datos: la entidad rellena la declaración |
PendienteRevision (2) |
No | La declaración fue presentada, está en revisión |
Revisada (3) |
No | El Comisionado ha revisado, esperando alegaciones |
AlegacionesAbiertas (4) |
Sí | La entidad puede alegar y modificar datos |
PendRevAlegaciones (5) |
No | Las alegaciones fueron presentadas, en revisión |
Finalizada (6) |
No | Evaluación completada, datos inmutables |
¹ Informes: disponibilidad condicional
Los informes (GET /declaraciones/{id}/informes) están disponibles cuando se cumple alguna de estas condiciones:
- La declaración está en estado
Finalizada(6): siempre disponibles. - La evaluación está en fase
EvaluandoLaDeclaracion(3),EvaluandoLasAlegaciones(5) oCerrada(6): disponibles en cualquier estado de declaración.
En la práctica, los informes aparecen cuando el Comisionado entra en fase de revisión. Ver GET /declaraciones/{id}/informes.
Error al escribir en estado incorrecto
Si intenta una operación de escritura en un estado que no lo permite:
{
"type": "https://api.evaluax.es/errores/estado-no-permite-operacion",
"title": "Operación no permitida",
"status": 400,
"detail": "La declaración está en estado 'PendienteRevision' y no permite modificaciones.",
"instance": "/apientidades/v1/declaraciones/5001/pa/7001"
}
Verificar estado antes de escribir
Antes de enviar datos, verifique el estado de la declaración con GET /declaraciones/{id}. Si el estadoCodigo no es 1 ni 4, no intente operaciones de escritura.
Lectura siempre permitida
Las operaciones de lectura (GET) están permitidas en todos los estados, excepto en SinAbrir (0) donde no hay datos que consultar para PA, cuestionarios ni DA.
Flujo temporal
gantt
title Fases de una evaluación
dateFormat YYYY-MM-DD
section Escritura
Carga de datos (NoPresentada) :active, carga, 2025-01-15, 2025-03-15
Alegaciones (AlegacionesAbiertas) :active, aleg, 2025-07-01, 2025-07-31
section Solo lectura
Revisión :revision, 2025-03-16, 2025-06-30
Revisión alegaciones :revaleg, 2025-08-01, 2025-08-31
Publicación definitiva :final, 2025-09-01, 2025-09-15Códigos de error
La API usa el formato RFC 7807 Problem Details para todos los errores. Este estándar proporciona una estructura consistente y legible para comunicar problemas.
Formato de error
{
"type": "https://api.evaluax.es/errores/recurso-no-encontrado",
"title": "Recurso no encontrado",
"status": 404,
"detail": "Declaracion con ID 999 no encontrada.",
"instance": "/apientidades/v1/declaraciones/999",
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Campos del error
| Campo | Tipo | Descripción |
|---|---|---|
type |
string | URI que identifica el tipo de error (único por tipo) |
title |
string | Título corto y genérico del error |
status |
int | Código HTTP del error |
detail |
string | Explicación detallada específica de esta petición |
instance |
string | Ruta de la petición que falló |
traceId |
string | ID de correlación para soporte (coincide con cabecera X-Correlation-ID) |
Códigos HTTP utilizados
| Código | Significado | Cuándo ocurre |
|---|---|---|
200 |
OK | Petición exitosa (lectura o actualización) |
201 |
Created | Recurso creado (incidencias, webhooks) |
400 |
Bad Request | Parámetros inválidos, body malformado, validación fallida |
401 |
Unauthorized | Token ausente, expirado o API Key inválida |
404 |
Not Found | Recurso no existe o no pertenece a la entidad |
500 |
Internal Server Error | Error inesperado del servidor |
Tipos de error
400 Bad Request
Se produce cuando los datos enviados no son válidos:
{
"type": "https://api.evaluax.es/errores/validacion-bulk",
"title": "Errores de validación",
"status": 400,
"detail": "3 ítems con errores de validación (mostrando primeros 10)",
"instance": "/apientidades/v1/declaraciones/5001/pa",
"errors": [
"Ítem 0 (obligacionId 7001): URL demasiado larga (max 2048 caracteres)",
"Ítem 2 (obligacionId 7003): opcionPublicacion debe ser 0-3",
"Ítem 5 (obligacionId 7004): Máximo 10 enlaces por obligación"
]
}
401 Unauthorized
Se produce cuando hay problemas de autenticación:
404 Not Found
Se produce cuando el recurso no existe o no pertenece a la entidad:
{
"type": "https://api.evaluax.es/errores/recurso-no-encontrado",
"title": "Recurso no encontrado",
"status": 404,
"detail": "Declaracion con ID 999 no encontrada.",
"instance": "/apientidades/v1/declaraciones/999"
}
404 por seguridad
Cuando una entidad intenta acceder a un recurso de otra entidad, la API devuelve 404 en lugar de 403. Esto evita revelar la existencia de recursos de otras entidades.
500 Internal Server Error
Error inesperado del servidor:
{
"type": "https://api.evaluax.es/errores/interno",
"title": "Error interno",
"status": 500,
"detail": "Error inesperado al procesar la petición.",
"instance": "/apientidades/v1/declaraciones/5001/inicializar",
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Manejo recomendado de errores
response = requests.put(f"{BASE_URL}/declaraciones/5001/pa/7001",
headers=headers, json=payload)
if response.status_code == 200:
print("OK:", response.json())
elif response.status_code == 400:
error = response.json()
print(f"Validación: {error.get('detail')}")
if "errors" in error:
for e in error["errors"]:
print(f" - {e}")
elif response.status_code == 401:
print("Token expirado, renovando...")
# Renovar token y reintentar
elif response.status_code == 404:
print("Recurso no encontrado")
else:
error = response.json()
print(f"Error {response.status_code}: {error.get('detail')}")
print(f"TraceId: {error.get('traceId')}")
var response = await client.PutAsync(url, content);
switch (response.StatusCode)
{
case HttpStatusCode.OK:
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"OK: {result}");
break;
case HttpStatusCode.BadRequest:
var error = JsonConvert.DeserializeObject<ProblemDetails>(
await response.Content.ReadAsStringAsync());
Console.WriteLine($"Validación: {error.Detail}");
break;
case HttpStatusCode.Unauthorized:
// Renovar token y reintentar
break;
case HttpStatusCode.NotFound:
Console.WriteLine("Recurso no encontrado");
break;
default:
var serverError = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error: {serverError}");
break;
}
TraceId para soporte
Si recibe un error 500, incluya el traceId (o la cabecera X-Correlation-ID) al contactar con el equipo de soporte. Esto permite localizar el error exacto en los logs del servidor.
Referencia
Referencia
Esta sección recopila información transversal de la API que aplica a múltiples endpoints: enumeraciones, formato de respuestas y límites del sistema.
Contenido
| Sección | Descripción |
|---|---|
| Enumeraciones | Códigos de estados, tipos de incidencia, opciones de publicación |
| Formato de respuestas | Estructura de objetos individuales y listas paginadas |
| Límites del sistema | Límites de tamaños, cantidades y frecuencias |
URL base
| Entorno | URL |
|---|---|
| Producción | https://apientidades-pro.transparenciacanarias.org/apientidades/v1 |
Cabeceras comunes
Todas las peticiones autenticadas deben incluir:
Todas las respuestas incluyen:
X-Correlation-ID: <uuid> Identificador único de la petición
X-Api-Version: 1.0 Versión de la API
Content-Type: application/json; charset=utf-8
Convenciones
| Aspecto | Convención |
|---|---|
| Formato JSON | camelCase para nombres de campos |
| Fechas | ISO 8601 (ej: 2026-03-25T14:30:00Z) |
| Fechas solo | YYYY-MM-DD (ej: 2026-03-25) |
| Paginación | Base 0 (page=0 es la primera página) |
| Zona horaria | UTC en las respuestas de la API |
| Codificación | UTF-8 |
| IDs | Enteros autoincrementales |
| Booleanos | true / false (no 1 / 0) |
Estructura de URLs
https://apientidades-pro.transparenciacanarias.org/apientidades/v1/{recurso}
└─────┬──────┘ └┬┘ └──┬──┘
área API ver recurso
Ruta de la API
Todas las peticiones de la API de integración se realizan bajo la ruta /apientidades/v1/*.
Enumeraciones
Listado completo de códigos y enumeraciones utilizados en la API.
Estado de la declaración
El estado de la declaración determina en qué fase se encuentra la participación de la entidad en una evaluación.
| Código | Nombre | Descripción | Permite escritura |
|---|---|---|---|
0 |
SinAbrir |
La entidad no ha iniciado la declaración | No |
1 |
NoPresentada |
Declaración iniciada pero no presentada | Sí |
2 |
PendienteRevision |
Presentada, pendiente de revisión por el Comisionado | No |
3 |
Revisada |
Revisada por el Comisionado con calificaciones provisionales | No |
4 |
AlegacionesAbiertas |
Período de alegaciones abierto | Sí |
5 |
PendienteRevisionAlegaciones |
Alegaciones presentadas, pendiente de revisión | No |
6 |
Finalizada |
Evaluación completada con notas definitivas | No |
graph LR
A["0: SinAbrir"] --> B["1: NoPresentada"]
B --> C["2: PendienteRevision"]
C --> D["3: Revisada"]
D --> E["4: AlegacionesAbiertas"]
E --> F["5: PendRevAlegaciones"]
F --> G["6: Finalizada"]
D --> G
style B fill:#4CAF50,color:#fff
style E fill:#4CAF50,color:#fff
Estados con escritura
Los estados en verde (1 y 4) son los únicos que permiten operaciones de escritura (actualizar PA, cuestionarios, DA).
Estado de la evaluación
El estado de la evaluación refleja la fase global del ciclo de evaluación (común a todas las entidades).
| Código | Nombre | Descripción |
|---|---|---|
0 |
Pendiente |
Evaluación creada pero aún no abierta |
1 |
Abierta |
Abierta para recepción de declaraciones |
2 |
EnProrroga |
Plazo ampliado para alguna entidad |
3 |
EvaluandoLaDeclaracion |
En proceso de evaluación por el Comisionado |
4 |
Alegaciones |
Período de alegaciones abierto |
5 |
EvaluandoLasAlegaciones |
Evaluando alegaciones |
6 |
Cerrada |
Evaluación finalizada |
7 |
SinCumplir |
Sin cumplir requisitos mínimos |
Tipo de incidencia
| Código | Nombre | Descripción |
|---|---|---|
0 |
Otros |
Incidencias generales no clasificadas |
1 |
Técnico |
Problemas técnicos (errores de sistema, acceso, rendimiento) |
2 |
Funcional |
Dudas sobre el proceso o la metodología de evaluación |
3 |
Datos |
Problemas con datos (incorrectos, faltantes, duplicados) |
Opción de publicación (publicidad activa)
Indica si la entidad publica o no una obligación de publicidad activa.
| Código | Nombre | Descripción |
|---|---|---|
0 |
Publica información | La entidad publica y proporciona enlaces |
1 |
No publica por tipo de actividad | La obligación no aplica al tipo de entidad |
2 |
No publica por otra razón | No publica por una razón diferente |
3 |
No publica por carecer de la información | No dispone de la información requerida |
Tipo de documento
| Código | Nombre |
|---|---|
0 |
Informe provisional |
1 |
Informe definitivo |
2 |
Informe definitivo detallado |
3 |
Justificante de presentación de declaración |
4 |
Justificante de presentación de alegaciones |
Tipo de usuario
| Código | Nombre | Consultar | Editar | Presentar |
|---|---|---|---|---|
0 |
Consultivo | Sí | No | No |
1 |
Carga | Sí | Sí | No |
2 |
Total | Sí | Sí | Sí |
Tipo de componente de cuestionario
| Tipo | Formato de valor |
|---|---|
checkbox |
true / false |
texto |
String (max 10.000 chars) |
entero |
Integer |
decimal |
Number |
fecha |
String ISO YYYY-MM-DD |
memo |
String largo (max 10.000 chars) |
selector |
Integer (ID de opción) |
Formato de respuestas
La API utiliza dos formatos de respuesta principales: objetos individuales y listas paginadas.
Objeto individual
Las respuestas de un recurso individual devuelven el objeto directamente, sin envelope:
{
"declaracionId": 5001,
"evaluacionId": 10,
"estado": "NoPresentada",
"estadoCodigo": 1,
"ultimaPuntuacion": 72.5
}
Sin envelope
Los objetos individuales se devuelven directamente como JSON, sin un campo data ni metadatos adicionales. La respuesta es el objeto en sí.
Lista paginada
Los endpoints de listado devuelven un envelope con metadatos de paginación:
{
"data": [
{
"evaluacionId": 10,
"evaluacion": "Evaluacion ITCanarias 2024",
"declaracionId": 5001,
"estado": "NoPresentada"
}
],
"total": 142,
"page": 0,
"size": 20,
"totalPages": 8
}
Campos del envelope de paginación
| Campo | Tipo | Descripción |
|---|---|---|
data |
array | Registros de la página actual |
total |
int | Total de registros sin paginar |
page |
int | Página actual (base 0) |
size |
int | Registros por página solicitados |
totalPages |
int | Total de páginas disponibles |
Parámetros de paginación
Los endpoints paginados aceptan estos parámetros en el query string:
| Parámetro | Tipo | Default | Rango | Descripción |
|---|---|---|---|---|
page |
int | 0 |
0 - N | Página a obtener (base 0) |
size |
int | 20 |
1 - 100 | Registros por página |
Ejemplo:
Ejemplo de navegación de páginas
import requests
BASE_URL = "https://apientidades-pro.transparenciacanarias.org/apientidades/v1"
headers = {"Authorization": f"Bearer {token}"}
# Obtener la primera página
page = 0
size = 20
all_items = []
while True:
response = requests.get(
f"{BASE_URL}/evaluaciones",
headers=headers,
params={"page": page, "size": size}
)
data = response.json()
all_items.extend(data["data"])
if page >= data["totalPages"] - 1:
break # No hay más páginas
page += 1
print(f"Total obtenido: {len(all_items)} registros")
var allItems = new List<EvaluacionResumen>();
int page = 0;
int size = 20;
int totalPages;
do
{
var response = await client.GetAsync(
$"{baseUrl}/evaluaciones?page={page}&size={size}");
var result = JsonConvert.DeserializeObject<PaginatedResponse<EvaluacionResumen>>(
await response.Content.ReadAsStringAsync());
allItems.AddRange(result.Data);
totalPages = result.TotalPages;
page++;
}
while (page < totalPages);
Console.WriteLine($"Total: {allItems.Count} registros");
Listas no paginadas
Algunos endpoints devuelven arrays directos (sin paginación):
GET /usuarios— Array de usuariosGET /documentos/declaracion/{id}— Array de documentosGET /webhooks— Array de webhooksGET /declaraciones/{id}/pa— Árbol de PAGET /declaraciones/{id}/cuestionarios— Array de cuestionarios
Mensajes de confirmación
Las operaciones de escritura devuelven un objeto de confirmación:
Respuestas binarias
Los endpoints de descarga de PDF devuelven ficheros binarios:
No parsear como JSON
Las respuestas de descarga (borrador, documentos, informes) son ficheros binarios, no JSON. Trate la respuesta como bytes y guárdela en un fichero.
Límites del sistema
Tabla completa de todos los límites de tamaño, cantidad y frecuencia que aplican a la API de integración.
Autenticación
| Límite | Valor | Descripción |
|---|---|---|
| Expiración del token JWT | 1 hora | Tras expirar, obtener nuevo con POST /auth o renovar con POST /auth/refresh |
| Longitud API Key | Sin límite específico | La API Key puede contener caracteres especiales |
Paginación
| Límite | Valor | Descripción |
|---|---|---|
| Página base | 0 | La primera página es la 0 |
| Registros por página (default) | 20 | Si no se especifica size |
| Registros por página (máximo) | 100 | Valor máximo del parámetro size |
Publicidad activa
| Límite | Valor | Descripción |
|---|---|---|
| Ítems por petición bulk | 500 | Máximo en PUT /declaraciones/{id}/pa |
| Ítems mínimo bulk | 1 | Al menos un ítem |
| Enlaces por obligación | 10 | Máximo URLs por obligación |
| Longitud URL | 2.048 caracteres | Por enlace |
| Orden de enlace | 0-99 | Rango permitido |
| Longitud aclaraciones | 5.000 caracteres | Texto de aclaraciones |
| Criterios autoevaluación | 10 | Por obligación |
| Valor autoevaluación | 0-100 | Rango permitido |
| Opciones de publicación | 0-3 | Rango permitido |
Cuestionarios
| Límite | Valor | Descripción |
|---|---|---|
| Grupos por petición | 100 | Máximo en PUT /cuestionarios/{id} |
| Preguntas totales por petición | 500 | Sumando todas las preguntas de todos los grupos |
| Componentes por pregunta | Al menos 1 | Respuesta no puede estar vacía |
| Longitud texto/memo | 10.000 caracteres | Por componente de tipo texto o memo |
Derecho de acceso
| Límite | Valor | Descripción |
|---|---|---|
| Ítems por petición | 2.000 | Máximo en PUT /derecho-acceso |
| Valor por ítem | 0 - 999.999 | Rango permitido |
| Longitud URL formulario | 750 caracteres | En datos generales |
| Plazo medio días | 0 - 9.999 | Rango permitido |
| Longitud aclaraciones | 5.000 caracteres | En datos generales |
Incidencias
| Límite | Valor | Descripción |
|---|---|---|
| Longitud título | 500 caracteres | Obligatorio |
| Longitud descripción | 10.000 caracteres | Obligatorio |
| Longitud comentario | 10.000 caracteres | Obligatorio |
| Tipo de incidencia | 0-3 | Rango permitido |
Webhooks
| Límite | Valor | Descripción |
|---|---|---|
| Webhooks por entidad | 5 | Máximo registros activos |
| Longitud URL | 2.000 caracteres | |
| Longitud secreto | 512 caracteres | Para firma HMAC-SHA256 |
| Eventos mínimos | 1 | Al menos un evento al registrar |
| Eventos totales disponibles | 11 | Ver lista de eventos |
| Protocolo URL | Solo HTTPS | HTTP no aceptado |
Resumen visual
graph TD
A["Límites principales"] --> B["PA: 500 ítems bulk, 10 enlaces"]
A --> C["Cuestionarios: 100 grupos, 500 preguntas"]
A --> D["DA: 2.000 ítems"]
A --> E["Webhooks: 5 por entidad"]
A --> F["Token: 1 hora"]
A --> G["Paginación: 100 por página"]
Lotes para operaciones grandes
Si necesita enviar más ítems de los permitidos por petición, divida la operación en lotes:
- PA: Lotes de 500 ítems máximo
- DA: Lotes de 2.000 ítems máximo
- Cuestionarios: 500 preguntas máximo (raramente se alcanza)
Límites de validación vs. límites técnicos
Los límites listados son límites de validación que la API impone activamente. Además, existen límites técnicos del servidor (tamaño máximo de request, timeout de conexión, etc.) que no se listan aquí pero que podrían afectar a peticiones muy grandes.
Rendimiento
Para operaciones con muchos ítems (especialmente DA con >1.000 ítems), las peticiones pueden tardar varios segundos. El token JWT se valida al inicio de la petición, no durante la ejecución, así que no expirará a mitad de una operación larga.