Pirmą mėnesį gausite 50% nuolaidą su kodu LAUNCH50 Tik mėnesiniams planams. Pradėti dabar

Išbandykite nemokamai

Paimkite savo straipsnius.
Paskelbkite juos savo būdu.

Kiekvieną straipsnį, kurį jums parašome, galima nuskaityti per HTTP: pavadinimą, metaduomenis, Markdown, HTML, pagrindinį vaizdą ir DUK. Kai vyksta jūsų kompiliavimas, paimkite juos į savo Next.js, Astro ar pasirinktinę svetainę, paskelbkite ir praneškite mums adresą, kuriuo kiekvienas atsidūrė.

Vienas bearer token. JSON į vidų, JSON į išorę. Nereikia diegti jokio SDK ir nieko konfigūruoti, išskyrus raktą.

Webhook siunčia. API leidžia jums pasiimti.

Jos perduoda tą patį straipsnį tuo pačiu formatu, todėl vienai parašytas analizatorius kitą nuskaito be pakeitimų. Skirtumas tas, kas pradeda ryšį.

Webhook, kai jūsų svetainė gali priimti įrašą bet kuriuo metu

Jūsų galinį tašką iškviečiame tą akimirką, kai straipsnis paruoštas, ir dar kartą, kai jis pasikeičia. Nereikia nieko tikrinti, nereikia nieko planuoti. WordPress, Shopify, Ghost, Zapier trigeris arba maršrutas, kurį parašėte patys.

API, kai jūsų svetainė kuriama ir diegiama kaip vienetas

Statinė svetainė negali priimti įrašo pusę septynių ryto, pirmiausia kažkas turi būti perkompiliuota. Todėl jūsų kompiliavimas paklausia, kas laukia, tai paima ir po to praneša mums adresą. Jūs pasirenkate kada.

Abu, jei jų norite

Tai atskiros jungtys ir nė viena nepaneigia kitos. Webhook, kuris pildo naujienlaiškį, ir API, kuris pildo svetainę, yra įprastas derinys.

Vienas raktas, siunčiamas kaip bearer token.

Sukurkite raktą valdymo skydelyje, skiltyje Settings, tada Connections. Jis parodomas vieną kartą, kai sukuriamas, nes saugoma tik jo maiša: jei jį prarasite, atšaukite jį ir sukurkite kitą. Raktas skaito visas paskyros svetaines ir atšaukiamas tame pačiame ekrane.

Kiekviena užklausa

Authorization: Bearer sns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

Patikrinkite, ar tai veikia

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
}

Užklausų limitas

120 užklausų per minutę, skaičiuojama pagal raktą, o ne pagal adresą, iš kurio ji ateina, nes kompiliavimo vykdyklės dalijasi adresais. Viršijus gausite 429 su standartinėmis Retry-After ir X-RateLimit antraštėmis. Straipsnių paėmimas kompiliavimui yra vos kelios užklausos, todėl netyčia šios ribos neturėtumėte pasiekti.

Nelaikykite rakto savo saugykloje

Jis skaito viską, ką parašėme jūsų paskyrai, įskaitant dar nepaskelbtus straipsnius. Laikykite jį savo kompiliavimo aplinkoje, ne išeities kode. Atšaukite jį čia tą pačią akimirką, kai jis atsiduria ten, kur neturėtų būti, ir viskas, kas jį naudoja, iškart gaus 401.

Jų yra penki, o keturi tik skaito.

Viskas yra po /api/v1/. Versija kelyje yra nuo pirmos dienos, todėl vieną dieną gali atsirasti v2 nesugadindama to, ką rašote šiandien.

GET /api/v1/me

Paskyra, kuriai priklauso raktas, ir paties rakto pavadinimas bei prefiksas. Tai ypač naudinga vienam dalykui: parodyti jums, kad šioje aplinkoje esantis raktas yra tas, kurį manote esant.

Pavyzdys

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

Kiekviena paskyros svetainė. id naudojamas straipsnių sąrašo filtravimui, o domenas pateikiamas tam, kad kompiliavimo scenarijus galėtų sieti pagal tai, ką jau žino, užuot perdavęs id.

Pavyzdys

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

Straipsnių puslapis, pirmiausia naujausi pakeitimai, be teksto. Rikiuojama pagal tai, kada kiekvienas straipsnis paskutinį kartą buvo pakeistas, o ne kada parašytas, todėl updated_since yra naudingas: jau paskelbtas straipsnis vėliau gali būti redaguojamas, kai į jį įterpiama nuoroda arba ji vėl pašalinama.

Užklausos parametrai

Pavadinimas Ką tai daro
site_id Viena svetainė iš /sites. id, kurio nėra jūsų paskyroje, grąžina 404.
status Vienas iš draft, qa, needs_repair, review, ready, published, failed. Bet kas kita grąžina 422, o ne tuščią puslapį. "ready" yra tai, ko reikia kompiliavimui: parašyta, patikrinta ir dar niekur nepaskelbta.
updated_since ISO 8601, pavyzdžiui, 2026-09-06T00:00:00Z. Įsiminkite paskutinio vykdymo laiko žymą ir perduokite ją kitą kartą.
per_page Iki 100. Numatytoji reikšmė yra 25.
page Nuo 1. meta.has_more nurodo, ar prašyti kito.

Pavyzdys

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}

Vienas straipsnis su viskuo, ko nėra sąraše: content_markdown, content_html, DUK kaip schema.org JSON-LD, paruoštas įdėti į puslapį, ir visos straipsnyje esančios mainų nuorodos.

Laukų pavadinimai yra tokie, kokius mūsų webhook siunčia tyčia. Jei jau turite webhook gavėją, tas pats analizatorius perskaitys ir tai.

Pavyzdys

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

Jūs paskelbiate straipsnį; čia nurodote mums adresą. Tai vienintelis galinis taškas, kuris ką nors pakeičia.

Tai daro tiksliai tą patį, ką ir mūsų pačių atliktas pristatymas: straipsnis pažymimas kaip paskelbtas, jis įskaičiuojamas į jūsų mėnesio limitą, plano elementas uždaromas, visos jame esančios mainų nuorodos gauna adresą, kurio laukė tikrintuvas, ir jūsų Facebook Page apie tai informuojamas, jei jį esate prijungę. Be šio iškvietimo straipsnis lieka paruoštas visam laikui ir nieko iš to neįvyksta.

Pavyzdys

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"}'

Atsakymai

200 Užregistruota. Straipsnis grąžinamas su nauja būsena, published_at ir published_url.
422 Trūksta url arba tai nėra pilnas http:// ar https:// adresas.
409 Straipsnis dar neparuoštas skelbti arba jau pažymėtas kaip paskelbtas. Straipsnis grąžinamas kartu su atmetimu, kad galėtumėte matyti, kuris atvejis tai yra.

Prijunkite "REST API" kaip savo publikavimo variklį.

Skiltyje Settings, tada Publishing, yra variklis pavadinimu "REST API: Jūs pasiimate ir paskelbiate". Jis nieko neprašo, nes mums nėra ko siųsti. Jis pakeičia tai, kas vyksta kiekvieną rytą: baigtas straipsnis lieka paruoštas be adreso, o skelbimo ekrane rodoma, kad laukiama, kol jūs jį pasiimsite, užuot rodžius nepavykusį pristatymą.

Galite naudoti API jo neprijungę: galiniai taškai veikia bet kuriai paskyrai su raktu. Prijungimas leidžia likusiai produkto daliai suprasti, kad nebereikia tikėtis nuosavo adreso, ir būtent tai neleidžia jūsų pačių paskelbtam straipsniui būti laikomam nesėkme.

Kiekviena klaida yra JSON su pranešimu.

Visur viena forma, nesvarbu, ar atmetimas atėjo iš rakto patikros, ar iš validatoriaus, todėl niekam nereikia apdoroti dviejų.

{ "message": "That API key is not valid, or it has been revoked." }
Būsena Ką tai reiškia
401 Nėra rakto, raktas ne mūsų arba raktas buvo atšauktas.
404 Šioje paskyroje nėra tokio straipsnio ar svetainės. Kitos paskyros straipsniui grąžinamas 404, o ne 403: API, kuri sako "tai ne jūsų", patvirtina, kad toks objektas egzistuoja.
409 Užklausa buvo tinkama, bet straipsnio būsena tam netinka.
422 Trūksta parametro arba jis neteisingas. Atsakyme yra errors objektas su lauko pavadinimu ir pranešimu.
429 Viršytas užklausų limitas. Retry-After nurodo, kiek laukti.

Ką iš tikrųjų daro kūrimo žingsnis.

#!/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

Patikrinkite, ar pristatymas atėjo iš mūsų.

Kiekvienas mūsų formato webhook pristatymas turi tris antraštes iš Standard Webhooks specifikacijos. Patikrinkite jas naudodami pasirašymo raktą iš Publishing nustatymų ir atmeskite viską, kas neatitinka arba yra senesnė nei penkios minutės.

webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
Antraštė Kas tai yra
webhook-id Pristatymas, ta pati reikšmė kaip delivery_id turinyje. Įsiminkite jau apdorotus pristatymus ir į pakartotinį atsakykite 200, darbo nekartodami.
webhook-timestamp Kada mes tai pasirašėme, pilnomis sekundėmis nuo 1970 metų. Atmeskite viską, kas nuo jūsų laikrodžio skiriasi daugiau nei penkiomis minutėmis, kad senas pristatymas negalėtų būti jums atsiųstas dar kartą.
webhook-signature v1, tada id, laiko žymos ir neapdoroto turinio base64 HMAC-SHA256, sujungti taškais ir sudaryti naudojant jūsų pasirašymo raktą. Jų gali būti daugiau nei vienas, atskirti tarpais. Priimkite pristatymą, jei sutampa bent vienas iš jų.

Pasirašymo raktas

Jis prasideda whsec_. Pašalinkite tai ir likusią dalį iškoduokite iš base64: tie baitai yra HMAC raktas, o ne tekstas, kurį nukopijavote. Jis parodomas vieną kartą, kai prijungiate webhook arba Publishing puslapyje paspaudžiate "New signing key", ir mes jo niekada nesiunčiame.

Patikrinkite neapdorotą turinį

Apskaičiuokite parašą iš baitų tiksliai tokių, kokie jie atėjo, prieš bet kam juos išnagrinėjant. JSON, kuris buvo iškoduotas ir vėl užkoduotas, nebėra tie patys baitai, todėl jo parašas nesutaps.

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 });
});

Arba naudokite biblioteką

Oficialios Standard Webhooks bibliotekos, skirtos PHP, JavaScript, Python, Go, Ruby ir kitoms kalboms, visa tai atlieka vienu iškvietimu. Pateikite joms pasirašymo raktą tokį, koks jis buvo parodytas jums, įskaitant whsec_. github.com/standard-webhooks

Senesnės antraštės

Pristatymuose taip pat siunčiama pasirašymo paslaptis kaip bearer prieigos raktas ir X-AutoSEO-LT-Signature, tik turinio HMAC, sudarytas su ta paslaptimi. Jie vis dar siunčiami, kad jais paremti galiniai taškai toliau veiktų. Naujas galinis taškas vietoj to turėtų tikrinti webhook-signature: senasis parašas neapima nei laiko, nei id, o jo raktas keliauja kiekvienoje užklausoje.

AutoSEO, Outrank ir BabyLoveGrowth formatai siunčia tiksliai tai, ką atsiuntė tos paslaugos, todėl šių antraščių jie neturi.

Sukurkite raktą ir išbandykite.

Bandomojo laikotarpio pakanka API skaitymui: jo metu parašyti straipsniai yra tikri straipsniai ir per šiuos galinius taškus grįžta kaip ir visi kiti.

Išbandykite nemokamai