OROVA.VN — BIZ AI AGENT

/ 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.

CampoTipoDescripción
titlestringEl titular del artículo.
slugstringIdentificador apto para URL que se sugiere para la entrada.
content_htmlstringEl cuerpo completo del artículo en HTML listo para publicar.
excerptstringUn resumen corto, pensado para la meta descripción.
featured_image_urlstring | nullURL de la imagen de portada, o null si no hay ninguna.
keywordstringLa palabra clave SEO objetivo para la que se escribió el artículo.
langstringIdioma como código ISO, por ejemplo "en" o "vi".
published_atstringEl momento en que Orova envió el artículo, en ISO 8601 y hora UTC, sin sufijo de zona horaria.
statusstring | undefinedOpcional. "draft" deja el artículo sin publicar; "publish", o ningún valor, lo publica.
actionstring | undefinedNo 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_urlstringSolo con action = "update": la URL pública de la entrada que hay que sobrescribir. No cambies esa URL.
idnumber | undefinedSolo 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.

← Volver a la biblioteca de guías

/ ¿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.