Referencia · v1

API de AppConsol

Automatiza todo lo que haces en el dashboard: crear aplicaciones, publicar y modificar recursos, agregar o actualizar políticas de privacidad y términos, y gestionar solicitudes de eliminación de datos. La API es REST, usa JSON en UTF-8 y está versionada en la URL.

URL base

https://www.appconsol.com/api/v1

Solo HTTPS. Los cambios incompatibles se publicarán en una nueva versión (/v2); dentro de v1 solo se agregan campos o endpoints nuevos, así que tu cliente debe ignorar campos desconocidos.

Inicio rápido

  1. Inicia sesión y abre Dashboard → API y tokens.
  2. Crea un token con los scopes mínimos que necesites y una expiración corta.
  3. Copia el token (se muestra una sola vez) y guárdalo como secreto, p. ej. APPCONSOL_TOKEN.
  4. Verifica que funciona:
export APPCONSOL_TOKEN="acp_..."   # mejor: cárgalo desde tu gestor de secretos

curl https://www.appconsol.com/api/v1/me -H "Authorization: Bearer $APPCONSOL_TOKEN"

Autenticación

Todas las solicitudes requieren un token de acceso personal en el encabezado Authorization con el esquema Bearer:

Authorization: Bearer acp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • Los tokens empiezan con acp_ e incluyen una suma de verificación, lo que facilita detectarlos con escáneres de secretos.
  • La API ignora las cookies de sesión del navegador: solo acepta el encabezado Authorization, por lo que no es vulnerable a CSRF.
  • Nunca envíes el token en la URL o query string: quedaría en logs e historiales.
  • La API está pensada para uso servidor a servidor (backend, CI/CD, scripts). No expone CORS: no incrustes tokens en apps móviles, frontends ni extensiones.
  • Los tokens se crean y revocan solo desde el dashboard con tu sesión; un token no puede crear otros tokens.

Seguridad

Hash, nunca texto plano

Solo almacenamos un HMAC-SHA256 del token. Se muestra una única vez al crearlo; ni nuestro equipo puede recuperarlo.

Mínimo privilegio

Cada token tiene scopes granulares. Lectura, escritura, borrado, IA y datos personales son permisos separados.

Restricción por app

Limita un token a aplicaciones concretas. Para las demás responde 404, como si no existieran.

Lista de IPs

Opcionalmente acepta el token solo desde IPs o rangos CIDR conocidos (p. ej. tus runners de CI).

Expiración obligatoria

Todos los tokens expiran (7 a 365 días). Rota tokens antes de su vencimiento.

Revocación inmediata

Revocar un token desde el dashboard lo invalida en la siguiente solicitud.

Auditoría

Registramos creación y revocación de tokens, toda escritura y cada acceso denegado (IP, ruta, estado, requestId) durante 90 días.

Límites de uso

Rate limiting distribuido por token, por cuenta y por IP para intentos fallidos.

Recomendaciones

  • Guarda los tokens en un gestor de secretos o en los secrets de tu CI; nunca los subas a git.
  • Usa un token distinto por integración para poder revocarlos de forma independiente.
  • No registres el encabezado Authorization en tus logs.
  • Si un token se filtra: revócalo inmediatamente, revisa la actividad reciente en el dashboard y crea uno nuevo.
  • El contenido HTML que publiques se sirve en tu subdominio con una política CSP restrictiva; valida el contenido que generes automáticamente.

Scopes

Si falta un scope la API responde 403 insufficient_scope indicando cuáles faltan.

apps:readListar y consultar aplicaciones
apps:writeCrear y modificar aplicaciones
apps:deleteEliminar aplicaciones (irreversible)
resources:readLeer recursos y políticas
resources:writeCrear y modificar recursos y políticas
resources:deleteEliminar recursos y políticas
ai:generateGenerar políticas con IA (consume cuota)
data_deletion:readLeer solicitudes de eliminación de datos (datos personales)
data_deletion:writeActualizar o eliminar solicitudes de eliminación de datos

Respuestas y errores

Las respuestas exitosas envuelven el resultado en data. Los borrados responden 204 sin cuerpo. Los errores tienen siempre esta forma:

{
  "error": {
    "code": "validation_error",
    "message": "path usa una ruta reservada por la plataforma",
    "details": { "field": "path" },
    "requestId": "req_9f2c1d0e8b7a6f5e4d3c2b1a"
  }
}

Cada respuesta incluye X-Request-Id; compártelo con soporte si necesitas ayuda. Los campos desconocidos en el cuerpo se rechazan para evitar errores silenciosos.

HTTPcodeSignificado
400validation_errorCampo inválido, faltante o no permitido. details.field indica cuál.
400invalid_jsonEl cuerpo no es un objeto JSON válido.
401unauthorizedFalta el encabezado Authorization.
401invalid_tokenToken inválido, expirado, revocado o de una cuenta desactivada.
403insufficient_scopeEl token no tiene el scope requerido. details.missingScopes lo indica.
403ip_not_allowedLa IP de origen no está en la lista permitida del token.
403token_restrictedEl token está limitado a apps específicas y la operación no lo permite.
404not_foundEl recurso no existe o el token no tiene acceso a él.
409conflictConflicto: subdominio o ruta ya usados, o escritura concurrente.
409limit_exceededSe alcanzó un límite de la cuenta (apps, recursos, tokens).
413payload_too_largeEl cuerpo supera 512 KB.
415unsupported_media_typeFalta Content-Type: application/json.
429rate_limitedLímite de solicitudes superado. Respeta Retry-After.
500internal_errorError inesperado. Comparte el requestId con soporte.
502upstream_errorFalló un proveedor externo (p. ej. IA). Reintenta más tarde.

Límites

Cada respuesta autenticada incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (epoch en segundos). Ante un 429, espera los segundos indicados en Retry-After y reintenta con backoff exponencial.

Solicitudes por token120 por minuto
Autenticaciones fallidas por IP30 cada 10 minutos
Creación de apps20 por hora · 100 apps por cuenta
Generación con IA10 por hora por cuenta
Recursos por app50
Contenido por recurso100 000 caracteres
Tamaño del cuerpo512 KB
Tokens activos por cuenta20
Expiración máxima de un token365 días

Tipos de recurso y rutas

privacy-policyPolítica de privacidad (HTML).
termsTérminos y condiciones (HTML).
robotsrobots.txt.
adsapp-ads.txt para AdMob y redes publicitarias.
htmlPágina HTML libre (soporte, FAQ, landing).
textArchivo de texto plano.
otherCualquier otro archivo de texto (JSON, XML, etc.).
  • path debe empezar con /, tener como máximo 120 caracteres y contener solo letras, números, . _ - /. No se permiten .., //, segmentos que empiecen con punto ni barra final.
  • Rutas reservadas: /data-deletion-request, /api, /dashboard, /admin, /_next.
  • Cada ruta es única dentro de la app; el recurso queda publicado en https://app-<slug>.appconsol.com<path>.
  • El Content-Type servido se deduce de la extensión de name (.html, .txt, .json, .xml, .css…). Sin extensión: HTML para políticas y páginas, texto plano para el resto.

Cuenta

Verifica el token y consulta sus permisos.

GET/me

Devuelve el usuario dueño del token, sus scopes, restricciones y fecha de expiración. Úsalo para validar la configuración.

Scope: ninguno (cualquier token válido)

curl https://www.appconsol.com/api/v1/me \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
{
  "data": {
    "user": { "id": "66f1...", "email": "dev@ejemplo.com", "name": "Dev" },
    "token": {
      "id": "66f2...",
      "name": "CI deploy",
      "prefix": "acp_Ab12Cd",
      "scopes": ["apps:read", "resources:read", "resources:write"],
      "appIds": [],
      "expiresAt": "2026-12-29T18:00:00.000Z"
    },
    "rateLimit": { "requestsPerMinute": 120 }
  }
}

Aplicaciones

Cada aplicación tiene un subdominio público (app-<slug>.appconsol.com) donde se publican sus recursos.

GET/apps

Lista las aplicaciones accesibles por el token (todas, o solo las permitidas si el token está restringido).

Scope: apps:read

curl https://www.appconsol.com/api/v1/apps \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
{
  "data": [
    {
      "id": "66f1c0a2b7e4d91a2c3f4e59",
      "name": "Mi App",
      "description": "App de notas",
      "subdomain": "app-mi-app",
      "url": "https://app-mi-app.appconsol.com",
      "dataDeletionEnabled": true,
      "dataDeletionUrl": "https://app-mi-app.appconsol.com/data-deletion-request",
      "iconUrl": null,
      "featureGraphicUrl": null,
      "resourceCount": 4,
      "createdAt": "2026-09-30T18:00:00.000Z"
    }
  ]
}
POST/apps

Crea una aplicación. Por defecto incluye política de privacidad, términos, robots.txt y app-ads.txt a partir de plantillas.

Scope: apps:write

  • No disponible para tokens restringidos a aplicaciones específicas (403 token_restricted).
  • Límite: 20 creaciones por hora y 100 aplicaciones por cuenta.

Cuerpo JSON

name*string (1–80)Nombre visible de la app.
subdomain*string (3–20)Slug en minúsculas, números y guiones. Se publica como app-<slug>. Inmutable.
descriptionstring (≤500)Descripción mostrada en la página pública.
includeDefaultResourcesbooleanPor defecto true.
dataDeletionEnabledbooleanActiva el formulario público de eliminación de datos. Por defecto false.
curl -X POST https://www.appconsol.com/api/v1/apps \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mi App",
    "subdomain": "mi-app",
    "description": "App de notas",
    "dataDeletionEnabled": true
  }'
201 Created · { "data": { ...app, "resources": [ ... ] } }
GET/apps/{appId}

Detalle de una aplicación, incluyendo el listado de recursos (sin contenido).

Scope: apps:read

curl https://www.appconsol.com/api/v1/apps/$APP_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
PATCH/apps/{appId}

Modifica campos de la aplicación. Solo se actualizan los campos enviados.

Scope: apps:write

Cuerpo JSON

namestring (1–80)Nuevo nombre.
descriptionstring (≤500)Nueva descripción.
dataDeletionEnabledbooleanActiva o desactiva el formulario de eliminación de datos.
curl -X PATCH https://www.appconsol.com/api/v1/apps/$APP_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Nueva descripción", "dataDeletionEnabled": false }'
DELETE/apps/{appId}

Elimina la aplicación, todos sus recursos y sus solicitudes de eliminación de datos. Irreversible.

Scope: apps:delete

curl -X DELETE https://www.appconsol.com/api/v1/apps/$APP_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
204 No Content

Recursos

Un recurso es cualquier archivo publicado en el subdominio de la app: páginas HTML, políticas, robots.txt, app-ads.txt, texto, etc.

GET/apps/{appId}/resources

Lista los recursos de la app.

Scope: resources:read

Parámetros de consulta

typestringFiltra por tipo (ver Tipos de recurso).
include"content"Incluye el contenido completo de cada recurso.
curl "https://www.appconsol.com/api/v1/apps/$APP_ID/resources?type=html&include=content" \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
POST/apps/{appId}/resources

Crea un recurso nuevo y lo publica inmediatamente en su ruta.

Scope: resources:write

Cuerpo JSON

name*string (1–120)Nombre del archivo. Su extensión define el Content-Type servido.
type*stringprivacy-policy, terms, robots, ads, html, text u other.
pathstringRuta pública, p. ej. /faq.html. Si se omite se deriva de name.
contentstring (≤100 000)Contenido del recurso. Por defecto vacío.
curl -X POST https://www.appconsol.com/api/v1/apps/$APP_ID/resources \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "soporte.html",
    "type": "html",
    "path": "/soporte.html",
    "content": "<h1>Soporte</h1><p>Escríbenos a soporte@ejemplo.com</p>"
  }'
201 Created
{
  "data": {
    "id": "66f1c0a2b7e4d91a2c3f4e5a",
    "name": "privacy-policy",
    "type": "privacy-policy",
    "path": "/privacy-policy",
    "url": "https://app-mi-app.appconsol.com/privacy-policy",
    "contentLength": 5120,
    "lastUpdated": "2026-09-30T18:20:00.000Z",
    "content": "<!DOCTYPE html>..."
  }
}
GET/apps/{appId}/resources/{resourceId}

Obtiene un recurso con su contenido completo.

Scope: resources:read

curl https://www.appconsol.com/api/v1/apps/$APP_ID/resources/$RESOURCE_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
PATCH/apps/{appId}/resources/{resourceId}

Modifica un recurso. Solo se actualizan los campos enviados y lastUpdated se renueva.

Scope: resources:write

Cuerpo JSON

namestring (1–120)Nuevo nombre.
typestringNuevo tipo.
pathstringNueva ruta (debe ser única en la app).
contentstring (≤100 000)Nuevo contenido (reemplaza el anterior).
curl -X PATCH https://www.appconsol.com/api/v1/apps/$APP_ID/resources/$RESOURCE_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "content": "<h1>Soporte</h1><p>Nuevo horario</p>" }'
DELETE/apps/{appId}/resources/{resourceId}

Elimina el recurso; su URL pública deja de responder.

Scope: resources:delete

curl -X DELETE https://www.appconsol.com/api/v1/apps/$APP_ID/resources/$RESOURCE_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
204 No Content

Políticas

Atajo especializado para recursos de tipo privacy-policy y terms. Puedes crearlas desde plantilla, con IA o con tu propio contenido.

GET/apps/{appId}/policies

Lista las políticas (privacidad y términos) de la app.

Scope: resources:read

Parámetros de consulta

include"content"Incluye el contenido completo.
curl https://www.appconsol.com/api/v1/apps/$APP_ID/policies \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
POST/apps/{appId}/policies

Agrega una política a la app.

Scope: resources:writeai:generate (solo source=ai)

  • source=template genera un HTML profesional con el nombre de tu app.
  • source=ai redacta la política con IA (límite 10 por hora) y requiere además el scope ai:generate.
  • source=custom publica exactamente el contenido que envíes.
  • Si ya existe un recurso en la ruta, responde 409: usa PATCH para modificarla.

Cuerpo JSON

type*"privacy-policy" | "terms"Tipo de política.
source"template" | "ai" | "custom"Por defecto custom si envías content, template si no.
contentstring (≤100 000)Obligatorio con source=custom.
pathstringPor defecto /privacy-policy o /terms.
namestring (1–120)Por defecto privacy-policy o terms.
contactEmailstringSolo template: correo de contacto que aparece en el documento.
companyNamestring (≤120)Solo ai: empresa responsable.
curl -X POST https://www.appconsol.com/api/v1/apps/$APP_ID/policies \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "privacy-policy",
    "source": "template",
    "contactEmail": "privacidad@ejemplo.com"
  }'
201 Created · { "data": { ...recurso, "source": "template" } }
GET/apps/{appId}/policies/{policyId}

Obtiene una política con su contenido.

Scope: resources:read

curl https://www.appconsol.com/api/v1/apps/$APP_ID/policies/$POLICY_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
PATCH/apps/{appId}/policies/{policyId}

Modifica una política (contenido, nombre, ruta o tipo entre privacy-policy y terms).

Scope: resources:write

Cuerpo JSON

contentstring (≤100 000)Nuevo contenido.
namestring (1–120)Nuevo nombre.
pathstringNueva ruta.
type"privacy-policy" | "terms"Nuevo tipo.
curl -X PATCH https://www.appconsol.com/api/v1/apps/$APP_ID/policies/$POLICY_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @- <<'JSON'
{ "content": "<!DOCTYPE html><html><body><h1>Política de privacidad</h1>...</body></html>" }
JSON
DELETE/apps/{appId}/policies/{policyId}

Elimina la política.

Scope: resources:delete

curl -X DELETE https://www.appconsol.com/api/v1/apps/$APP_ID/policies/$POLICY_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
204 No Content

Solicitudes de eliminación de datos

Solicitudes enviadas por usuarios finales desde el formulario público. Contienen datos personales: concede estos scopes solo cuando sea imprescindible.

GET/apps/{appId}/data-deletion-requests

Lista solicitudes (más recientes primero, sin captura de pantalla) con paginación por cursor.

Scope: data_deletion:read

Parámetros de consulta

statusstringpending, processed o rejected.
limitinteger (1–100)Por defecto 50.
cursorstringValor pagination.nextCursor de la página anterior.
curl "https://www.appconsol.com/api/v1/apps/$APP_ID/data-deletion-requests?status=pending&limit=20" \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
{
  "data": [
    {
      "id": "66f3...",
      "appId": "66f1...",
      "username": "juan",
      "email": "juan@correo.com",
      "status": "pending",
      "createdAt": "2026-09-29T10:00:00.000Z"
    }
  ],
  "pagination": { "hasMore": false, "nextCursor": null }
}
GET/apps/{appId}/data-deletion-requests/{requestId}

Detalle de una solicitud, incluida la captura de pantalla (data URL).

Scope: data_deletion:read

curl https://www.appconsol.com/api/v1/apps/$APP_ID/data-deletion-requests/$REQUEST_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
PATCH/apps/{appId}/data-deletion-requests/{requestId}

Cambia el estado de la solicitud.

Scope: data_deletion:write

Cuerpo JSON

status*"pending" | "processed" | "rejected"Nuevo estado.
curl -X PATCH https://www.appconsol.com/api/v1/apps/$APP_ID/data-deletion-requests/$REQUEST_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "processed" }'
DELETE/apps/{appId}/data-deletion-requests/{requestId}

Elimina la solicitud definitivamente.

Scope: data_deletion:write

curl -X DELETE https://www.appconsol.com/api/v1/apps/$APP_ID/data-deletion-requests/$REQUEST_ID \
  -H "Authorization: Bearer $APPCONSOL_TOKEN"
204 No Content

Ejemplo completo

Crea una app, agrega su política de privacidad, la modifica y publica app-ads.txt. Requiere los scopes apps:write y resources:write.

// Node.js 18+ — guarda el token en una variable de entorno o gestor de secretos.
const API = 'https://www.appconsol.com/api/v1';
const TOKEN = process.env.APPCONSOL_TOKEN;

async function api(method, path, body) {
  const res = await fetch(API + path, {
    method,
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (res.status === 204) return null;
  const json = await res.json();
  if (!res.ok) {
    throw new Error(`${json.error.code}: ${json.error.message} (${json.error.requestId})`);
  }
  return json.data;
}

// 1. Crear la app sin recursos por defecto
const app = await api('POST', '/apps', {
  name: 'Mi App',
  subdomain: 'mi-app',
  includeDefaultResources: false,
});

// 2. Agregar la política de privacidad desde plantilla
const policy = await api('POST', `/apps/${app.id}/policies`, {
  type: 'privacy-policy',
  source: 'template',
  contactEmail: 'privacidad@ejemplo.com',
});

// 3. Modificar la política con tu propio HTML
await api('PATCH', `/apps/${app.id}/policies/${policy.id}`, {
  content: '<!DOCTYPE html><html><body><h1>Privacidad</h1>...</body></html>',
});

// 4. Publicar app-ads.txt
await api('POST', `/apps/${app.id}/resources`, {
  name: 'app-ads.txt',
  type: 'ads',
  content: 'google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0\n',
});

console.log('Publicado en', policy.url);

¿Encontraste un problema de seguridad?

Repórtalo de forma responsable a soporte@appconsol.com con el requestId y los pasos para reproducirlo. No incluyas tokens reales.

Documentación de la API | AppConsol