50% zniżki na pierwszy miesiąc z kodem LAUNCH50 Tylko plany miesięczne. Zacznij teraz

Rozpocznij darmowy okres próbny

Pobieraj swoje artykuły.
Publikuj je po swojemu.

Każdy artykuł, który dla Państwa napiszemy, można odczytać przez HTTP: tytuł, metadane, Markdown, HTML, obraz główny i FAQ. Proszę pobrać je do swojej witryny Next.js, Astro lub własnej podczas buildu, opublikować je i przekazać nam adres, pod którym każdy z nich ostatecznie się znalazł.

Jeden token bearer. JSON na wejściu, JSON na wyjściu. Bez SDK do instalacji i bez niczego do konfiguracji poza kluczem.

Webhook wysyła dane. API pozwala je pobierać.

Przenoszą ten sam artykuł w tej samej postaci, więc parser napisany dla jednego odczyta drugie bez zmian. Różnica polega na tym, kto rozpoczyna komunikację.

Webhook, gdy Państwa witryna może przyjąć wpis o każdej porze

Wywołujemy Państwa endpoint w chwili, gdy artykuł jest gotowy, i ponownie, gdy się zmieni. Nie trzeba nic odpytywać ani nic planować. WordPress, Shopify, Ghost, wyzwalacz Zapier albo trasa napisana przez Państwa samodzielnie.

API, gdy Państwa witryna jest budowana i wdrażana jako całość

Statyczna witryna nie może przyjąć wpisu o szóstej trzydzieści rano, najpierw coś musi się przebudować. Dlatego Państwa build pyta, co czeka, pobiera to i potem przekazuje nam adres. To Państwo wybierają moment.

Oba, jeśli Państwo chcą

To osobne połączenia i żadne nie wyklucza drugiego. Webhook zasilający newsletter i API zasilające witrynę to normalny układ.

Jeden klucz, wysyłany jako token bearer.

Utwórz klucz w panelu w sekcji Settings, a potem Connections. Jest pokazywany tylko raz, przy utworzeniu, ponieważ przechowywany jest tylko jego hash: jeśli go Państwo zgubią, proszę go unieważnić i utworzyć nowy. Klucz odczytuje każdą witrynę na koncie i można go unieważnić na tym samym ekranie.

Każde żądanie

Authorization: Bearer sns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

Sprawdź, czy działa

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 żądań

120 żądań na minutę, liczonych względem klucza, a nie adresu, z którego pochodzą, ponieważ uruchomienia buildów współdzielą adresy. Po przekroczeniu dostaną Państwo 429 ze standardowymi nagłówkami Retry-After i X-RateLimit. Pobieranie artykułów do buildu to kilka żądań, więc nie jest to limit, na który powinni Państwo trafić przypadkiem.

Nie przechowuj klucza w repozytorium

Odczytuje wszystko, co napisaliśmy dla Państwa konta, w tym artykuły, które nie są jeszcze opublikowane. Proszę umieścić go w środowisku buildu, a nie w kodzie źródłowym. Proszę unieważnić go tutaj od razu, gdy znajdzie się gdziekolwiek, gdzie nie powinien, a wszystko, co go używa, natychmiast dostanie 401.

Jest ich pięć, a cztery tylko do odczytu.

Wszystko znajduje się pod /api/v1/. Wersja jest w ścieżce od pierwszego dnia, więc kiedyś może istnieć v2 bez psucia tego, co napiszą Państwo dzisiaj.

GET /api/v1/me

Konto, do którego należy klucz, oraz jego nazwa i prefiks. Przydaje się przede wszystkim do jednego: informuje Państwa, że klucz w tym środowisku jest tym kluczem, za który go Państwo uważają.

Przykład

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

Każda witryna na koncie. id służy do filtrowania listy artykułów, a domena jest po to, aby skrypt buildu mógł dopasować coś, co już zna, zamiast przenosić id.

Przykład

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

Strona artykułów, od najnowszej zmiany, bez treści. Kolejność według czasu ostatniej zmiany każdego artykułu, a nie czasu jego napisania, dlatego updated_since ma sens: artykuł, który jest już opublikowany, może zostać później edytowany, gdy link zostanie do niego dodany albo z niego usunięty.

Parametry zapytania

Nazwa Co robi
site_id Jedna witryna, z /sites. id, którego nie ma na Państwa koncie, zwraca 404.
status Jedna z wartości: draft, qa, needs_repair, review, ready, published, failed. Cokolwiek innego zwraca 422 zamiast pustej strony. "ready" to to, czego chce build: napisane, sprawdzone i jeszcze nigdzie nieopublikowane.
updated_since ISO 8601, na przykład 2026-09-06T00:00:00Z. Proszę zapamiętać znacznik czasu ostatniego uruchomienia i przekazać go przy następnym.
per_page Do 100. Domyślnie 25.
page Od 1. meta.has_more mówi, czy pytać o następną.

Przykład

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 artykuł, ze wszystkim, co lista pomija: content_markdown, content_html, FAQ jako schema.org JSON-LD gotowe do wstawienia na stronę oraz wszystkie linki wymienne, które artykuł zawiera.

Nazwy pól są celowo takie same jak te wysyłane przez nasz webhook. Jeśli mają już Państwo odbiornik webhooków, ten sam parser odczyta także to.

Przykład

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

Publikują Państwo artykuł; tutaj podają nam Państwo jego adres. To jedyny endpoint, który cokolwiek zmienia.

Działa dokładnie tak samo jak publikacja wykonana przez nas: artykuł zostaje oznaczony jako opublikowany, wlicza się do Państwa miesięcznego limitu, pozycja planu zostaje zamknięta, wszystkie linki wymienne w nim dostają adres, na który czekał weryfikator, a Państwa Strona na Facebook jest powiadamiana, jeśli została podłączona. Bez tego wywołania artykuł pozostaje gotowy na zawsze i nic z tego się nie dzieje.

Przykład

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

Odpowiedzi

200 Zapisano. Artykuł wraca z nowym statusem, published_at i published_url.
422 Brakuje adresu URL albo nie jest to pełny adres http:// lub https://.
409 Artykuł nie jest gotowy do publikacji albo jest już zapisany jako opublikowany. Artykuł wraca z odmową, aby mogli Państwo zobaczyć, który to przypadek.

Połącz "REST API" jako mechanizm publikacji.

W Ustawieniach, a potem w sekcji Publikowanie, znajduje się mechanizm o nazwie "REST API: pobierają Państwo i publikują". Nie wymaga niczego, bo nie mamy czego wysyłać. Zmienia to, co dzieje się każdego ranka: gotowy artykuł pozostaje gotowy bez adresu, a ekran publikacji pokazuje, że czeka na pobranie przez Państwa, zamiast wyświetlać nieudane dostarczenie.

Mogą Państwo używać API bez podłączania go: endpointy działają dla każdego konta z kluczem. Podłączenie go sprawia, że reszta produktu wie, że ma przestać oczekiwać własnego adresu, i to właśnie zapobiega liczeniu artykułu opublikowanego samodzielnie jako niepowodzenia.

Każda odpowiedź z błędem to JSON z komunikatem.

Jeden format wszędzie, niezależnie od tego, czy odmowa pochodzi ze sprawdzenia klucza, czy z walidatora, więc nic nie musi parsować dwóch.

{ "message": "That API key is not valid, or it has been revoked." }
Status Co to oznacza
401 Brak klucza, klucz, który nie jest nasz, albo klucz, który został unieważniony.
404 Na tym koncie nie ma takiego artykułu ani witryny. Artykuł z innego konta zwraca 404 zamiast 403: API, które mówi "to nie jest Państwa", potwierdziło istnienie tej rzeczy.
409 Żądanie było poprawne, ale artykuł nie jest w odpowiednim stanie.
422 Brakuje parametru albo jest nieprawidłowy. Odpowiedź zawiera obiekt errors z nazwą pola oraz komunikatem.
429 Przekroczono limit żądań. Retry-After mówi, jak długo czekać.

Co faktycznie robi krok budowania.

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

Proszę sprawdzić, czy dostawa pochodzi od nas.

Każda dostawa webhooka w naszym własnym formacie zawiera trzy nagłówki ze specyfikacji Standard Webhooks. Proszę sprawdzać je kluczem podpisu z ustawień Publishing i odrzucać wszystko, co się nie zgadza lub ma więcej niż pięć minut.

webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
Nagłówek Co to jest
webhook-id Dostawa, ta sama wartość co delivery_id w treści. Proszę zapamiętywać już obsłużone dostawy i odpowiadać na powtórkę kodem 200 bez ponownego wykonywania pracy.
webhook-timestamp Moment podpisania, w pełnych sekundach od 1970 roku. Proszę odrzucać wszystko, co różni się od Państwa zegara o więcej niż pięć minut, aby nie dało się ponownie wysłać starej dostawy.
webhook-signature v1, a potem base64 HMAC-SHA256 identyfikatora, znacznika czasu i surowej treści połączonych kropkami, utworzony Państwa kluczem podpisu. Może być ich więcej niż jeden, oddzielonych spacjami. Proszę przyjąć dostawę, jeśli zgadza się którykolwiek z nich.

Klucz podpisu

Zaczyna się od whsec_. Proszę to usunąć i zdekodować resztę z base64: te bajty są kluczem HMAC, a nie skopiowanym tekstem. Jest pokazywany tylko raz, gdy podłączają Państwo webhook albo naciskają "New signing key" na stronie Publishing, i nigdy go nie wysyłamy.

Sprawdź surową treść

Proszę obliczyć podpis z bajtów dokładnie w takiej postaci, w jakiej dotarły, zanim cokolwiek je przetworzy. JSON, który został zdekodowany i zakodowany ponownie, nie ma tych samych bajtów i jego podpis się nie zgodzi.

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

Lub użyć biblioteki

Oficjalne biblioteki Standard Webhooks dla PHP, JavaScript, Python, Go, Ruby i innych języków robią to wszystko jednym wywołaniem. Proszę przekazać im klucz podpisu dokładnie tak, jak został Państwu pokazany, razem z whsec_. github.com/standard-webhooks

Starsze nagłówki

Dostawy zawierają też sekret podpisu jako token bearer oraz X-AutoSEO-LT-Signature, czyli HMAC samej treści utworzony tym sekretem. Nadal je wysyłamy, aby endpointy oparte na nich dalej działały. Nowy endpoint powinien zamiast tego sprawdzać webhook-signature: stary podpis nie obejmuje czasu ani identyfikatora, a jego klucz jest przesyłany w każdym żądaniu.

Formaty AutoSEO, Outrank i BabyLoveGrowth wysyłają dokładnie to, co wysłały te usługi, więc nie zawierają tych nagłówków.

Utwórz klucz i wypróbuj go.

Okres próbny wystarcza do korzystania z API: artykuły napisane w jego trakcie są prawdziwymi artykułami i wracają przez te endpointy jak wszystkie inne.

Rozpocznij darmowy okres próbny