/ 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.
| Campo | Tipo | Descrição |
|---|---|---|
| title | string | O título do artigo. |
| slug | string | URL-friendly identifier suggested for the post. |
| content_html | string | O corpo completo do artigo em HTML, pronto para publicar. |
| excerpt | string | Um resumo curto, usado como meta description. |
| featured_image_url | string | null | URL da imagem de capa, ou null quando não houver imagem. |
| keyword | string | A palavra-chave de SEO para a qual o artigo foi escrito. |
| lang | string | Language as an ISO code, e.g. "en" or "vi". |
| published_at | string | O momento em que o Orova enviou o artigo, em ISO 8601 no horário UTC, sem sufixo de fuso horário. |
| status | string | undefined | Opcional. "draft" mantém o artigo como rascunho; "publish", ou nenhum valor, publica na hora. |
| action | string | undefined | Ausente 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_url | string | Só com action = "update": o URL real do post a sobrescrever. Mantenha esse URL. |
| id | number | undefined | Only 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.
/ 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.
