OROVA.VN — BIZ AI AGENT

/ Guia para desenvolvedores

Publicar via API em qualquer site.

Como criar seu próprio endpoint para que o Orova publique artigos em um site que não usa WordPress.

O Orova publica artigos no WordPress desde o início. Se o seu site não é WordPress — um CMS próprio, um sistema headless, um site estático ou um backend próprio — você ainda pode deixar o Orova publicar automaticamente, recebendo os artigos por uma pequena API. Você escreve um endpoint HTTP; toda vez que termina um artigo, o Orova chama esse endpoint. Esta página é para desenvolvedores e descreve o contrato completo da requisição.

/ Visão geral

Como funciona

Ao terminar um artigo para uma palavra-chave, o Orova envia uma única requisição HTTP POST para o endpoint URL que você cadastrou. O corpo da requisição é JSON e contém o artigo inteiro. Seu endpoint cria o post do seu lado e devolve o URL público final do artigo. O Orova salva esse URL como link publicado, igual faz com um post do WordPress. Use isto quando quiser publicar automaticamente em um site que não é WordPress, ou quando preferir controlar você mesmo como os posts são armazenados.

/ Configuração

Configuração — seis passos

  • 1

    Ative “Conectar via API” no seu projeto. Abra seu projeto (Projetos → seu projeto → Conexões). Ao lado do cartão WordPress há o cartão Conectar via API. Insira ali o URL do seu endpoint. O campo de chave secreta é opcional: deixe em branco e o Orova gera uma chave, ou cole uma chave que seu site já usa. Depois de salvar, a chave aparece no cartão com o botão Copiar. Cada projeto publica em um único destino — conectar via API bloqueia o botão do WordPress, e vice-versa.

  • 2

    Crie um endpoint que aceite POST. No seu site ou servidor, crie uma rota que escute HTTP POST com corpo JSON. Esse é o URL que você cola de volta na tela Conexões do projeto. O Orova chama esse endpoint uma vez para cada artigo terminado.

  • 3

    Verifique o Bearer token em toda requisição. Toda requisição do Orova traz um cabeçalho Authorization no formato “Bearer <secret>”, com exatamente a chave secreta mostrada no seu projeto. Compare com a cópia que você guardou e rejeite tudo o que não bater — é isso que impede estranhos de publicar no seu site.

  • 4

    Crie o artigo a partir do payload. Leia o corpo JSON e crie o post no seu CMS ou banco de dados: use title, slug e content_html para o artigo, excerpt como meta description, featured_image_url como imagem de capa, e keyword / lang / published_at como metadados.

  • 5

    Devolva o URL real do artigo. Responda com HTTP 200 ou 201 e um corpo JSON { "url": "https://yoursite.com/the-new-article" }. O Orova salva esse URL como link publicado do artigo. Se você devolver um status fora de 2xx, ou omitir o campo url, o Orova considera a publicação como falha.

  • 6

    Trate as atualizações do mecanismo Otimizar. Quando o corpo traz action = "update", encontre o post existente pelo target_url (ou slug) e sobrescreva title, content_html e excerpt no lugar — mantendo o URL. Responda 200 com { "url": ... } apontando para o mesmo post. Endpoints que ainda não tratam isso só fazem o Orova marcar a otimização como falha; a publicação de artigos novos continua funcionando como antes.

/ Contrato

A requisição que o Orova envia

Cada artigo terminado chega como uma requisição neste formato:

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.

/ Referência

Campos do payload

Campos de uma requisição de publicação (artigo novo). As requisições de exclusão e de atualização usam o mesmo endpoint com corpos menores — veja a seção de cada uma abaixo.

CampoTipoDescrição
titlestringO título do artigo.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringO corpo completo do artigo em HTML, pronto para publicar.
excerptstringUm resumo curto, usado como meta description.
featured_image_urlstring | nullURL da imagem de capa, ou null quando não houver imagem.
keywordstringA palavra-chave de SEO para a qual o artigo foi escrito.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringO momento em que o Orova enviou o artigo, em ISO 8601 no horário UTC, sem sufixo de fuso horário.
statusstring | undefinedOpcional. "draft" mantém o artigo como rascunho; "publish", ou nenhum valor, publica na hora.
actionstring | undefinedAusente em artigos novos. "delete" pede que você remova um post; "update" (enviado pelo mecanismo Otimizar) pede que você sobrescreva um post existente no lugar.
target_urlstringSó com action = "update": o URL real do post a sobrescrever. Mantenha esse URL.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Campos opcionais sem nada a enviar ficam totalmente fora do corpo — o Orova nunca envia null, então leia esses campos de forma defensiva. status também é opcional: "draft" cria o artigo como rascunho, enquanto "publish" ou nenhum status publica na hora.

/ Contrato

A resposta que você deve devolver

Depois de criar o post, responda com o status HTTP 200 ou 201 e este corpo JSON:

HTTP 200 (or 201)
Content-Type: application/json

{
  "url": "https://yoursite.com/published-article"
}

O Orova salva esse url como link publicado do artigo. Se seu endpoint devolver qualquer status fora de 2xx, ou um corpo que não seja um objeto JSON com o campo url, o Orova considera a publicação como falha e marca o artigo para você tentar de novo. Responda em até 60 segundos — depois disso o Orova encerra a requisição (vale para publicação, atualização e exclusão).

/ Exemplo

Código de exemplo

Um endpoint de recebimento mínimo em Node.js com Express. Os passos são os mesmos em qualquer linguagem ou framework: verificar o token, ramificar pela action (update / delete), criar ou alterar o post e devolver o URL.

// 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. Required: list your articles (duplicate check, internal links). action = "list".
  if (req.body.action === "list") {
    const posts = await listPosts(req.body.limit || 500); // newest first, 500 max
    // each post: { id, title, slug, link, date, modified, status, author, excerpt? }
    return res.status(200).json({ ok: true, posts });
  }

  // 3. Required: read one article in full (used when optimizing it). 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 — atualização

Atualizar artigos (Otimizar)

O mecanismo Otimizar do Orova reescreve artigos que já estão no ar — fatos atualizados, títulos melhores, conteúdo consolidado — e depois sobrescreve o post existente no lugar, mantendo o URL. A requisição vai para o mesmo endpoint URL, como POST com o mesmo 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.

Encontre o post pelo target_url (se não achar, use o slug), substitua título, corpo e meta description, mantenha o URL e responda 200 com { "url": "<same post URL>" }. Qualquer status fora de 2xx, ou um corpo sem url, faz o Orova marcar a otimização como falha (o Orova nunca cria post duplicado). Endpoints que ainda não implementaram isso continuam publicando artigos novos normalmente — só é preciso implementar quando você usar a tela Otimizar.

Desde 29/8, o corpo da atualização também pode trazer id (procure por ele antes do target_url), featured_image_url para trocar a imagem de capa e status com "publish" ou "draft". Os três são opcionais; sem valor, o Orova remove a chave do corpo e nunca envia null.

/ Contrato — Editor visual

Ler artigos (obrigatório)

O Orova precisa ler os artigos que já estão no seu site — para não escrever sobre o mesmo tema, inserir links internos e otimizar posts antigos. Adicione mais duas actions no mesmo endpoint URL, com o mesmo Bearer token: list devolve a lista de artigos, get devolve um artigo 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": "...",
        "excerpt": "..."         # optional: short summary, 300 characters max
      }
    ]
  }
  # Required: Orova reads this list to avoid duplicate topics,
  # add internal links and analyze the whole site.
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 tem no máximo 500, com o artigo mais recente primeiro. No get, o Orova envia id, target_url e slug juntos — procure pelo que você reconhecer, nessa ordem. Se não encontrar, responda 404 com { "ok": false, "err": "not_found" }. O excerpt no list é opcional (até 300 caracteres). Obrigatório: o Orova chama list para saber quais artigos já existem (evitar duplicados, inserir links internos e analisar o site inteiro) e chama get para ler o conteúdo ao otimizar um artigo. Se o seu site não responder a essas duas actions, os artigos novos ficam sem links internos e os antigos não podem ser otimizados.

/ Contrato — Editor visual

Enviar imagens (Editor visual)

Quando você solta uma imagem no Editor visual, o Orova a envia para o mesmo endpoint URL em base64 dentro do corpo JSON — sem multipart, sem outro URL para 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
  }

Aceite só image/* e rejeite arquivos acima de 15 MB. Salve o arquivo e responda com o URL absoluto da imagem: um caminho relativo quebra o artigo assim que ele aparece em outro lugar. Esta action só é necessária para o Editor visual.

/ Contrato — exclusão

Excluir artigos

Quando um usuário remove um artigo publicado dentro do Orova (na tela Escrever ou Relatórios), o Orova pede para o seu site excluí-lo também. A requisição vai para o mesmo endpoint URL, como POST com o mesmo Bearer token — só o corpo muda:

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
  }

Procure o post pelo url (se não achar, use o slug), exclua ou despublique e responda 200 com { "deleted": true } — um 204 vazio também serve. Se o post não existir mais, responda 404 e o Orova entende que já foi excluído. Devolver { "deleted": false } avisa o Orova de que o post não foi removido. Requisições normais de publicação nunca têm o campo action, então endpoints existentes continuam funcionando como antes — implementar a exclusão é opcional, mas recomendado.

/ Segurança

Observações de segurança

  • Sempre verifique o Bearer token. Seu endpoint é um URL público — qualquer um pode chamá-lo. O cabeçalho Authorization é a única prova de que a requisição veio mesmo do Orova, então rejeite toda requisição cujo token não seja exatamente igual à chave que você guardou.
  • Sirva o endpoint em HTTPS. A chave secreta viaja no cabeçalho. O HTTPS impede que ela seja lida no caminho.
  • Não deixe a chave no código. Guarde a chave em uma variável de ambiente ou em um gerenciador de segredos, nunca fixa em um arquivo que você faz commit. Se ela vazar, gere uma nova em Projeto → Conexões.
  • Trate content_html como conteúdo, não como markup confiável. Armazene e exiba esse conteúdo com o mesmo cuidado que qualquer corpo de artigo no seu CMS.

/ Para fechar

O contrato é só isso

Com o endpoint no ar e cadastrado no projeto, o Orova publica no seu site automaticamente — exatamente como faz para quem usa WordPress. Cada artigo terminado chega ao seu servidor, vira um post, e o URL público dele volta para Relatórios e Análise para você acompanhar o desempenho.

← Voltar à biblioteca de guias

/ Precisa de ajuda?

Não consegue conectar seu endpoint? Abra a seção Suporte no seu espaço de trabalho, ou envie uma mensagem para nós.