Ensimmäisestä kuukaudestanne 50 % alennusta koodilla LAUNCH50 Vain kuukausitilauksiin. Aloita nyt

Aloita ilmainen kokeilu

Noutakaa artikkelinne.
Julkaiskaa ne omalla tavallanne.

Jokainen teille kirjoittamamme artikkeli voidaan lukea HTTP:n kautta: otsikko, metatiedot, Markdown, HTML, hero-kuva ja FAQ. Tuokaa ne Next.js-, Astro- tai mukautetulle sivustollenne buildin aikana, julkaiskaa ne ja kertokaa meille osoite, johon kukin päätyi.

Yksi bearer token. JSON sisään, JSON ulos. Ei asennettavaa SDK:ta eikä mitään määritettävää avaimen lisäksi.

Webhook lähettää. API antaa Teidän hakea.

Ne välittävät saman artikkelin samassa muodossa, joten toiselle kirjoitettu jäsennin lukee myös toisen muuttamattomana. Ero on siinä, kumpi aloittaa yhteyden.

Webhook, kun sivustonne voi vastaanottaa postauksen mihin aikaan tahansa

Kutsumme päätepistettänne heti, kun artikkeli on valmis, ja uudelleen, kun se muuttuu. Ei kyselyä, ei ajastettavaa. WordPress, Shopify, Ghost, Zapier-laukaisin tai itse kirjoittamanne reitti.

API, kun sivustonne rakennetaan ja otetaan käyttöön yhtenä kokonaisuutena

Staattinen sivusto ei voi vastaanottaa julkaisua puoli seitsemältä aamulla, vaan jotain on rakennettava ensin uudelleen. Siksi buildinne kysyy, mitä odottaa, hakee sen ja kertoo meille osoitteen jälkeenpäin. Te päätätte milloin.

Molemmat, jos haluatte

Ne ovat erillisiä yhteyksiä, eikä kumpikaan sulje toista pois. Webhook, joka syöttää uutiskirjettä, ja API, joka syöttää verkkosivustoa, on tavallinen järjestely.

Yksi avain, lähetetään bearer tokenina.

Luokaa avain hallintapaneelissa kohdassa Settings, sitten Connections. Se näytetään kerran, kun se luodaan, koska vain sen tiiviste tallennetaan: jos kadotatte sen, kumotkaa se ja tehkää uusi. Avain lukee tilin kaikki sivustot, ja se kumotaan samasta näkymästä.

Jokainen pyyntö

Authorization: Bearer sns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

Tarkistakaa, että se toimii

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
}

Nopeusraja

120 pyyntöä minuutissa, laskettuna avainta eikä lähdeosoitetta kohti, koska build runnerit jakavat osoitteita. Sen yli saatte 429-vastauksen tavallisilla Retry-After- ja X-RateLimit-otsakkeilla. Artikkelien noutaminen buildia varten on vain muutama pyyntö, joten tähän rajaan ei pitäisi osua vahingossa.

Pitäkää avain poissa repositoriostanne

Se lukee kaiken, mitä olemme kirjoittaneet tilillenne, myös artikkelit, joita ei ole vielä julkaistu. Laittakaa se build-ympäristöönne, ei lähdekoodiin. Kumotkaa se täällä heti, jos se päätyy paikkaan, jossa sen ei pitäisi olla, ja kaikki sitä käyttävä saa heti 401-vastauksen.

Niitä on viisi, ja neljä niistä vain lukee.

Kaikki on polun /api/v1/ alla. Versio on polussa alusta asti, joten v2 voi joskus olla olemassa rikkomatta sitä, mitä kirjoitatte tänään.

GET /api/v1/me

Tili, jolle avain kuuluu, sekä avaimen oma nimi ja etuliite. Tästä on hyötyä ennen kaikkea yhdessä asiassa: se kertoo Teille, että tämän ympäristön avain on se avain, joksi luulette sitä.

Esimerkki

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

Jokainen tilin sivusto. id:tä artikkeliluettelo suodattaa, ja domain on mukana, jotta build-skripti voi kohdistaa johonkin jo tuntemaansa eikä kuljettaa mukana id:tä.

Esimerkki

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

Sivu artikkeja, uusin muutos ensin, ilman sisältöä. Järjestetty sen mukaan, milloin kukin artikkeli on viimeksi muuttunut eikä milloin se kirjoitettiin, mikä tekee updated_since-parametrista hyödyllisen: jo julkaistua artikkelia voidaan muokata myöhemmin, kun siihen lisätään linkki tai siitä poistetaan linkki.

Kyselyparametrit

Nimi Mitä se tekee
site_id Yksi sivusto, osoitteesta /sites. id, jota ei ole tilillänne, vastaa 404.
status Yksi seuraavista: draft, qa, needs_repair, review, ready, published, failed. Mikä tahansa muu antaa 422-vastauksen tyhjän sivun sijaan. "ready" on se, mitä build haluaa: kirjoitettu, tarkistettu eikä vielä missään.
updated_since ISO 8601, kuten 2026-09-06T00:00:00Z. Muistakaa viimeisimmän ajon aikaleima ja välittäkää se takaisin seuraavalla kerralla.
per_page Enintään 100. Oletus on 25.
page Alkaen arvosta 1. meta.has_more kertoo, pitääkö pyytää seuraava.

Esimerkki

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}

Yksi artikkeli, mukana kaikki mitä luettelo ei sisällä: content_markdown, content_html, FAQ schema.org JSON-LD -muodossa valmiina lisättäväksi sivulle sekä kaikki artikkelin sisältämät vaihtolinkit.

Kenttien nimet ovat tarkoituksella samat, jotka webhookimme lähettää. Jos Teillä on jo webhook-vastaanotin, sama jäsennin lukee myös tämän.

Esimerkki

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

Te julkaiset artikkelin; tässä kerrotte meille osoitteen. Tämä on ainoa päätepiste, joka muuttaa mitään.

Se tekee täsmälleen saman kuin itse tekemämme toimitus: artikkeli merkitään julkaistuksi, se lasketaan kuukausikiintiöönne, suunnitelman kohta suljetaan, kaikki sen vaihtolinkit saavat osoitteen, jota tarkistin on odottanut, ja Facebook-sivullenne ilmoitetaan, jos olette yhdistäneet sellaisen. Ilman tätä kutsua artikkeli pysyy valmiina ikuisesti eikä mitään tästä tapahdu.

Esimerkki

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

Vastaukset

200 Tallennettu. Artikkeli palautuu uuden tilansa sekä kenttien published_at ja published_url kanssa.
422 URL puuttuu tai ei ole täydellinen http://- tai https://-osoite.
409 Artikkeli ei ole valmis julkaistavaksi tai se on jo merkitty julkaistuksi. Artikkeli palautuu hylkäyksen mukana, jotta näette kummasta on kyse.

Yhdistäkää "REST API" julkaisumoottoriksi.

Kohdassa Asetukset, sitten Julkaiseminen, on moottori nimeltä "REST API: Te haette ja julkaisette". Se ei pyydä mitään, koska meillä ei ole mitään lähetettävää. Se muuttaa sitä, mitä tapahtuu joka aamu: valmis artikkeli pysyy valmiina ilman osoitetta, ja julkaisunäyttö kertoo odottavansa, että haette sen, sen sijaan että se näyttäisi epäonnistuneen toimituksen.

Voitte käyttää API:a yhdistämättä sitä: päätepisteet toimivat kaikilla tileillä, joilla on avain. Yhdistäminen kertoo muulle tuotteelle, että sen pitää lakata odottamasta omaa osoitetta, ja se estää itse julkaisemanne artikkelin laskemisen epäonnistumiseksi.

Jokainen virhe on JSON, jossa on viesti.

Sama muoto kaikkialla, tulipa hylkäys avaintarkistuksesta tai validaattorilta, joten mitään ei tarvitse jäsentää kahdesti.

{ "message": "That API key is not valid, or it has been revoked." }
Tila Mitä se tarkoittaa
401 Ei avainta, avain joka ei ole meidän, tai avain joka on kumottu.
404 Tällä tilillä ei ole sellaista artikkelia tai sivustoa. Toisen tilin artikkeli vastaa 404 eikä 403: API, joka sanoo "se ei ole teidän", on vahvistanut, että kohde on olemassa.
409 Pyyntö oli kunnossa, mutta artikkeli ei ole siihen sopivassa tilassa.
422 Parametri puuttuu tai on virheellinen. Vastauksessa on errors-objekti, jossa on kentän nimi sekä viesti.
429 Nopeusrajan yli. Retry-After kertoo, kuinka kauan pitää odottaa.

Mitä build-vaihe oikeasti tekee.

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

Tarkistakaa, että toimitus tuli meiltä.

Jokaisessa omassa muodossamme olevassa webhook-toimituksessa on kolme Standard Webhooks -määrityksen otsaketta. Tarkistakaa ne Publishing-asetusten allekirjoitusavaimella ja hylätkää kaikki, mikä ei täsmää tai on yli viisi minuuttia vanha.

webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
Otsake Mikä se on
webhook-id Toimitus, sama arvo kuin rungon delivery_id. Pitäkää kirjaa käsittelemistänne toimituksista ja vastatkaa toistoon koodilla 200 tekemättä työtä uudelleen.
webhook-timestamp Hetki, jolloin allekirjoitimme sen, kokonaisina sekunteina vuodesta 1970. Hylätkää kaikki, mikä poikkeaa omasta kellostanne yli viisi minuuttia, jotta vanhaa toimitusta ei voi lähettää teille uudelleen.
webhook-signature v1, sitten tunnisteesta, aikaleimasta ja raakadatasta pisteillä yhdistetty base64-HMAC-SHA256, tehtynä allekirjoitusavaimellanne. Niitä voi olla useampi kuin yksi, välilyönneillä erotettuna. Hyväksykää toimitus, jos mikä tahansa niistä täsmää.

Allekirjoitusavain

Se alkaa merkkijonolla whsec_. Poistakaa se alusta ja base64-purkakaa loppuosa: nuo tavut ovat HMAC-avain, eivät kopioimanne teksti. Se näytetään kerran, kun yhdistätte webhookin tai painatte Publishing-sivulla "New signing key", emmekä koskaan lähetä sitä.

Tarkistakaa raaka runko

Laskekaa allekirjoitus tavuista täsmälleen siinä muodossa kuin ne saapuivat, ennen kuin mikään jäsentää niitä. Purettu ja uudelleen koodattu JSON ei ole samoja tavuja, eikä sen allekirjoitus täsmää.

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

Tai käyttäkää kirjastoa

Viralliset Standard Webhooks -kirjastot PHP:lle, JavaScriptille, Pythonille, Golle, Rubylle ja muille hoitavat kaiken tämän yhdellä kutsulla. Antakaa niille allekirjoitusavain täsmälleen siinä muodossa kuin se näytettiin teille, whsec_ mukana. github.com/standard-webhooks

Vanhemmat otsakkeet

Toimituksissa on myös allekirjoitussalaisuus bearer-tokenina sekä X-AutoSEO-LT-Signature, joka on pelkästä rungosta tällä salaisuudella tehty HMAC. Ne lähetetään yhä, jotta niiden varaan rakennetut päätepisteet toimivat edelleen. Uuden päätepisteen kannattaa tarkistaa sen sijaan webhook-signature: vanha allekirjoitus ei kata aikaa eikä tunnistetta, ja sen avain kulkee jokaisessa pyynnössä.

AutoSEO-, Outrank- ja BabyLoveGrowth-muodot lähettävät täsmälleen sen, mitä kyseiset palvelut lähettivät, joten niissä ei ole näitä otsakkeita.

Luokaa avain ja kokeilkaa sitä.

Kokeilujakso riittää API:n käyttöön: sen aikana kirjoitetut artikkelit ovat oikeita artikkeleita, ja ne palautuvat näiden päätepisteiden kautta kuten muutkin.

Aloita ilmainen kokeilu