/ Guía para desarrolladores
Publicar mediante API en cualquier sitio web.
Cómo crear tu propio endpoint para que Orova publique los artículos terminados en un sitio que no usa WordPress.
Orova publica en WordPress desde el primer momento. Si tu sitio web no es WordPress — un CMS propio, una configuración headless, un sitio estático o tu propio backend — igualmente puedes dejar que Orova publique de forma automática recibiendo los artículos por una API pequeña. Tú escribes un endpoint HTTP; Orova lo llama cada vez que termina un artículo. Esta guía es para una persona desarrolladora y describe el contrato completo de la petición.
/ Resumen
Cómo funciona
Cuando Orova termina de escribir un artículo para una palabra clave, envía una única petición HTTP POST al endpoint que registraste. El cuerpo de la petición es JSON y contiene el artículo completo. Tu endpoint crea la entrada en tu lado y responde con la URL pública definitiva del artículo. Orova guarda esa URL como enlace publicado, igual que haría con una entrada de WordPress. Úsalo cuando quieras publicación automática en un sitio que no es WordPress, o cuando prefieras controlar por completo cómo se guardan las entradas.
/ Configuración
Configuración: seis pasos
- 1
Activa “Conectar mediante API” en tu proyecto. Abre tu proyecto (Proyectos → tu proyecto → Conexiones). Junto a la tarjeta de WordPress hay una tarjeta Conectar mediante API. Escribe ahí la URL de tu endpoint. El campo de clave secreta es opcional: déjalo vacío y Orova genera una, o pega la clave que ya use tu sitio. Al guardar, la clave aparece en la tarjeta con un botón Copiar. Un proyecto publica en un solo destino: al conectar por API se bloquea el botón de WordPress, y al revés también.
- 2
Crea un endpoint que acepte POST. En tu propio sitio o servidor, crea una ruta que escuche peticiones HTTP POST con cuerpo JSON. Esa es la URL que pegas en la pantalla Conexiones del proyecto. Orova la llama una vez por cada artículo que termina.
- 3
Verifica el Bearer token en cada petición. Cada petición de Orova lleva una cabecera Authorization con la forma “Bearer <secret>”, usando exactamente la clave secreta que se muestra en tu proyecto. Compárala con tu copia guardada y rechaza todo lo que no coincida: eso es lo que impide que cualquier otra persona publique en tu sitio.
- 4
Crea el artículo a partir del payload. Lee el cuerpo JSON y crea una entrada en tu CMS o base de datos: usa title, slug y content_html para el artículo, excerpt como meta descripción, featured_image_url como imagen de portada, y keyword / lang / published_at como metadatos.
- 5
Devuelve la URL real del artículo. Responde con HTTP 200 o 201 y un cuerpo JSON { "url": "https://tusitio.com/el-articulo-nuevo" }. Orova guarda esa URL como enlace publicado del artículo. Si devuelves un estado fuera de 2xx, o falta el campo url, Orova considera que la publicación falló.
- 6
Atiende las peticiones de actualización del motor Optimizar. Cuando el cuerpo trae action = "update", busca la entrada existente por target_url (o slug) y sobrescribe title, content_html y excerpt en el mismo sitio, sin cambiar la URL. Responde 200 con { "url": ... } apuntando a esa misma entrada. Los endpoints que todavía no lo implementen solo harán que Orova marque la optimización como fallida; la publicación de artículos nuevos sigue funcionando igual.
/ Contrato
La petición que envía Orova
Cada artículo terminado llega como una petición con esta forma:
POST <your endpoint URL>
Headers:
Content-Type: application/json
Authorization: Bearer <secret> # the secret key shown in Project -> Connections
User-Agent: Orova-SEO
Body (JSON):
{
"title": "...", # article headline
"slug": "...", # URL-friendly identifier
"content_html": "...", # full article HTML
"excerpt": "...", # meta description
"featured_image_url": "..." | null,
"keyword": "...", # target keyword
"lang": "en", # ISO language code
"published_at": "2026-05-17T09:00:00.482913", # ISO 8601, UTC, no offset suffix
"status": "draft" # optional: "draft" keeps it unpublished
}
# Optional fields are left out of the body when empty — never sent as null./ Referencia
Campos del payload
Campos de una petición de publicación (artículo nuevo). Las peticiones de borrado y de actualización reutilizan el mismo endpoint con los cuerpos más cortos que se muestran en sus propias secciones más abajo.
| Campo | Tipo | Descripción |
|---|---|---|
| title | string | El titular del artículo. |
| slug | string | Identificador apto para URL que se sugiere para la entrada. |
| content_html | string | El cuerpo completo del artículo en HTML listo para publicar. |
| excerpt | string | Un resumen corto, pensado para la meta descripción. |
| featured_image_url | string | null | URL de la imagen de portada, o null si no hay ninguna. |
| keyword | string | La palabra clave SEO objetivo para la que se escribió el artículo. |
| lang | string | Idioma como código ISO, por ejemplo "en" o "vi". |
| published_at | string | El momento en que Orova envió el artículo, en ISO 8601 y hora UTC, sin sufijo de zona horaria. |
| status | string | undefined | Opcional. "draft" deja el artículo sin publicar; "publish", o ningún valor, lo publica. |
| action | string | undefined | No aparece en artículos nuevos. "delete" te pide eliminar una entrada; "update" (lo envía el motor Optimizar) te pide sobrescribir una entrada existente en su sitio. |
| target_url | string | Solo con action = "update": la URL pública de la entrada que hay que sobrescribir. No cambies esa URL. |
| id | number | undefined | Solo se envía cuando Orova conoce el id de tu artículo (por `list` o `get`): búscalo antes que `target_url`. |
Los campos opcionales desaparecen del cuerpo cuando no hay nada que enviar: Orova nunca envía null, así que léelos de forma defensiva. status también es opcional: "draft" crea el artículo como borrador, y "publish" — o ningún status — lo publica de inmediato.
/ Contrato
La respuesta que debes devolver
Una vez creada la entrada, responde con un estado HTTP 200 o 201 y este cuerpo JSON:
HTTP 200 (or 201)
Content-Type: application/json
{
"url": "https://yoursite.com/published-article"
}Orova guarda ese url como enlace publicado del artículo. Si tu endpoint devuelve cualquier estado fuera de 2xx, o un cuerpo que no sea un objeto JSON con el campo url, Orova considera que la publicación falló y marca el artículo para que puedas reintentarlo. Responde en menos de 60 segundos: pasado ese tiempo Orova corta la petición (vale igual para publicar, actualizar y eliminar).
/ Ejemplo
Ejemplo de código
Un endpoint receptor mínimo en Node.js con Express. Los mismos pasos — verificar el token, ramificar según action (update / delete), crear o modificar la entrada y devolver la URL — sirven en cualquier lenguaje o framework.
// Node.js / Express — a minimal receiving endpoint
import express from "express";
const app = express();
app.use(express.json({ limit: "5mb" }));
// The secret Orova generated for this project.
const OROVA_SECRET = process.env.OROVA_SECRET;
app.post("/orova/publish", async (req, res) => {
// 1. Verify the Bearer token.
const auth = req.get("authorization") || "";
if (auth !== "Bearer " + OROVA_SECRET) {
return res.status(401).json({ error: "unauthorized" });
}
// 2. Visual Editor: list your articles. action = "list".
if (req.body.action === "list") {
const posts = await listPosts(req.body.limit || 500); // newest first, 500 max
return res.status(200).json({ ok: true, posts });
}
// 3. Visual Editor: read one article in full. action = "get".
if (req.body.action === "get") {
const post = await findPost(req.body.id, req.body.target_url, req.body.slug);
if (!post) return res.status(404).json({ ok: false, err: "not_found" });
return res.status(200).json({ ok: true, post });
}
// 4. Visual Editor: store an image, answer with its absolute URL.
if (req.body.action === "upload_image") {
const { filename, mime, data_base64 } = req.body;
if (!String(mime || "").startsWith("image/")) {
return res.status(422).json({ error: "image files only" });
}
const bytes = Buffer.from(data_base64, "base64");
if (bytes.length > 15 * 1024 * 1024) {
return res.status(413).json({ error: "image too large" });
}
return res.status(200).json({ ok: true, url: await saveImage(filename, bytes) });
}
// 5. Delete requests: body carries action = "delete".
if (req.body.action === "delete") {
const removed = await deletePostByUrl(req.body.url, req.body.slug);
if (!removed) return res.status(404).json({ deleted: true }); // already gone
return res.status(200).json({ deleted: true });
}
// 6. Update requests: action = "update". Optional fields may be absent.
if (req.body.action === "update") {
const updated = await updatePost(req.body.id, req.body.target_url, {
title: req.body.title,
html: req.body.content_html,
metaDescription: req.body.excerpt,
coverImage: req.body.featured_image_url, // optional
status: req.body.status, // optional: "publish" | "draft"
});
if (!updated) return res.status(404).json({ error: "post not found" });
return res.status(200).json({ url: updated.url });
}
// 7. New article: read the payload Orova sent.
const {
title, slug, content_html, excerpt,
featured_image_url, keyword, lang, published_at, status,
} = req.body;
// 8. Create the article in your own CMS or database.
const post = await createPost({
title,
slug,
html: content_html,
metaDescription: excerpt,
coverImage: featured_image_url,
keyword,
lang,
publishedAt: published_at,
draft: status === "draft", // status is optional; absent means publish now
});
// 9. Return 200/201 with the live URL.
return res.status(201).json({
url: "https://yoursite.com/" + post.slug,
});
});
app.listen(3000);/ Contrato — actualizar
Actualizar artículos (Optimizar)
El motor Optimizar de Orova reescribe artículos que ya están publicados — datos actualizados, mejores títulos, contenido fusionado — y luego sobrescribe la entrada existente en su sitio, sin cambiar la URL. La petición va al mismo endpoint, como un POST con el mismo Bearer token:
POST <your endpoint URL>
Headers:
Content-Type: application/json
Authorization: Bearer <secret>
User-Agent: Orova-SEO
Body (JSON):
{
"action": "update", # always the literal string "update"
"id": 123, # optional: post id, match on this first
"target_url": "...", # public URL of the post to overwrite (match next)
"title": "...", # new headline
"slug": "...", # slug fallback for matching, may be empty
"content_html": "...", # full new article body as HTML
"excerpt": "...", # new meta description
"featured_image_url": "...", # optional: new cover image
"status": "publish" # optional: "publish" or "draft"
}
# Optional fields are left out of the body when empty — never sent as null.Busca la entrada por target_url (si no aparece, prueba con slug), sustituye su título, su cuerpo y su meta descripción, deja la URL tal cual y responde 200 con { "url": "<la misma URL de la entrada>" }. Cualquier estado fuera de 2xx, o un cuerpo sin url, hace que Orova marque esa optimización como fallida (nunca crea una entrada duplicada). Los endpoints que aún no lo han implementado siguen publicando artículos nuevos sin cambios: solo necesitas añadirlo cuando uses la pantalla Optimizar.
Desde el 29 de agosto el cuerpo de actualización puede incluir id (búscalo antes que target_url), featured_image_url para cambiar la portada y status con "publish" o "draft". Los tres son opcionales; si están vacíos se omiten y nunca se envían como null.
/ Contrato — Visual Editor
Leer artículos (Visual Editor)
El Visual Editor abre artículos que ya están en tu sitio, así que primero necesita leerlos. Son dos acciones más sobre la misma URL del endpoint, con el mismo token Bearer: list devuelve tus artículos y get devuelve uno completo.
POST <your endpoint URL> # same endpoint, same Bearer secret
Body (JSON):
{
"action": "list", # always the literal string "list"
"limit": 500 # newest article first, 500 maximum
}
Response — HTTP 200:
{
"ok": true,
"posts": [
{
"id": 123, # your own post id, reused by "get" and "update"
"title": "...",
"slug": "...",
"link": "https://yoursite.com/the-article",
"date": "2026-08-29", # published date, YYYY-MM-DD
"modified": "2026-08-29",
"status": "publish", # "publish" or "draft"
"author": "..."
}
]
}POST <your endpoint URL>
Body (JSON):
{
"action": "get", # always the literal string "get"
"id": 123, # match on this first
"target_url": "...", # then the public URL
"slug": "..." # then the slug
}
Response — HTTP 200:
{
"ok": true,
"post": {
"id": 123,
"title": "...",
"slug": "...",
"link": "https://yoursite.com/the-article",
"content_html": "...", # full article body as HTML
"excerpt": "...",
"featured_image_url": "...",
"status": "publish" # "publish" or "draft"
}
}
No such post — HTTP 404:
{ "ok": false, "err": "not_found" }limit tiene un tope de 500 y el artículo más reciente va primero. En get, Orova envía id, target_url y slug a la vez: busca por el que reconozcas, en ese orden. Si no hay coincidencia, responde 404 con { "ok": false, "err": "not_found" }. Solo necesitas estas dos acciones si quieres editar los artículos del sitio en el Visual Editor; la publicación y la optimización automáticas funcionan sin ellas.
/ Contrato — Visual Editor
Subir imágenes (Visual Editor)
Cuando sueltas una imagen en el Visual Editor, Orova la envía a la misma URL del endpoint en base64 dentro del cuerpo JSON: sin multipart y sin otra URL que proteger.
POST <your endpoint URL>
Body (JSON):
{
"action": "upload_image", # always the literal string "upload_image"
"filename": "cover.png", # original file name, extension included
"mime": "image/png", # image/* only
"data_base64": "iVBORw0KGgo..." # file bytes, base64, 15 MB maximum
}
Response — HTTP 200 (or 201):
{
"ok": true,
"url": "https://yoursite.com/uploads/cover.png" # absolute URL
}Acepta solo image/* y rechaza cualquier archivo de más de 15 MB. Guarda el archivo y responde con la URL absoluta de la imagen: una ruta relativa rompe el artículo en cuanto se muestra en otro sitio. Esta acción solo hace falta para el Visual Editor.
/ Contrato — eliminar
Eliminar artículos
Cuando alguien elimina un artículo publicado dentro de Orova (desde la pantalla Redacción o Informes), Orova pide a tu sitio que también lo borre. La petición va al mismo endpoint, como un POST con el mismo Bearer token; lo único distinto es el cuerpo:
POST <your endpoint URL>
Headers:
Content-Type: application/json
Authorization: Bearer <secret>
User-Agent: Orova-SEO
Body (JSON):
{
"action": "delete", # always the literal string "delete"
"url": "...", # public URL of the article to remove (match on this first)
"slug": "...", # slug fallback, may be empty
"keyword": "..." # the article's target keyword, for your logs
}Busca la entrada por url (si no aparece, prueba con slug), elimínala o despublícala y responde 200 con { "deleted": true }; un 204 sin cuerpo también sirve. Si la entrada ya no existe, responde 404 y Orova lo toma como ya eliminada. Devolver { "deleted": false } le indica a Orova que la entrada no se eliminó. Las peticiones normales de publicación nunca traen el campo action, así que los endpoints existentes siguen funcionando igual: implementar el borrado es opcional, pero recomendable.
/ Seguridad
Notas de seguridad
- Verifica siempre el Bearer token. Tu endpoint es una URL pública: cualquiera puede llamarlo. La cabecera Authorization es lo único que demuestra que una petición viene de verdad de Orova, así que rechaza toda petición cuyo token no coincida exactamente con tu clave guardada.
- Sirve el endpoint por HTTPS. La clave viaja en una cabecera. HTTPS evita que se lea por el camino.
- Mantén la clave fuera de tu código. Guárdala en una variable de entorno o en un gestor de secretos, nunca escrita en un archivo que subas al repositorio. Si alguna vez se expone, genera una nueva desde Proyecto → Conexiones.
- Trata content_html como contenido, no como markup de confianza. Guárdalo y muéstralo con el mismo cuidado que cualquier otro cuerpo de artículo en tu CMS.
/ Para terminar
Ese es todo el contrato
Con el endpoint en marcha y registrado en tu proyecto, Orova publica en tu sitio de forma automática, igual que hace con quienes usan WordPress. Cada artículo que termina llega a tu servidor, se convierte en una entrada y su URL pública vuelve a Informes y Análisis para que sigas su rendimiento.
/ ¿Necesitas ayuda?
¿Se te atasca la conexión del endpoint? Abre la sección Soporte dentro de tu espacio de trabajo, o escríbenos un mensaje.
