50 % zľava na Váš prvý mesiac s kódom LAUNCH50 Len mesačné plány. Začať teraz

Začať bezplatnú skúšobnú verziu

Stiahnite si svoje články.
Zverejnite ich po svojom.

Každý článok, ktorý pre Vás napíšeme, možno čítať cez HTTP: nadpis, metadáta, Markdown, HTML, hlavný obrázok aj FAQ. Pri spustení buildu ich vezmite do svojho webu v Next.js, Astro alebo vlastného webu, zverejnite ich a oznámte nám adresu, na ktorej každý z nich skončil.

Jeden bearer token. JSON vstup, JSON výstup. Žiadne SDK na inštaláciu a nič na nastavovanie okrem kľúča.

Webhook odosiela. API Vám umožňuje načítavať.

Nesú ten istý článok v rovnakom formáte, takže parser napísaný pre jedno prečíta aj druhé bez zmien. Rozdiel je v tom, kto začína komunikáciu.

Webhook, keď Váš web môže prijať príspevok kedykoľvek

Váš endpoint zavoláme v momente, keď je článok pripravený, a znova, keď sa zmení. Netreba nič kontrolovať ani plánovať. WordPress, Shopify, Ghost, spúšťač v Zapieri alebo trasa, ktorú ste si napísali sami.

API, keď sa Váš web zostavuje a nasadzuje ako celok

Statický web nemôže prijať príspevok o pol siedmej ráno, najprv sa musí niečo znovu zostaviť. Váš build sa preto spýta, čo čaká, prevezme to a potom nám oznámi adresu. Vy určíte, kedy.

Oboje, ak ich chcete

Sú to samostatné prepojenia a ani jedno nevylučuje druhé. Webhook, ktorý napája newsletter, a API, ktoré napája web, je bežné usporiadanie.

Jeden kľúč, odoslaný ako bearer token.

Vytvorte kľúč v prehľade v časti Settings, potom Connections. Zobrazí sa iba raz, pri vytvorení, pretože sa ukladá len jeho hash: ak ho stratíte, zrušte ho a vytvorte ďalší. Kľúč číta každý web v účte a ruší sa na tej istej obrazovke.

Každá požiadavka

Authorization: Bearer sns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

Skontrolujte, že to funguje

curl https://seonis.ai/api/v1/me \
  -H "Authorization: Bearer $SEONIS_API_KEY"
{
  "organisation": { "id": 12, "name": "Peppervale", "country": "GB" },
  "key": {
    "name": "Production build",
    "prefix": "sns_live_ab",
    "created_at": "2026-09-06T09:12:44+00:00",
    "last_used_at": "2026-09-06T09:31:02+00:00"
  },
  "sites": 2
}

Limit požiadaviek

120 požiadaviek za minútu, počítaných podľa kľúča, nie podľa adresy, z ktorej prichádzajú, pretože build runnery zdieľajú adresy. Nad tento limit dostanete 429 so štandardnými hlavičkami Retry-After a X-RateLimit. Na stiahnutie článkov pre build stačí niekoľko požiadaviek, takže na tento limit by ste nemali naraziť omylom.

Nedávajte kľúč do svojho repozitára

Číta všetko, čo sme napísali pre Váš účet, vrátane článkov, ktoré ešte nie sú zverejnené. Dajte ho do svojho build prostredia, nie do zdrojového kódu. Zrušte ho tu hneď, ako sa objaví niekde, kde nemá byť, a všetko, čo ho používa, okamžite dostane 401.

Je ich päť a štyri sú len na čítanie.

Všetko je pod /api/v1/. Verzia je v ceste od prvého dňa, takže raz môže existovať v2 bez toho, aby pokazila to, čo napíšete dnes.

GET /api/v1/me

Účet, ku ktorému kľúč patrí, a vlastný názov a prefix kľúča. Užitočné najmä na jednu vec: aby ste vedeli, že kľúč v tomto prostredí je ten, za ktorý ho považujete.

Príklad

curl https://seonis.ai/api/v1/me \
  -H "Authorization: Bearer $SEONIS_API_KEY"
GET /api/v1/sites

Každý web v účte. id je to, podľa čoho sa filtruje zoznam článkov, a doména je tam preto, aby build skript mohol porovnávať podľa niečoho, čo už pozná, namiesto toho, aby niesol id.

Príklad

curl https://seonis.ai/api/v1/sites \
  -H "Authorization: Bearer $SEONIS_API_KEY"
{
  "sites": [
    {
      "id": 5,
      "name": "Peppervale",
      "domain": "peppervale.co.uk",
      "url": "https://peppervale.co.uk",
      "language": "en",
      "country": "GB",
      "market": "en-GB",
      "status": "active",
      "articles_published": 34,
      "created_at": "2026-06-02T11:04:19+00:00"
    }
  ]
}
GET /api/v1/articles

Stránka článkov, najnovšia zmena ako prvá, bez obsahu. Zoradené podľa toho, kedy sa ktorý článok naposledy zmenil, nie podľa toho, kedy bol napísaný, a práve preto má updated_since zmysel: článok, ktorý už je zverejnený, možno neskôr upraviť, keď sa doň vloží odkaz alebo sa z neho odstráni.

Parametre dotazu

Názov Čo robí
site_id Jeden web, z /sites. id, ktoré nie je vo Vašom účte, vráti 404.
status Jedna z hodnôt draft, qa, needs_repair, review, ready, published, failed. Čokoľvek iné vráti 422 namiesto prázdnej stránky. "ready" je to, čo build potrebuje: napísané, skontrolované a ešte nikde nezverejnené.
updated_since ISO 8601, napríklad 2026-09-06T00:00:00Z. Zapamätajte si časovú značku posledného spustenia a pri ďalšom ju odošlite späť.
per_page Najviac 100. Predvolená hodnota je 25.
page Od 1. meta.has_more určuje, či sa má žiadať ďalšia.

Príklad

curl -G https://seonis.ai/api/v1/articles \
  -H "Authorization: Bearer $SEONIS_API_KEY" \
  -d site_id=5 \
  -d status=ready \
  -d updated_since=2026-09-01T00:00:00Z \
  -d per_page=50
{
  "articles": [
    {
      "id": 918,
      "site_id": 5,
      "language": "en",
      "status": "ready",
      "title": "How to choose a pepper grinder",
      "slug": "how-to-choose-a-pepper-grinder",
      "meta_title": "How to choose a pepper grinder | Peppervale",
      "meta_description": "Burr or blade, ceramic or steel: what matters in a grinder.",
      "meta_keywords": ["pepper grinder", "burr grinder"],
      "tags": ["Kitchen"],
      "excerpt": "A burr grinder gives an even grind; a blade one does not.",
      "key_takeaways": ["A burr grinder gives an even grind; a blade one does not."],
      "faq": [{ "question": "Ceramic or steel?", "answer": "Steel for pepper, ceramic for salt." }],
      "hero_image": { "url": "https://seonis.ai/storage/images/5/918.jpg", "alt": "A pepper grinder" },
      "infographic_url": null,
      "word_count": 1812,
      "published_url": null,
      "published_at": null,
      "updated_at": "2026-09-06T06:31:12+00:00",
      "created_at": "2026-09-05T02:14:55+00:00",
      "quality": { "seo_score": 88, "language_score": 96, "language_findings": [] }
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1, "has_more": false }
}
GET /api/v1/articles/{id}

Jeden článok so všetkým, čo zoznam vynecháva: content_markdown, content_html, FAQ ako schema.org JSON-LD pripravené na vloženie do stránky a všetky výmenné odkazy, ktoré článok obsahuje.

Názvy polí sú zámerne rovnaké ako tie, ktoré posiela náš webhook. Ak už máte prijímač webhookov, ten istý parser prečíta aj toto.

Príklad

curl https://seonis.ai/api/v1/articles/918 \
  -H "Authorization: Bearer $SEONIS_API_KEY"
{
  "article": {
    "id": 918,
    "site_id": 5,
    "language": "en",
    "status": "ready",
    "title": "How to choose a pepper grinder",
    "slug": "how-to-choose-a-pepper-grinder",
    "meta_description": "Burr or blade, ceramic or steel: what matters in a grinder.",
    "content_markdown": "Grinders differ.\n\n## Burr or blade\n\nA **burr** grinder ...",
    "content_html": "<p>Grinders differ.</p>\n<h2>Burr or blade</h2> ...",
    "faq_schema": { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [] },
    "hero_image": { "url": "https://seonis.ai/storage/images/5/918.jpg", "alt": "A pepper grinder" },
    "word_count": 1812,
    "published_url": null,
    "updated_at": "2026-09-06T06:31:12+00:00",
    "quality": { "seo_score": 88, "language_score": 96, "language_findings": [] },
    "exchange_links": []
  }
}
POST /api/v1/articles/{id}/published

Tu článok zverejníte; tu nám oznámite adresu. Je to jediný endpoint, ktorý niečo mení.

Urobí presne to isté ako doručenie, ktoré vykonáme sami: článok sa označí ako zverejnený, započíta sa do Vášho mesačného limitu, položka plánu sa uzavrie, všetky výmenné odkazy v ňom dostanú adresu, na ktorú čakal overovač, a Vaša Facebook Page dostane oznámenie, ak ste ju pripojili. Bez tohto volania zostane článok navždy pripravený a nič z toho sa nestane.

Príklad

curl -X POST https://seonis.ai/api/v1/articles/918/published \
  -H "Authorization: Bearer $SEONIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://peppervale.co.uk/blog/how-to-choose-a-pepper-grinder"}'

Odpovede

200 Zaznamenané. Článok sa vráti s novým stavom, published_at a published_url.
422 Chýba url alebo to nie je úplná adresa http:// alebo https://.
409 Článok nie je pripravený na publikovanie alebo je už evidovaný ako publikovaný. Článok sa vráti spolu s odmietnutím, takže vidíte, čo z toho platí.

Pripojte "REST API" ako svoj publikačný mechanizmus.

V časti Settings, potom Publishing, je modul s názvom "REST API: Vy načítate a publikujete". Nepýta si nič, pretože nemáme čo odosielať. Mení to to, čo sa deje každé ráno: hotový článok zostane pripravený bez adresy a obrazovka publikovania uvádza, že čaká, kým ho načítate, namiesto zobrazenia neúspešného doručenia.

API môžete používať aj bez jeho pripojenia: endpointy fungujú pre každý účet s kľúčom. Pripojenie je spôsob, ako zvyšok produktu zistí, že už nemá očakávať vlastnú adresu, a zároveň zabráni tomu, aby sa článok, ktorý publikujete sami, počítal ako zlyhanie.

Každé zlyhanie je JSON so správou.

Všade rovnaký formát, či odmietnutie prišlo z kontroly kľúča alebo z validátora, takže netreba spracúvať dva.

{ "message": "That API key is not valid, or it has been revoked." }
Stav Čo to znamená
401 Žiadny kľúč, kľúč, ktorý nie je náš, alebo kľúč, ktorý bol zrušený.
404 V tomto účte taký článok ani web neexistuje. Článok iného účtu vracia 404 namiesto 403: API, ktoré povie "to nie je Vaše", potvrdilo, že tá vec existuje.
409 Požiadavka bola v poriadku, ale článok nie je v stave, ktorý to umožňuje.
422 Parameter chýba alebo je nesprávny. Odpoveď obsahuje objekt errors s názvom poľa aj správou.
429 Prekročený limit požiadaviek. Retry-After určuje, ako dlho čakať.

Čo krok zostavenia skutočne robí.

#!/usr/bin/env bash
set -euo pipefail

# 1. What is finished and not yet on the site?
ready=$(curl -sG https://seonis.ai/api/v1/articles \
  -H "Authorization: Bearer $SEONIS_API_KEY" \
  -d site_id=5 -d status=ready -d per_page=100)

for id in $(echo "$ready" | jq -r '.articles[].id'); do
  # 2. Take the article, body and all.
  curl -s https://seonis.ai/api/v1/articles/$id \
    -H "Authorization: Bearer $SEONIS_API_KEY" \
    | jq -r '.article.content_markdown' > "content/posts/$id.md"

  # 3. Your build puts it live, and you know where it landed.
  slug=$(echo "$ready" | jq -r ".articles[] | select(.id==$id) | .slug")

  curl -s -X POST https://seonis.ai/api/v1/articles/$id/published \
    -H "Authorization: Bearer $SEONIS_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"url\": \"https://peppervale.co.uk/blog/$slug\"}"
done

Overte, že doručenie prišlo od nás.

Každé webhook doručenie v našom vlastnom formáte nesie tri hlavičky zo špecifikácie Standard Webhooks. Overte ich podpisovacím kľúčom z nastavení Publishing a odmietnite všetko, čo sa nezhoduje alebo je staršie ako päť minút.

webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
Hlavička Čo to je
webhook-id Doručenie, rovnaká hodnota ako delivery_id v tele. Zapamätajte si tie, ktoré ste už spracovali, a na opakovanie odpovedzte 200 bez toho, aby ste prácu robili znova.
webhook-timestamp Čas, keď sme ho podpísali, v celých sekundách od roku 1970. Odmietnite všetko, čo sa od Vašich hodín líši o viac ako päť minút, aby Vám staré doručenie nebolo možné poslať znova.
webhook-signature v1, potom base64 HMAC-SHA256 z id, časovej pečiatky a nespracovaného tela spojených bodkami, vytvorený Vaším podpisovacím kľúčom. Môže ich byť viac, oddelených medzerami. Prijmite doručenie, ak sa zhoduje ktorýkoľvek z nich.

Podpisovací kľúč

Začína sa na whsec_. To odstráňte a zvyšok dekódujte z base64: tieto bajty sú kľúč HMAC, nie text, ktorý ste skopírovali. Zobrazí sa raz, keď pripojíte webhook alebo stlačíte "New signing key" na stránke Publishing, a nikdy ho neposielame.

Skontrolujte nespracované telo

Vypočítajte podpis z bajtov presne tak, ako prišli, skôr než ich čokoľvek spracuje. JSON, ktorý bol dekódovaný a znovu zakódovaný, nie sú tie isté bajty a jeho podpis sa nebude zhodovať.

PHP

<?php
$raw = getenv('SEONIS_SIGNING_KEY'); // whsec_...

// No key, no deliveries. A missing or empty variable would otherwise become
// an empty HMAC key, and anybody can sign with that.
$secret = is_string($raw) && str_starts_with($raw, 'whsec_')
    ? base64_decode(substr($raw, strlen('whsec_')), true)
    : false;

if ($secret === false || strlen($secret) < 24) {
    http_response_code(500);
    exit('Signing key not configured.');
}

$id        = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$timestamp = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$header    = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';
$body      = file_get_contents('php://input');

if (! ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('Too old.');
}

$expected = base64_encode(hash_hmac('sha256', "$id.$timestamp.$body", $secret, true));

$valid = false;
foreach (explode(' ', $header) as $signature) {
    [$version, $value] = array_pad(explode(',', $signature, 2), 2, '');
    $valid = $valid || ($version === 'v1' && hash_equals($expected, $value));
}

if (! $valid) {
    http_response_code(401);
    exit('Bad signature.');
}

$delivery = json_decode($body, true);

Node.js

import crypto from 'node:crypto';
import express from 'express';

const app = express();

// No key, no server. An empty variable would otherwise become an empty HMAC
// key, and anybody can sign with that.
const raw = process.env.SEONIS_SIGNING_KEY ?? '';
if (!raw.startsWith('whsec_')) throw new Error('SEONIS_SIGNING_KEY is not set.');
const key = Buffer.from(raw.slice('whsec_'.length), 'base64');
if (key.length < 24) throw new Error('SEONIS_SIGNING_KEY is not a valid key.');

// express.raw keeps the body as the bytes we signed.
app.post('/hooks/seonis', express.raw({ type: 'application/json' }), (req, res) => {
  const id = req.get('webhook-id') ?? '';
  const timestamp = req.get('webhook-timestamp') ?? '';
  const header = req.get('webhook-signature') ?? '';

  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(401).send('Too old.');
  }

  const expected = crypto.createHmac('sha256', key)
    .update(`${id}.${timestamp}.`)
    .update(req.body)
    .digest();

  const valid = header.split(' ').some((signature) => {
    const [version, value = ''] = signature.split(',');
    const given = Buffer.from(value, 'base64');
    return version === 'v1' && given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });

  if (!valid) return res.status(401).send('Bad signature.');

  const delivery = JSON.parse(req.body);
  res.json({ ok: true });
});

Alebo použite knižnicu

Oficiálne knižnice Standard Webhooks pre PHP, JavaScript, Python, Go, Ruby a ďalšie toto všetko urobia jedným volaním. Dajte im podpisovací kľúč presne tak, ako sa Vám zobrazil, vrátane whsec_. github.com/standard-webhooks

Staršie hlavičky

Doručenia nesú aj podpisovací tajný kľúč ako bearer token a X-AutoSEO-LT-Signature, teda HMAC len tela vytvorený týmto tajným kľúčom. Stále sa posielajú, aby endpointy, ktoré sú na nich postavené, fungovali ďalej. Nový endpoint by mal namiesto toho overovať webhook-signature: starý podpis nepokrýva čas ani id a jeho kľúč sa posiela v každej požiadavke.

Formáty AutoSEO, Outrank a BabyLoveGrowth posielajú presne to, čo poslali tieto služby, takže tieto hlavičky nenesú.

Vytvorte kľúč a vyskúšajte to.

Skúšobná verzia stačí na čítanie API: články vytvorené počas nej sú skutočné články a vracajú sa cez tieto endpointy ako všetky ostatné.

Začať bezplatnú skúšobnú verziu