/ Guide pour développeurs
Publication via API sur n'importe quel site.
Comment créer votre propre point de terminaison pour qu'Orova publie les articles terminés sur un site qui ne tourne pas sous WordPress.
Orova publie sur WordPress dès le départ. Si votre site n'est pas WordPress — CMS maison, architecture headless, site statique ou backend à vous — vous pouvez quand même laisser Orova publier automatiquement, en recevant les articles via une petite API. Vous écrivez un point de terminaison HTTP ; Orova l'appelle chaque fois qu'un article est terminé. Cette page s'adresse à une personne développeuse et décrit tout le contrat de requête.
/ Vue d'ensemble
Comment ça marche
Quand Orova termine un article pour un mot-clé, il envoie une seule requête HTTP POST vers l'URL du point de terminaison que vous avez enregistrée. Le corps de la requête est en JSON et contient l'article entier. Votre point de terminaison crée l'article de votre côté, puis renvoie son URL publique définitive. Orova enregistre cette URL comme lien publié, exactement comme pour un article WordPress. Utilisez cette méthode si vous voulez une publication sans intervention sur un site qui n'est pas WordPress, ou si vous préférez garder la main sur la façon dont les articles sont stockés.
/ Mise en place
Mise en place — six étapes
- 1
Activez « Connexion via API » dans votre projet. Ouvrez votre projet (Projets → votre projet → Connexions). À côté de la carte WordPress se trouve une carte Connexion via API. Saisissez-y l'URL de votre point de terminaison. Le champ de clé secrète est facultatif : laissez-le vide et Orova en génère une, ou collez la clé que votre site utilise déjà. Après enregistrement, la clé s'affiche sur la carte avec un bouton Copier. Un projet ne publie que vers une seule destination : une connexion via API verrouille le bouton WordPress, et inversement.
- 2
Créez un point de terminaison qui accepte POST. Sur votre site ou votre serveur, créez une route qui écoute les requêtes HTTP POST avec un corps JSON. C'est l'URL que vous collez dans l'écran Connexions du projet. Orova l'appelle une fois pour chaque article terminé.
- 3
Vérifiez le jeton Bearer à chaque requête. Chaque requête d'Orova porte un en-tête Authorization de la forme « Bearer <secret> », avec exactement la clé secrète affichée dans votre projet. Comparez-la à votre copie enregistrée et rejetez tout ce qui ne correspond pas : c'est la seule chose qui empêche quelqu'un d'autre de publier sur votre site.
- 4
Créez l'article à partir du payload. Lisez le corps JSON et créez un article dans votre CMS ou votre base de données : utilisez title, slug et content_html pour l'article lui-même, excerpt comme méta-description, featured_image_url comme image de couverture, et keyword / lang / published_at comme métadonnées.
- 5
Renvoyez l'URL réelle de l'article. Répondez avec un statut HTTP 200 ou 201 et un corps JSON { "url": "https://votresite.com/le-nouvel-article" }. Orova enregistre cette URL comme lien publié de l'article. Si vous renvoyez un statut hors 2xx, ou si le champ url manque, Orova considère la publication comme échouée.
- 6
Traitez les requêtes de mise à jour du moteur Optimiser. Quand le corps contient action = "update", retrouvez l'article existant par target_url (ou slug) et écrasez sur place ses title, content_html et excerpt — sans changer l'URL. Répondez 200 avec { "url": ... } pointant vers ce même article. Les points de terminaison qui ne gèrent pas encore ce cas feront simplement qu'Orova signale l'optimisation comme échouée ; la publication de nouveaux articles continue comme avant.
/ Contrat
La requête envoyée par Orova
Chaque article terminé arrive sous la forme d'une requête comme celle-ci :
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./ Référence
Champs du payload
Champs d'une requête de publication (nouvel article). Les requêtes de suppression et de mise à jour réutilisent le même point de terminaison, avec les corps plus courts présentés dans leurs sections respectives plus bas.
| Champ | Type | Description |
|---|---|---|
| title | string | Le titre de l'article. |
| slug | string | Identifiant adapté aux URL, proposé pour l'article. |
| content_html | string | Le corps complet de l'article, en HTML prêt à publier. |
| excerpt | string | Un résumé court, prévu pour la méta-description. |
| featured_image_url | string | null | URL de l'image de couverture, ou null s'il n'y en a pas. |
| keyword | string | Le mot-clé SEO visé par l'article. |
| lang | string | Langue sous forme de code ISO, par exemple "en" ou "vi". |
| published_at | string | Le moment où Orova a envoyé l'article, au format ISO 8601 en UTC, sans suffixe de fuseau horaire. |
| status | string | undefined | Optionnel. "draft" laisse l’article non publié ; "publish", ou aucune valeur, le publie. |
| action | string | undefined | Absent pour les nouveaux articles. "delete" vous demande de supprimer un article ; "update" (envoyé par le moteur Optimiser) vous demande d'écraser sur place un article existant. |
| target_url | string | Uniquement avec action = "update" : l'URL publique de l'article à écraser. Gardez cette URL inchangée. |
| id | number | undefined | Envoyé seulement quand Orova connaît l’identifiant de votre article (via `list` ou `get`) : cherchez avec lui avant `target_url`. |
Les champs optionnels disparaissent du corps quand il n’y a rien à envoyer : Orova n’envoie jamais null, lisez-les donc de façon défensive. status est optionnel lui aussi : "draft" crée l’article en brouillon, "publish" — ou aucun status — le publie tout de suite.
/ Contrat
La réponse que vous devez renvoyer
Une fois l'article créé, répondez avec un statut HTTP 200 ou 201 et ce corps JSON :
HTTP 200 (or 201)
Content-Type: application/json
{
"url": "https://yoursite.com/published-article"
}Orova enregistre ce url comme lien publié de l'article. Si votre point de terminaison renvoie un statut hors 2xx, ou un corps qui n'est pas un objet JSON avec un champ url, Orova considère la publication comme échouée et marque l'article pour que vous puissiez réessayer. Répondez en moins de 60 secondes : au-delà, Orova coupe la requête (cela vaut pour la publication, la mise à jour et la suppression).
/ Exemple
Exemple de code
Un point de terminaison de réception minimal en Node.js avec Express. Les mêmes étapes — vérifier le jeton, brancher selon action (update / delete), créer ou modifier l'article, renvoyer l'URL — valent dans n'importe quel langage ou 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);/ Contrat — mise à jour
Mettre à jour des articles (Optimiser)
Le moteur Optimiser d'Orova réécrit des articles déjà en ligne — faits actualisés, meilleurs titres, contenus fusionnés — puis écrase l'article existant sur place, en gardant l'URL inchangée. La requête part vers le même point de terminaison, en POST, avec le même jeton Bearer :
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.Retrouvez l'article par target_url (à défaut, par slug), remplacez son titre, son corps et sa méta-description, gardez l'URL telle quelle, puis répondez 200 avec { "url": "<la même URL d'article>" }. Tout statut hors 2xx, ou un corps sans url, fait qu'Orova marque cette optimisation comme échouée (il ne crée jamais d'article en double). Les points de terminaison qui ne l'ont pas encore implémenté continuent de publier les nouveaux articles sans changement : il n'y a besoin de gérer la mise à jour que si vous utilisez l'écran Optimiser.
Depuis le 29 août, le corps d’une mise à jour peut aussi porter id (à utiliser avant target_url), featured_image_url pour changer l’image de couverture, et status valant "publish" ou "draft". Les trois sont optionnels ; vides, ils sont retirés du corps et jamais envoyés en null.
/ Contrat — Visual Editor
Lire les articles (Visual Editor)
L’éditeur visuel ouvre les articles déjà en ligne sur votre site : il doit donc d’abord les lire. Deux actions de plus sur la même URL d’endpoint, avec le même jeton Bearer : list renvoie vos articles, get en renvoie un en entier.
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 est plafonné à 500 et l’article le plus récent arrive en premier. Pour get, Orova envoie id, target_url et slug ensemble : cherchez avec celui que vous reconnaissez, dans cet ordre. Sans correspondance, répondez 404 avec { "ok": false, "err": "not_found" }. Ces deux actions ne servent que si vous voulez modifier les articles du site dans l’éditeur visuel — la publication et l’optimisation automatiques fonctionnent sans elles.
/ Contrat — Visual Editor
Envoyer des images (Visual Editor)
Quand vous déposez une image dans l’éditeur visuel, Orova l’envoie à la même URL d’endpoint, en base64 dans le corps JSON : pas de multipart, pas de seconde URL à protéger.
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
}N’acceptez que image/* et refusez tout fichier de plus de 15 Mo. Enregistrez le fichier, puis répondez avec l’URL absolue de l’image : un chemin relatif casse l’article dès qu’il s’affiche ailleurs. Cette action ne sert qu’à l’éditeur visuel.
/ Contrat — suppression
Supprimer des articles
Quand quelqu'un supprime un article publié depuis Orova (écran Rédaction ou Rapports), Orova demande à votre site de le supprimer aussi. La requête part vers le même point de terminaison, en POST, avec le même jeton Bearer — seul le corps change :
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
}Retrouvez l'article par url (à défaut, par slug), supprimez-le ou dépubliez-le, puis répondez 200 avec { "deleted": true } — un simple 204 fonctionne aussi. Si l'article n'existe plus, répondez 404 et Orova le considère comme déjà supprimé. Renvoyer { "deleted": false } indique à Orova que l'article n'a pas été supprimé. Les requêtes de publication classiques ne contiennent jamais de champ action, donc les points de terminaison existants continuent de fonctionner tels quels : gérer la suppression est facultatif, mais recommandé.
/ Sécurité
Notes de sécurité
- Vérifiez toujours le jeton Bearer. Votre point de terminaison est une URL publique : n'importe qui peut l'appeler. L'en-tête Authorization est la seule chose qui prouve qu'une requête vient bien d'Orova ; rejetez donc toute requête dont le jeton ne correspond pas exactement à votre clé enregistrée.
- Servez le point de terminaison en HTTPS. La clé circule dans un en-tête. HTTPS empêche qu'elle soit lue en transit.
- Gardez la clé hors de votre code. Stockez-la dans une variable d'environnement ou un gestionnaire de secrets, jamais en dur dans un fichier que vous commitez. Si elle est exposée un jour, générez-en une nouvelle depuis Projet → Connexions.
- Traitez content_html comme du contenu, pas comme du markup de confiance. Stockez-le et affichez-le avec le même soin que n'importe quel corps d'article dans votre CMS.
/ Pour conclure
Voilà tout le contrat
Une fois le point de terminaison en ligne et enregistré dans votre projet, Orova publie automatiquement sur votre site — exactement comme pour les utilisateurs WordPress. Chaque article terminé arrive sur votre serveur, devient un article publié, et son URL publique remonte dans Rapports et Analyse pour que vous suiviez ses performances.
/ Besoin d'un coup de main ?
Bloqué sur le branchement de votre point de terminaison ? Ouvrez la section Assistance dans votre espace de travail, ou envoyez-nous un message.
