Un blog editorial embebible para tu sitio, publicando desde un panel simple y consumiendo una API JSON privada desde tu propio servidor.
redacta + te da un blog que vive en tu dominio, con SEO real y datos estructurados, sin que tengas que montar un CMS. El contenido se administra en el panel de Axobit y se publica en tu sitio mediante una API REST de solo lectura, protegida por una llave privada por proyecto que se consume en servidor (nunca llega al navegador).
Esta documentación cubre la integración actual (server-side), la referencia completa de la API y el roadmap del widget por script tag que está en camino.
¿Qué necesitas para empezar?
Un proyecto activo en Axobit con el producto redacta +, una llave de API generada en el panel (sección redacta +) y un servidor capaz de ejecutar PHP para hacer las llamadas server-side.
Conceptos clave
Antes de integrar, conviene conocer estas piezas:
Concepto
Qué es
Proyecto (blog)
Un blog configurado en el panel: título, descripción, idioma, URL base y categorías.
Llave de API
Token privado rda_… por proyecto. Solo se guarda su hash en el servidor de Axobit.
Artículo publicado
Solo los artículos en estado publicado aparecen en la API.
Bloques de cuerpo
El campo cuerpo del detalle es una lista tipada de párrafos (p) e imágenes (img).
Render server-side
Tu servidor arma la interfaz con la API. El navegador nunca ve la llave.
La llave privada de cada blog se genera en el panel y solo se almacena su hash SHA-256 — ni siquiera el panel puede volver a mostrarla después de crearla.
Autenticación
Hay dos tipos de llave y cada uno sirve para una cosa. No son intercambiables.
Llave
Formato
Dónde viaja
Para qué
Privada
rda_…
Solo en tu servidor, en el encabezado Authorization: Bearer
Pedir metadatos y contenido desde tu backend. Acceso completo.
Pública
rda_pub_…
En el navegador, como ?llave=
Solo lectura, para pintar el blog. Exige que tu origen esté autorizado.
Llave privada (server-to-server)
Se envía en el encabezado Authorization con esquema Bearer:
Como solo lee, si se filtra no pierdes nada: la regeneras en el panel. El daño real lo hace quien la use para consumir tu cuota, por eso la API también limita peticiones por llave.
CORS: tu dominio tiene que estar autorizado
Cuando la petición viene del navegador, la API mira el encabezado Origin y lo compara contra Orígenes autorizados en el panel del blog:
La allowlist es estricta: no hay Access-Control-Allow-Origin: *. Un origen por línea.
Sin orígenes configurados el acceso desde navegador queda denegado (fail-closed). El blog se ve, pero no carga contenido.
El flujo server-to-server no depende de la allowlist porque no envía Origin.
El preflight OPTIONS se responde sin leer la llave —el navegador no la manda ahí—, pero la allowlist sí se valida en el GET real.
Síntoma típico
El esqueleto del blog aparece y los artículos no. Casi siempre es el origen sin autorizar: revisa que coincida con https:// y con el puerto si lo usas, sin barra final.
Obtener las llaves
Entra al panel de Axobit
Inicia sesión y abre la sección redacta +.
Selecciona tu blog
Cada proyecto tiene su propio blog, su propia llave y sus propios orígenes.
Genera la que necesites
La privada se muestra una sola vez: guárdala en tu servidor. La pública se regenera tantas veces quieras.
Inicio rápido
Este ejemplo hace una llamada completa y muestra la portada del blog en tu página. Sustituye TU_LLAVE por tu llave privada.
Hay dos caminos, ambos soportados y con la misma cuenta:
· Server-side (esta página): pides los datos con la llave privada y renderizas tú. Máximo control, incluyendo el HTML.
· Kit en el navegador: subes el kit a tu hosting y redacta.js pide los datos con la llave pública. Es lo que quieres en el 90% de los casos — ver Kit de cliente.
Ojo con esto
El kit sirve el <head> desde PHP pero pinta la lista y el cuerpo de los artículos en el navegador. Google lo indexa, pero si necesitas el HTML completo en la primera respuesta (SSR, algunos renderers), usa el camino server-side.
Kit de cliente reutilizable
Lo más rápido es no integrar a mano: descargas el kit desde el panel, lo subes a tu hosting y editas cuatro constantes. Trae portada, artículo, SEO y estilos ya resueltos. Vive en axobit.mx/redacta-kit/.
Cómo se instala
Descarga el ZIP desde el panel
Se genera con tu llave pública ya incluida y sin llave privada: el ZIP no lleva ningún secreto.
Súbelo a tu hosting
En la raíz del dominio o en una subcarpeta (/blog, /noticias…). El kit se autodetecta con SCRIPT_NAME, así que funciona igual en ambos casos.
Edita las constantes
Todo vive en redacta-cliente.php. Cuatro valores y ya.
Registra tu origen en el panel
Sin esto el navegador no puede leer la API y el blog se ve vacío.
Archivos del kit
Archivo
Rol
index.php
Portada: hero, filtros por categoría, rejilla de tarjetas, banners y pie con los últimos artículos.
articulo.php
Artículo por slug: hero, progreso de lectura, byline, compartir, banners y relacionados.
redacta-cliente.php
Único archivo a editar. Constantes REDACTA_* + helpers.
seo.php
Head por artículo (title, description, canonical, OG, Twitter), JSON-LD y caché local con TTL.
redacta.js
Pinta portada, artículo y filtros desde la API en el navegador. Cachea 1 minuto.
uii.css
Tema editorial completo, con tokens de marca en :root.
.htaccess
URLs limpias, páginas legales y bloqueo de la carpeta cache/.
privacidad.php, terminos.php, cookies.php
Páginas legales, ya incluidas en el pie.
LEEME.txt
Instrucciones del propio ZIP.
Las constantes que tocas
redacta-cliente.php
// Lo mínimo para que funcione (4 líneas)const REDACTA_BLOG_URL = 'https://tusitio.com/blog'; // base pública, CON carpetaconst REDACTA_SITIO_URL = 'https://tusitio.com'; // raíz del dominioconst REDACTA_TITULAR = 'Nombre de tu empresa';
const REDACTA_LLAVE_PUBLICA = 'rda_pub_TU_LLAVE'; // la del panel// Opcionalesconst REDACTA_AUTOR_MARCA = 'Redacción Mi Sitio'; // firma de los artículosconst REDACTA_DESCRIPCION = 'Tu eslogan.';
const REDACTA_LOGO = '/logo.png';
const REDACTA_EMAIL_CONTACTO = 'hola@tusitio.com';
const REDACTA_URL_PUBLICIDAD = ''; // legacy: el destino real vive por banner en blog.banners// ¿Tu Apache aplica el .htaccess? false -> /blog/articulo.php?slug=…const REDACTA_URLS_LINDAS = true;
// Opcional: sin llave privada el SEO cae a los valores por defectoconst REDACTA_LLAVE_PRIVADA = '';
Cómo funciona por dentro
index.php pide los metadatos a la API desde el servidor y guarda el head en cache/ con TTL de 60 s. Usa la llave privada si existe; si no, la pública con un Origin permitido. Si la API no responde, degrada a los valores por defecto.
El navegador carga redacta.js, que pide portada o detalle con la llave pública y pinta todo.
Sin caché en el navegador: la respuesta no se guarda en localStorage, así que recargar siempre trae contenido recién publicado. fetch va con cache: 'no-store'.
El <head> es lo único que se sirve en el HTML inicial; por eso es lo que Google lee sin ejecutar JS.
Tu hosting no es Apache
No pasa nada: pon REDACTA_URLS_LINDAS = false y el blog funciona con direcciones tipo articulo.php?slug=…. Los enlaces se arman solos. El .htaccess también bloquea cache/; si no usas Apache, ese archivo simplemente se ignora.
Si quieres partirte de cero y montar tu propia UI, ignora el kit: consume la API con la llave privada y renderiza tú. Ver Resumen de la API.
Referencia
Resumen de la API
URL base (server-to-server, en Axobit):
¡Solo GET!Base
https://axobit.mx/bknd/archivosPHP/redactaApi.php
Cada endpoint usa el parámetro de query endpoint (alias resource). Todos son solo lectura GET. La API es sin CORS y debe consumirse en servidor.
Endpoint
Método
Descripción
Query
config
GET
Configuración del blog + categorías
—
categorias
GET
Solo las categorías activas
—
listado
GET
Artículos publicados paginados
categoria, pagina, porPagina, destacado
detalle
GET
Un artículo por slug (con cuerpo)
slugobligatorio
Respuesta estándar de éxito:
200 OK
{ "ok" : true, "data": { // … según el endpoint } }
Devuelve la configuración general del blog y sus categorías activas. Es el primer endpoint a llamar para obtener urlBase, urlImagenes y el mapa de rutas.
Cada artículo del listado usa el molde de artículo (sin el campo cuerpo).
detalle
Devuelve un artículo completo por slug. Es el único endpoint que incluye el cuerpo tipado y las imágenes de cuerpo. Además registra una vista (dedupe diario por IP + UA + fecha).
El campo cuerpo es una lista tipada de bloques que tu plantilla debe recorrer: renderiza html para los de tipo p y <img> para los de tipo img. Es ideal para insertar banners publicitarios entre párrafos (el kit lo hace con redacta.banner(i) cada 2 párrafos).
Banners desde el JSON (nov-2026):blog.bannersActivos + blog.banners[] ({id,src,href,target,rel,alt}) traen los banners del banco según la categoría del blog; con el switch apagado llega banners:[] y no se pinta nada. La portada y cada artículo usan el mismo set. La carpeta banners/ del kit es opcional: con tu propia implementación puedes quitarla. Cada banner pintado cuenta impresión y cada click cuenta click (redactaBanner.php), que Root monetiza por espacio. Contacto y logo del blog salen de la ficha del proyecto (no se capturan en el blog).
Open Graph por defecto: el artículo comparte su imagen de portada; la portada comparte blog.imagenSocial (campo Imagen para compartir en el panel) o tu valor en código, con caída a og-default.jpg. Siempre en URL absoluta.
Objetos de la API
Artículo (molde)
Campo
Tipo
Descripción
idArticulo
int
Identificador único.
titulo
string
Título del artículo.
slug
string
Slug ASCII usado en la URL.
url
string
URL absoluta de publicación (urlBase + slug).
resumen
string
Resumen / descripción SEO.
etiquetas
array
Etiquetas opcionales (array de strings).
categoria
objeto|null
{ id, nombre, slug } o null.
autor
objeto
{ id, nombre }.
minLectura
int
Minutos de lectura estimados.
destacado
bool
Si el artículo está destacado.
publicadoEn
string
Fecha/hora de publicación (datetime).
actualizadoEn
string
Fecha/hora de última actualización.
portada
objeto|null
Imagen de portada: { url, alt, ancho, alto } o null.
contenido
string
HTML sin estructurar (por compatibilidad).
Bloque de cuerpo
Campo
Tipo
Descripción
tipo
"p"|"img"
Párrafo o imagen.
html
string
Solo para p: HTML del párrafo.
url
string
Solo para img: URL de la imagen.
alt
string
Solo para img: texto alternativo.
Imagen (portada / cuerpo)
Campo
Tipo
Descripción
url
string
URL absoluta del recurso (WebP optimizado).
alt
string
Texto alternativo.
ancho/alto
int
Dimensiones en píxeles (útil para evitar CLS).
Paginación
Campo
Tipo
Descripción
pagina
int
Página actual.
porPagina
int
Tamaño de página solicitado.
total
int
Total de artículos que coinciden.
paginas
int
Total de páginas (ceil(total/porPagina)).
Operaciones
Códigos de error
Los errores siempre devuelven el mismo contenedor JSON y un código estable para que tu cliente pueda reaccionar de forma programática.
HTTP
Código
Cuándo ocurre
401
unauthorized
Falta el encabezado Authorization o la llave no es válida.
403
service_unavailable
El blog no está activo, está en pausa, el proyecto no está activo o no hay suscripción vigente.
404
not_found
El slug del artículo no existe (o no está publicado).
404
unknown
El endpoint solicitado no existe.
405
method_not_allowed
Se usó un método distinto a GET (POST, PUT, etc.).
422
slug_required
Se llamó a detalle sin el slug.
429
rate_limited
Se superó el límite de peticiones por minuto.
Ejemplo de respuesta de error
401 · unauthorized
{
"ok": false,
"error": {
"code": "unauthorized",
"message": "Se requiere una llave de proyecto."
}
}
Manejo recomendado
Revisa ok antes de usar data. Si ok === false, lee error.code y muestra un mensaje amigable. Implementa re-intentos con backoff en 429, respetando Retry-After.
Rate limits
Cada llave puede hacer hasta 180 peticiones por minuto, contadas por llave + IP de origen.
Si superas el límite, la API responde 429 con código rate_limited y la cabecera Retry-After: 60.
La ventana se reinicia cada minuto (por hora UTC + minuto).
180/min es holgado para un uso típico de blog. Aplica cache del lado del cliente para evitar llamadas repetidas en cada visita.
Mejora práctica: cache
Como la API está pensada para render server-side, conviene cachear listado y detalle durante unos minutos (p. ej. en archivo o memoria) para reducir latencia y llamadas. El kit base ya lo facilita por diseño (los helpers son idempotentes).
Seguridad
Sin CORS: la API solo responde GET y no habilita CORS, así que no puede consumirse desde el navegador de un cliente. Se usa desde tu backend.
Llaves con hash: solo se almacena el hash SHA-256 de cada llave. La llave real se muestra una vez al crearla.
Bloqueo de la API: en el despliegue del cliente, el .htaccess bloquea ^api/redacta\.php$ para que la API pública nunca se exponga en el dominio del cliente.
HTTPS forzado: el dominio sirve solo por HTTPS; redirige www y HTTP a la versión canónica.
Sanitización: todo el HTML de contenido se escapa/limpia en servidor antes de devolverse al cliente.
Rendimiento
Head en el servidor: PHP arma el <head> de cada artículo y lo guarda en cache/ con TTL. No hay una llamada a la API por visita.
Una sola capa de caché, en el servidor: la API responde con ETag y max-age=60 y seo.php guarda el head en disco con el mismo TTL. El navegador no cachea nada: recargar refresca al instante y, como la caché del servidor se revalida por ETag, no hay contenido viejo visible.
Render en servidor (opcional): si prefieres HTML listo e indexable sin depender de JS, renderiza tú con la llave privada.
Imágenes WebP optimizadas: el panel optimiza las subidas a WebP y devuelve dimensiones (ancho/alto) para evitar cambios de layout (CLS).
Tipografía variable: Fraunces + Inter con display=swap, sin bloqueo de render.
Datos estructurados: la portada y el artículo emiten JSON-LD (Blog, BlogPosting, BreadcrumbList) para SEO.
Lo que todavía no hay
No generamos sitemap.xml, ni RSS/Atom, ni export a markdown. Si necesitas alguno, dilo: son pendientes conocidos, no olvidos.
Navegador
Acceso con llave pública
Si solo quieres leer contenido desde el navegador —sin montar backend— la llave pública lo hace. Se pasa como parámetro llave y da acceso de solo lectura.
La llave debe ser pública (rda_pub_…). Una privada desde el navegador no funciona.
Tu dominio debe estar en Orígenes autorizados. Sin eso, la respuesta se corta aunque la llave sea válida.
La respuesta viene con { ok: true, data: … }. Si ok es false, mira error.code — la lista está en Códigos de error.
Y esto ya existe, listo para usar
No hace falta que escribas esa llamada: redacta.js del kit ya lo hace, con caché y los renderizadores de portada y artículo. El embed de una línea (embed.js con data-project) sigue siendo roadmap: si lo necesitas, dilo, pero por ahora el kit es la vía corta.
Más
Changelog
v1.0
API pública inicial
Endpoints config, categorias, listado y detalle; autenticación Bearer por llave privada; rate limit de 180/min; cuerpo tipado por bloques; registro de visitas con dedupe.
ago-2026
Simplificación del kit de cliente
El blog se instala en la raíz del dominio. Config centralizada en constantes REDACTA_* en redacta-cliente.php (ya no existe redacta.config.php); hoja de estilos uii.css. Kit base en redacta-kit/.
nov-2026
Banners en el JSON + banco Root
blog.bannersActivos/bannersCategoria/banners[] en portada/config/listado/detalle; switch por blog (apagado = banners:[]); banco Root con categorías, URL de click e impresiones/clicks; carpeta banners/ opcional; contacto y logo desde el proyecto (se quitaron los inputs repetidos).
próximamente
Widget por script tag
Embed de la portada sin backend. Detalles al liberarse.
Soporte
¿Necesitas ayuda para integrar o hay algo que no cuadra con la documentación?
Panel de Axobit: crea o configura tu blog en la sección redacta +.
Cliente de referencia:creditoautosnuevosyseminuevos.com (kit en redacta-kit/).
Guía interna:redacta-plus.md / documentacion.md del proyecto Axobit.