50% afsláttur af fyrsta mánuðinum með kóðanum LAUNCH50 Aðeins mánaðaráætlanir. Byrjið núna

Byrja ókeypis prufuáskrift

Sækið greinar yðar.
Birtið þær á yðar hátt.

Hægt er að lesa hverja grein sem við skrifum fyrir yður yfir HTTP: titilinn, lýsigögnin, Markdown, HTML, aðalmyndina og FAQ. Takið þetta inn í Next.js, Astro eða sérsniðið vefsvæði þegar buildið yðar keyrir, setjið það í loftið og segið okkur á hvaða vistfangi hver grein endaði.

Eitt bearer token. JSON inn, JSON út. Ekkert SDK til að setja upp og ekkert að stilla umfram lykil.

Webhook-ið ýtir. API-ið leyfir yður að sækja.

Þau flytja sömu greinina í sama sniði, svo þáttari sem er skrifaður fyrir annað les hitt óbreytt. Munurinn er hver byrjar samskiptin.

Webhook-ið, þegar vefurinn yðar getur tekið við færslu hvenær sem er

Við köllum á endapunkt yðar um leið og grein er tilbúin og aftur þegar hún breytist. Ekkert að kanna reglulega, ekkert að tímasetja. WordPress, Shopify, Ghost, Zapier-kveikja eða leið sem þér skrifuðuð sjálfir.

API-ið, þegar vefurinn yðar er smíðaður og settur í loftið sem ein heild

Staðvætt vefsvæði getur ekki tekið við færslu klukkan hálf sjö að morgni, eitthvað þarf fyrst að endurbyggjast. Því spyr buildið yðar hvað bíður, sækir það og segir okkur vistfangið á eftir. Þér veljið hvenær.

Hvort tveggja, ef þér viljið það

Þetta eru aðskildar tengingar og hvorug útilokar hina. Webhook sem fæðir fréttabréf og API sem fæðir vefsvæðið er eðlilegt fyrirkomulag.

Einn lykill, sendur sem bearer token.

Búið til lykil í stjórnborðinu undir Settings, síðan Connections. Hann er sýndur einu sinni, þegar hann er búinn til, því aðeins hakk hans er geymt: ef þér missið hann, afturkallið hann og búið til annan. Lykill les öll vefsvæði á aðganginum og er afturkallaður af sama skjá.

Hver beiðni

Authorization: Bearer sns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

Athugið að það virki

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
}

Hraðatakmörk

120 beiðnir á mínútu, taldar á lykilinn frekar en vistfangið sem þær koma frá, því build runners deila vistföngum. Farið þér yfir það fáið þér 429 með stöðluðu Retry-After og X-RateLimit hausunum. Að sækja greinar fyrir build eru örfáar beiðnir, svo þetta eru ekki mörk sem þér ættuð að ná fyrir slysni.

Haldið lyklinum utan geymslunnar yðar

Hann les allt sem við höfum skrifað fyrir aðgang yðar, þar á meðal greinar sem eru ekki enn birtar. Setjið hann í build-umhverfið yðar, ekki í grunnkóðann. Afturkallið hann hér um leið og hann er einhvers staðar þar sem hann á ekki að vera, og allt sem notar hann fær 401 strax.

Fimm alls, og fjórir lesa aðeins.

Allt er undir /api/v1/. Útgáfan er í slóðinni frá fyrsta degi, svo v2 getur einn daginn verið til án þess að rjúfa það sem þér skrifið í dag.

GET /api/v1/me

Reikningurinn sem lykillinn tilheyrir og eigið heiti og forskeyti lykilsins. Gagnast umfram allt til eins: að segja yður að lykillinn í þessu umhverfi sé sá lykill sem þér haldið að hann sé.

Dæmi

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

Öll vefsvæði á aðganginum. id er það sem greinalistinn síar eftir, og lénið er þar svo build-skripta geti parað við eitthvað sem hún þekkir nú þegar í stað þess að bera með sér id.

Dæmi

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

Síða af greinum, nýjasta breyting fyrst, án textans. Raðað eftir því hvenær hver grein breyttist síðast frekar en hvenær hún var skrifuð, sem er ástæðan fyrir að updated_since er gagnlegt: grein sem er þegar komin í loftið má breyta síðar, þegar hlekkur er settur inn í hana eða tekinn út aftur.

Fyrirspurnarstikur

Heiti Hvað það gerir
site_id Eitt vefsvæði, frá /sites. id sem er ekki á aðgangi yðar svarar 404.
status Eitt af draft, qa, needs_repair, review, ready, published, failed. Allt annað gefur 422 frekar en tóma síðu. "ready" er það sem build vill: skrifað, yfirfarið og ekki enn komið neitt.
updated_since ISO 8601, eins og 2026-09-06T00:00:00Z. Munið tímastimpilinn frá síðustu keyrslu og sendið hann aftur með þeirri næstu.
per_page Allt að 100. Sjálfgefið er 25.
page Frá 1. meta.has_more segir til um hvort eigi að biðja um aðra.

Dæmi

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}

Ein grein, með öllu sem listinn sleppir: content_markdown, content_html, FAQ sem schema.org JSON-LD tilbúið til að setja inn á síðuna, og allir skiptihlekkir sem greinin ber.

Heiti reitanna eru þau sem webhook-ið okkar sendir, af ásettu ráði. Ef þér eruð þegar með móttakara fyrir webhook, les sami þáttari þetta.

Dæmi

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

Þér setjið greinina í loftið, hér segið þér okkur slóðina. Þetta er eini endapunkturinn sem breytir einhverju.

Þetta gerir nákvæmlega það sama og afhending sem við framkvæmdum sjálf: greinin er merkt birt, hún telst með í mánaðarheimild yðar, áætlunarliðurinn lokast, allir skiptihlekkir í henni fá vistfangið sem staðfestingin hefur beðið eftir, og Facebook Page yðar fær upplýsingar ef þér hafið tengt slíka síðu. Án þessa kalls helst greinin tilbúin að eilífu og ekkert af þessu gerist.

Dæmi

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

Svör

200 Skráð. Greinin kemur til baka með nýrri stöðu, published_at og published_url.
422 Slóðina vantar eða hún er ekki fullt http:// eða https:// vistfang.
409 Greinin er ekki tilbúin til birtingar eða er þegar skráð sem birt. Greinin kemur til baka með synjuninni svo þér getið séð hvort á við.

Tengið "REST API" sem birtingarvél yðar.

Undir Settings, síðan Publishing, er vél sem heitir "REST API: þér sækið og birtið". Hún biður ekki um neitt, því það er ekkert sem við þurfum að senda. Það sem hún breytir er það sem gerist á hverjum morgni: fullgerð grein helst tilbúin án slóðar og birtingarskjárinn segir að hann bíði eftir að þér sækið hana í stað þess að sýna sendingu sem mistókst.

Þér getið notað API-ið án þess að tengja það: endapunktarnir virka fyrir hvaða reikning sem er með lykil. Að tengja það er það sem lætur restina af vörunni vita að hún eigi að hætta að búast við eigin slóð, og það er það sem kemur í veg fyrir að grein sem þér birtið sjálfir sé talin sem mistök.

Allar villur eru JSON með skilaboðum.

Sama form alls staðar, hvort sem höfnunin kom úr lykilathuguninni eða frá staðfestingunni, svo ekkert þarf að þátta tvennt.

{ "message": "That API key is not valid, or it has been revoked." }
Staða Hvað það þýðir
401 Enginn lykill, lykill sem er ekki okkar, eða lykill sem hefur verið afturkallaður.
404 Engin slík grein eða vefsvæði á þessum aðgangi. Grein á öðrum aðgangi svarar 404 frekar en 403: API sem segir "það er ekki yðar" hefur staðfest að hluturinn sé til.
409 Beiðnin var í lagi en greinin er ekki í réttu ástandi fyrir þetta.
422 Vantar stika eða hún er röng. Svarið inniheldur errors-hlut sem nefnir reitinn, ásamt skilaboðunum.
429 Farið yfir hraðatakmörkin. Retry-After segir hversu lengi á að bíða.

Hvað smíðiskref gerir í raun.

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

Staðfestu að afhending hafi komið frá okkur.

Hver webhook-afhending á okkar eigin sniði ber þrjá hausa úr Standard Webhooks staðlinum. Athugaðu þá með undirritunarlyklinum úr Publishing stillingunum og hafnaðu öllu sem passar ekki eða er meira en fimm mínútna gamalt.

webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
Haus Hvað það er
webhook-id Afhendingin, sama gildi og delivery_id í meginhlutanum. Mundu hvaða afhendingar þú hefur afgreitt og svaraðu endurtekningu með 200 án þess að vinna verkið aftur.
webhook-timestamp Þegar við undirrituðum hana, í heilum sekúndum frá 1970. Hafnaðu öllu sem er meira en fimm mínútum frá þinni eigin klukku, svo ekki sé hægt að senda þér gamla afhendingu aftur.
webhook-signature v1, síðan base64 HMAC-SHA256 af auðkenninu, tímastimplinum og hráa meginhlutanum tengdu saman með punktum, gert með undirritunarlyklinum þínum. Það geta verið fleiri en ein, aðskilin með bilum. Samþykktu afhendinguna ef eitthvert þeirra passar.

Undirritunarlykillinn

Hann byrjar á whsec_. Taktu það af og base64-afkóðaðu afganginn: þessi bæti eru HMAC lykillinn, ekki textinn sem þú afritaðir. Hann er sýndur einu sinni, þegar þú tengir webhookið eða ýtir á "New signing key" á Publishing síðunni, og við sendum hann aldrei.

Athugaðu hráan meginhluta

Reiknaðu undirritunina út frá bætunum nákvæmlega eins og þær komu, áður en nokkuð þáttar þær. JSON sem hefur verið afkóðað og kóðað aftur eru ekki sömu bæti, og undirritun þess passar ekki.

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

Eða notaðu safn

Opinber Standard Webhooks söfnin fyrir PHP, JavaScript, Python, Go, Ruby og fleira gera allt þetta í einu kalli. Gefðu þeim undirritunarlykilinn eins og hann var sýndur þér, whsec_ meðtalið. github.com/standard-webhooks

Eldri hausarnir

Afhendingar bera líka undirritunarleyndarmálið sem bearer token og X-AutoSEO-LT-Signature, HMAC af meginhlutanum einum gert með því leyndarmáli. Þau eru enn send svo endapunktar sem byggja á þeim haldi áfram að virka. Nýr endapunktur ætti frekar að athuga webhook-signature: gamla undirritunin nær hvorki yfir tíma né auðkenni, og lykill hennar fer með hverri beiðni.

AutoSEO, Outrank og BabyLoveGrowth sniðin senda nákvæmlega það sem þær þjónustur sendu, svo þau bera ekki þessa hausa.

Búið til lykil og prófið hann.

Prufutíminn nægir til að lesa API-ið: greinarnar sem eru skrifaðar á honum eru raunverulegar greinar og þær koma til baka í gegnum þessa endapunkta eins og allar aðrar.

Byrja ókeypis prufuáskrift