API de Capturas de Pantalla de Sitios Web#
El endpoint POST /v1/convert/url-to-screenshot captura una captura de pantalla de página completa de cualquier URL de acceso público como una imagen PNG de alta fidelidad. Gestiona automáticamente banners de cookies, modales, contenido de carga diferida, animaciones activadas por scroll y encabezados fijos (sticky) para producir una captura limpia y precisa. Ejecútalo de forma síncrona para obtener una URL de descarga prefirmada o los bytes PNG en bruto, o usa el modo asíncrono para capturar varias URLs en un lote (batch).
Endpoint#
POST /v1/convert/url-to-screenshot
Content-Type: application/json
Formato de salida: PNG (siempre). El formato de salida no es configurable: todas las capturas se generan como imágenes PNG de página completa.
Autenticación#
Este endpoint admite autenticación tanto con clave privada como con clave pública.
Clave Privada#
Incluye tu clave secreta en el header X-API-Key. Úsala para llamadas servidor a servidor donde la clave nunca se expone al cliente.
X-API-Key: sk_your_private_key
Clave Pública con JWT#
Para uso en el cliente, primero genera un token JWT usando tu clave pública y luego pásalo como token Bearer.
Paso 1 -- Obtén un token:
POST /v1/auth/token
X-API-Key: pk_your_public_key
Paso 2 -- Usa el token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Parámetros de la Solicitud#
Parámetros de Nivel Superior#
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de Plan |
|---|---|---|---|---|---|
url |
string o string[] |
Sí | -- | Una cadena de URL única o un array de URLs para capturar. Varias URLs requieren modo asíncrono. | -- |
async_mode |
boolean |
No | false |
Ejecuta la captura de forma asíncrona. Devuelve un batch_id inmediatamente. Obligatorio para lotes (múltiples URLs). |
Requiere acceso asíncrono |
direct_download |
boolean |
No | false |
Devuelve los bytes PNG en bruto en el cuerpo de la respuesta en lugar de una respuesta JSON con una URL prefirmada. Se fuerza a true para claves públicas. Incompatible con async_mode y con varias URLs. |
-- |
output_format |
boolean |
No | false |
Cuando es true con varias URLs, agrupa todos los PNG de salida en un único archivo ZIP. Requiere varias URLs. |
Requiere acceso a salida ZIP |
output_filename |
string |
No | Generado automáticamente | Nombre de archivo personalizado para el archivo de salida. La extensión .png se añade automáticamente. Formato predeterminado: {domain}_{timestamp}.png. |
-- |
job_id |
string |
No | -- | ID de job proporcionado por el cliente para recuperación ante timeout. Solo claves públicas. Cuando una conversión síncrona supera los límites de timeout del proxy inverso, el cliente puede sondear GET /v1/convert/status/{job_id} para obtener el resultado. Se ignora en claves privadas. |
-- |
notification_email |
string |
No | Email del propietario del proyecto | Dirección de email a notificar cuando finaliza un job asíncrono. Solo claves privadas. | -- |
callback_url |
string |
No | -- | URL de webhook que recibe una solicitud POST cuando finaliza la captura. Solo claves privadas. | Requiere acceso a webhooks |
Parámetros de Navegador y Renderizado#
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de Plan |
|---|---|---|---|---|---|
viewport_width |
integer |
No | 1920 |
Ancho del viewport del navegador en píxeles. El ancho de la captura coincide con este valor. | -- |
viewport_height |
integer |
No | 1080 |
Alto del viewport del navegador en píxeles. Se usa como referencia para el renderizado y el cálculo de unidades de viewport. El alto real de la captura lo determina el alto completo del contenido de la página. | -- |
load_media |
boolean |
No | true |
Espera a que todas las imágenes y videos terminen de cargar antes de capturar. Cuando es false, la captura es más rápida pero los medios pueden aparecer como placeholders. |
-- |
enable_scroll |
boolean |
No | true |
Recorre la página de arriba a abajo con scroll para activar el contenido de carga diferida (loaders basados en IntersectionObserver). | -- |
handle_sticky_header |
boolean |
No | true |
Detecta encabezados sticky/fijos y hace scroll hasta arriba antes de capturar para que el encabezado se renderice correctamente en la parte superior de la captura. | -- |
handle_cookies |
boolean |
No | true |
Cierra automáticamente los banners de consentimiento de cookies (OneTrust, Cookiebot, Didomi, Usercentrics y banners genéricos). | -- |
wait_for_images |
boolean |
No | true |
Espera a que todos los elementos <img> terminen de cargar (timeout de 5 segundos por imagen). |
-- |
wait_for_selector |
string |
No | null |
Selector CSS que se espera antes de la captura. Devuelve 422 si nunca aparece dentro de wait_for_selector_timeout. Útil para SPAs que hidratan el contenido después de la carga. |
-- |
wait_for_selector_timeout |
integer |
No | 10000 |
Milisegundos de espera para wait_for_selector (máximo 60000). |
-- |
block_ads |
boolean |
No | false |
Aborta las solicitudes a dominios conocidos de anuncios/rastreadores para que nunca se carguen, rendericen ni ralenticen la captura. | -- |
block_media |
boolean |
No | false |
Aborta por completo las solicitudes de imágenes y audio/vídeo para un renderizado más rápido y ligero. A diferencia de load_media (que solo controla la espera), esto evita por completo que los medios se descarguen. |
-- |
Autenticación y Solicitudes Personalizadas#
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de Plan |
|---|---|---|---|---|---|
auth |
object |
No | null |
Credenciales de HTTP Basic Auth para la URL de destino. Formato: {"username": "...", "password": "..."}. No se puede usar junto con un header Authorization personalizado. |
Requiere acceso a basic auth |
cookies |
array |
No | null |
Array de objetos de cookie a inyectar antes de la navegación. Máximo 50 cookies. Cada cookie debe tener name, value y domain o url. |
Requiere acceso a basic auth |
headers |
object |
No | null |
Diccionario de headers HTTP personalizados enviados en cada solicitud a la URL de destino. Máximo 20 headers. Headers bloqueados: host, content-length, transfer-encoding, connection, upgrade, te, trailer. |
Requiere acceso a basic auth |
single_page y pdf_options del endpoint url-to-pdf no son aplicables a las capturas de pantalla. Las capturas siempre capturan la página completa como una única imagen continua.
Esquema del Objeto Cookie#
Cada elemento del array cookies debe seguir esta estructura:
| Campo | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
name |
string |
Sí | -- | Nombre de la cookie. |
value |
string |
Sí | -- | Valor de la cookie. |
domain |
string |
Condicional | -- | Dominio de la cookie. Se debe proporcionar domain o url. |
url |
string |
Condicional | -- | URL con la que asociar la cookie. Se debe proporcionar domain o url. |
path |
string |
No | "/" |
Ruta de la cookie. Por defecto es "/" cuando se establece domain. |
Respuesta#
Síncrono con Descarga Directa (direct_download=true)#
Clave privada -- devuelve los bytes PNG en bruto:
HTTP 200 OK
Content-Type: image/png
Content-Disposition: inline; filename="example_20260405_123456789.png"
X-Object-Key: env/files/{project_id}/url-to-screenshot/example_20260405_123456789.png
X-File-Size: 456789
X-Conversion-Time: 8.2
X-Filename: example_20260405_123456789.png
(binary PNG data)
Clave pública -- devuelve JSON con una URL prefirmada:
{
"presigned_url": "https://spaces.example.com/...",
"object_key": "env/files/{project_id}/url-to-screenshot/example_20260405_123456789.png",
"filename": "example_20260405_123456789.png",
"file_size": 456789,
"conversion_time_seconds": 8.2,
"job_id": "client-provided-id"
}
Síncrono sin Descarga Directa (direct_download=false)#
Disponible solo con claves privadas.
{
"presigned_url": "https://spaces.example.com/...",
"object_key": "env/files/{project_id}/url-to-screenshot/example_20260405_123456789.png",
"filename": "example_20260405_123456789.png",
"file_size": 456789,
"conversion_time_seconds": 8.2
}
Modo Asíncrono#
Devuelve inmediatamente un batch_id para seguimiento.
HTTP 202 Accepted
{
"status": "processing",
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"url_count": 5,
"output_format": "individual"
}
Cuando output_format=true (agrupación en ZIP):
{
"status": "processing",
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"url_count": 5,
"output_format": "zip"
}
Sondeo de Estado del Job (Solo Claves Públicas)#
Para recuperación ante timeout con clave pública:
GET /v1/convert/status/{job_id}
Authorization: Bearer <jwt_token>
| Estado | Respuesta |
|---|---|
| Procesando | {"status": "processing"} |
| Éxito | {"status": "success", "presigned_url": "...", "object_key": "..."} |
| Fallido | {"status": "failed", "error": "..."} |
Sondeo de Estado de Lote (Solo Claves Privadas)#
Para jobs de lote asíncronos, sondea con el batch_id de la respuesta 202:
GET /v1/convert/batch/{batch_id}
X-API-Key: sk_your_private_key
Devuelve el estado agregado, los estados por URL y las URLs de descarga prefirmadas. Consulta Sondeo de Estado de Lote para ver el esquema de respuesta completo.
Payload del Webhook de Callback#
Cuando se proporciona un callback_url, EnConvert envía una solicitud POST a esa URL al finalizar.
Job de una sola URL:
{
"job_id": "activity_id",
"status": "success",
"batch_id": "...",
"gcs_uri": "object_key",
"filename": "example_20260405_123456789.png",
"file_size": 456789
}
Job de lote:
{
"job_id": "activity_id",
"status": "success",
"batch_id": "...",
"total_tasks": 10,
"successful_tasks": 8,
"failed_tasks": 2,
"tasks": [
{"url": "https://example.com/page1", "status": "success", "filename": "page1.png"},
{"url": "https://example.com/page2", "status": "failed", "filename": null}
]
}
Funcionalidades#
Captura de Página Completa#
Cada captura incluye todo el contenido de la página, no solo el viewport visible. El conversor:
- Renderiza la página con los valores especificados de
viewport_widthyviewport_height - Recorre la página con scroll para activar todo el contenido de carga diferida
- Calcula el alto real del contenido usando un recorrido del árbol DOM que mide la posición inferior máxima de todos los elementos visibles
- Redimensiona el viewport para abarcar todo el alto del contenido
- Captura la pantalla con
full_page=true
El resultado es una única imagen PNG alargada de la página completa.
Modo de Captura Limpia#
EnConvert gestiona automáticamente los obstáculos habituales de las páginas web para producir capturas limpias:
- Banners de consentimiento de cookies -- Cierra automáticamente banners de OneTrust, Cookiebot, Didomi, Usercentrics e implementaciones genéricas. Funciona en la página principal y en iframes.
- Cierre de modales y popups -- Cierra overlays usando varias estrategias: tecla Escape, botones de cierre ARIA, botones de cierre basados en clase (
"Close","Not now","No thanks","Skip") y botones de diálogo basados en role. Elimina los efectos residuales de blur, backdrop e inert tras el cierre. - Revelado de animaciones por scroll -- Fuerza la visibilidad de elementos ocultos por librerías de animación activadas por scroll, incluyendo WOW.js, AOS, ScrollReveal, GSAP ScrollTrigger y clases de animación genéricas (
.fadeIn,.slideIn, etc.). También revela todos los slides de Swiper. - Limpieza de dropdowns -- Cierra todos los dropdowns abiertos, convierte los elementos button de navegación en enlaces anchor reales para que se mantengan visualmente limpios, oculta los elementos
role="menu"y reposiciona los encabezados fijos a posición static.
Normalización de Unidades de Viewport#
Las capturas requieren un manejo especial de las unidades de viewport de CSS (vh, svh, lvh, dvh) porque el viewport se redimensiona al alto completo de la página. Sin normalización, los elementos dimensionados con unidades de viewport se estirarían a tamaños enormes. El conversor:
- Convierte todas las unidades relativas al viewport en valores de píxeles fijos basados en el alto de viewport original
- Limita las imágenes y videos anormalmente altos a 1.5 veces el alto de viewport original
- Gestiona particularidades de altura específicas de Elementor (contenedores flex, efectos de movimiento, contenedores de fondo)
- Preserva las dimensiones de video durante el proceso de normalización
HTTP Basic Auth#
Pasa auth con username y password para capturar páginas protegidas con HTTP Basic Authentication.
{
"url": "https://staging.example.com/dashboard",
"auth": {
"username": "admin",
"password": "secret"
}
}
Inyección de Cookies#
Inyecta hasta 50 cookies antes de que cargue la página. Útil para capturar páginas que requieren una sesión activa.
{
"url": "https://example.com/dashboard",
"cookies": [
{"name": "session_id", "value": "abc123", "domain": "example.com"},
{"name": "locale", "value": "en-US", "domain": "example.com"}
]
}
Headers Personalizados#
Envía hasta 20 headers HTTP personalizados en cada solicitud a la página de destino.
{
"url": "https://example.com/report",
"headers": {
"X-Custom-Token": "my-token-value",
"Accept-Language": "en-US"
}
}
Carga Diferida de Imágenes#
Cuando load_media y enable_scroll están habilitados (ambos por defecto en true), el conversor recorre la página lentamente con scroll (120px cada 90ms) para activar los loaders diferidos y luego espera a que todas las imágenes terminen de cargar, con un periodo de estabilización de layout de 500ms.
Establece load_media=false para una captura más rápida: el conversor usa scroll rápido (300px cada 30ms) con una estabilización más corta de 100ms, pero los medios pueden aparecer como placeholders.
Manejo de Encabezados Sticky#
Cuando está habilitado (por defecto true), el conversor detecta elementos con posición fixed y sticky que parecen ser encabezados, los reposiciona a posición static para una captura limpia, y hace scroll hasta arriba de la página antes de capturar.
Funcionalidades Adicionales de Renderizado#
- Emulación de medio screen -- La página se renderiza usando el medio CSS
screen(noprint), de modo que la captura coincide con lo que los usuarios ven en su navegador. - Modo stealth -- Usa enmascaramiento de fingerprint del navegador para evitar la detección de bots en páginas protegidas.
- Intercepción de popups -- Cierra automáticamente cualquier pestaña nueva del navegador o popup activado por la página.
- Bypass de CSP -- Gestiona las restricciones de Content Security Policy y Trusted Types que de otro modo bloquearían la manipulación de la página.
- Preservación de sombras -- Los elementos con
box-shadowytext-shadowse etiquetan para garantizar que las sombras se rendericen correctamente en la captura de salida.
Restricciones por Plan de Suscripción#
| Funcionalidad | Founding | Indie | Studio | Enterprise |
|---|---|---|---|---|
| Captura básica (una URL, síncrona) | Sí | Sí | Sí | Sí |
| Dimensionamiento de viewport | Sí | Sí | Sí | Sí |
| Modo asíncrono | No | Sí | Sí | Sí |
| Procesamiento por lotes (varias URLs) | No | Sí | Sí | Sí |
| Agrupación de salida en ZIP | No | No | Sí | Sí |
| Callbacks de webhook | No | No | Sí | Sí |
| HTTP Basic Auth | No | Sí | Sí | Sí |
| Inyección de cookies | No | Sí | Sí | Sí |
| Headers personalizados | No | Sí | Sí | Sí |
| Conversiones mensuales | 100 | Según el plan | Según el plan | Ilimitadas |
| Límite de tamaño de lote | 0 | Según el plan | Según el plan | Ilimitado |
| Retención de archivos | 1 hora | Según el plan | Según el plan | Según el plan |
Modo Asíncrono#
El modo asíncrono es útil para capturas de larga duración o al capturar varias URLs.
Cómo Funciona#
- Envía una solicitud con
async_mode=true(o pasa varias URLs, lo que habilita el modo asíncrono automáticamente). - La API devuelve HTTP 202 inmediatamente con un
batch_idy unurl_count. - Cada URL se captura en segundo plano, se sube al almacenamiento y se rastrea individualmente.
- Supervisa la finalización mediante sondeo de estado de lote, notificación por email o callback de webhook.
Notificación por Email#
Por defecto, se envía un email de finalización a la dirección de correo del propietario del proyecto. Puedes anularlo con notification_email:
{
"url": ["https://example.com/page1", "https://example.com/page2"],
"async_mode": true,
"notification_email": "[email protected]"
}
Callback de Webhook#
Proporciona un callback_url para recibir una notificación POST automática al finalizar:
{
"url": ["https://example.com/page1", "https://example.com/page2"],
"async_mode": true,
"callback_url": "https://your-server.com/webhook/enconvert"
}
Procesamiento por Lotes y en Volumen#
Captura varias URLs en una sola solicitud. Requiere modo asíncrono y una clave privada.
Salida Individual (predeterminado)#
Cada URL genera un archivo PNG separado:
{
"url": [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
],
"async_mode": true
}
Salida en Paquete ZIP#
Agrupa todas las capturas en un único archivo ZIP:
{
"url": [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
],
"async_mode": true,
"output_format": true,
"output_filename": "monthly-screenshots"
}
Ejemplos de Código#
Python (Clave Privada)#
import requests
response = requests.post(
"https://api.enconvert.com/v1/convert/url-to-screenshot",
headers={"X-API-Key": "sk_your_private_key"},
json={
"url": "https://example.com",
"viewport_width": 1440,
"viewport_height": 900
}
)
data = response.json()
print(data["presigned_url"])
PHP (Clave Privada)#
$ch = curl_init("https://api.enconvert.com/v1/convert/url-to-screenshot");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: sk_your_private_key"
],
CURLOPT_POSTFIELDS => json_encode([
"url" => "https://example.com",
"viewport_width" => 1440,
"viewport_height" => 900
])
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo $data["presigned_url"];
Node.js (Clave Privada)#
const response = await fetch("https://api.enconvert.com/v1/convert/url-to-screenshot", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "sk_your_private_key"
},
body: JSON.stringify({
url: "https://example.com",
viewport_width: 1440,
viewport_height: 900
})
});
const data = await response.json();
console.log(data.presigned_url);
Go (Clave Privada)#
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]interface{}{
"url": "https://example.com",
"viewport_width": 1440,
"viewport_height": 900,
})
req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/url-to-screenshot", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", "sk_your_private_key")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
JavaScript -- Navegador (Clave Pública)#
// Step 1: Get a JWT token
const tokenRes = await fetch("https://api.enconvert.com/v1/auth/token", {
method: "POST",
headers: { "X-API-Key": "pk_your_public_key" }
});
const { token } = await tokenRes.json();
// Step 2: Capture screenshot
const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-screenshot", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({
url: "https://example.com"
})
});
const data = await convertRes.json();
// Display the screenshot
const img = document.createElement("img");
img.src = data.presigned_url;
document.body.appendChild(img);
React (Clave Pública)#
import { useState } from "react";
function UrlToScreenshot() {
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState(null);
async function captureScreenshot() {
setLoading(true);
try {
// Get JWT token
const tokenRes = await fetch("https://api.enconvert.com/v1/auth/token", {
method: "POST",
headers: { "X-API-Key": "pk_your_public_key" }
});
const { token } = await tokenRes.json();
// Capture screenshot
const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-screenshot", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({ url: "https://example.com" })
});
const data = await convertRes.json();
setImageUrl(data.presigned_url);
} finally {
setLoading(false);
}
}
return (
<div>
<button onClick={captureScreenshot} disabled={loading}>
{loading ? "Capturing..." : "Take Screenshot"}
</button>
{imageUrl && <img src={imageUrl} alt="Screenshot" style={{ maxWidth: "100%" }} />}
</div>
);
}
export default UrlToScreenshot;
Respuestas de Error#
| Estado | Condición |
|---|---|
400 Bad Request |
Parámetro url faltante o vacío |
400 Bad Request |
output_format=true con una sola URL (requiere varias URLs) |
400 Bad Request |
direct_download=true con varias URLs |
400 Bad Request |
direct_download=true con async_mode=true |
400 Bad Request |
Objeto auth inválido (falta username o password) |
400 Bad Request |
cookies inválido (no es un array, supera las 50 entradas, faltan campos obligatorios) |
400 Bad Request |
headers inválido (no es un objeto, supera las 20 entradas, nombres de header bloqueados, valores no string) |
400 Bad Request |
Conflicto entre auth y el header Authorization personalizado |
400 Bad Request |
Clave pública intentando usar varias URLs |
401 Unauthorized |
Clave de API o token JWT faltante o inválido |
402 Payment Required |
Cuota mensual de ops agotada |
402 Payment Required |
El lote superaría la cuota mensual de ops restante |
402 Payment Required |
Límite de almacenamiento alcanzado |
403 Forbidden |
El endpoint no está entre los endpoints permitidos de la clave de API |
403 Forbidden |
Funcionalidad no disponible en el plan actual (async, webhook, ZIP, basic auth) |
403 Forbidden |
El tamaño del lote supera el límite de lote del plan |
404 Not Found |
ID de job no encontrado (al sondear el estado) |
500 Internal Server Error |
Fallo en la captura (crash del navegador, error de renderizado) |
Límites#
| Límite | Valor |
|---|---|
| Timeout de navegación de página | 60 segundos |
| Timeout de carga por imagen | 5 segundos |
| Timeout de cierre de banner de cookies | 3 segundos |
| Máximo de cookies por solicitud | 50 |
| Máximo de headers personalizados por solicitud | 20 |
| Operaciones mensuales | Según el plan (Founding: 500) |
| Tamaño de lote | Según el plan (Founding: deshabilitado) |
| Retención de archivos | Según el plan (Founding: 1 hora) |
| Timeout de entrega de webhook | 30 segundos |
Preguntas frecuentes#
¿Cómo tomo una captura de pantalla de página completa de un sitio web con una API?#
Envía una solicitud POST a /v1/convert/url-to-screenshot con un cuerpo JSON que contenga url, autenticándote con tu clave privada en el header X-API-Key (o un token Bearer JWT de una clave pública). Cada captura incluye todo el contenido de la página, no solo el viewport visible: el conversor redimensiona el viewport al alto completo del contenido y captura con full_page=true.
¿Puedo cambiar el formato de salida de la captura a JPEG o WebP?#
No. El formato de salida no es configurable: todas las capturas se generan como imágenes PNG de página completa.
¿Cómo controlo el ancho y el tamaño de la captura?#
Establece viewport_width (por defecto 1920): el ancho de la captura coincide con este valor. El alto de la captura lo determina el alto completo del contenido de la página, usando viewport_height (por defecto 1080) como referencia para el renderizado y el cálculo de unidades de viewport.
¿Puedo capturar una página protegida por login?#
Sí. Usa el parámetro auth para HTTP Basic Auth, inyecta hasta 50 cookies de sesión con cookies, o envía hasta 20 headers HTTP personalizados con headers. Estas opciones requieren acceso a basic auth en tu plan.
¿Cómo elimina la API los banners de cookies y popups de las capturas?#
Con handle_cookies habilitado (por defecto true), el conversor cierra automáticamente los banners de consentimiento de OneTrust, Cookiebot, Didomi, Usercentrics e implementaciones genéricas, funcionando tanto en la página principal como en iframes. Los modales y popups se cierran usando la tecla Escape, botones de cierre ARIA, botones de cierre basados en clase y botones de diálogo basados en role, eliminando los efectos residuales de blur y backdrop.