50% skont fuq l-ewwel xahar Tiegħek bil-kodiċi LAUNCH50 Pjanijiet ta' kull xahar biss. Ibda issa

Ibda prova bla ħlas

Iġbed l-artikli tiegħek.
Ippubblikahom bil-mod tiegħek.

Kull artiklu li niktbu għalik jista’ jinqara fuq HTTP: it-titlu, il-metadata, il-Markdown, l-HTML, l-immaġni hero u l-FAQ. Daħħalhom fis-sit Next.js, Astro jew custom tiegħek meta taħdem il-build tiegħek, ippubblikahom, u għidilna l-indirizz fejn spiċċa kull wieħed.

Bearer token waħda. JSON jidħol, JSON joħroġ. L-ebda SDK x’tinstalla u xejn x’tikkonfigura lil hinn minn key.

Il-webhook jibgħat. L-API tħallikom tiġbdu.

Iġorru l-istess artiklu fl-istess forma, għalhekk parser miktub għal waħda jaqra lill-oħra mingħajr tibdil. Id-differenza hija min jibda l-konversazzjoni.

Il-webhook, meta s-sit tagħkom jista' jaċċetta post fi kwalunkwe ħin

Insejħu l-endpoint tagħkom fil-mument li artiklu jkun lest, u mill-ġdid meta jinbidel. Xejn x'tivverifikaw perjodikament, xejn x'tiskedaw. WordPress, Shopify, Ghost, trigger ta' Zapier, jew rotta li ktibtu intom stess.

L-API, meta s-sit tagħkom jinbena u jiġi implimentat bħala unità waħda

Sit statiku ma jistax jaċċetta post fis-sitta u nofs ta’ filgħodu, l-ewwel trid issir rebuild. Għalhekk il-build tiegħek tistaqsi x’inhu qed jistenna, tieħdu, u wara tgħidilna l-indirizz. Inti tagħżel meta.

It-tnejn, jekk tridhom

Huma konnessjonijiet separati u waħda ma teskludix lill-oħra. Webhook li jforni newsletter u API li tforni l-website huwa arranġament normali.

Key waħda, mibgħuta bħala bearer token.

Agħmel key fid-dashboard taħt Settings, imbagħad Connections. Tintwera darba biss, meta tinħoloq, għax jinħażen biss il-hash tagħha: jekk titlefha, irrevokaha u agħmel oħra. Key taqra kull sit fil-kont u tiġi revokata mill-istess skrin.

Kull talba

Authorization: Bearer sns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

Iċċekkja li jaħdem

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
}

Rate limit

120 talbiet kull minuta, magħduda mal-key aktar milli mal-indirizz li tiġi minnu, għax build runners jaqsmu l-indirizzi. Aktar minn hekk tirċievi 429 bl-headers standard Retry-After u X-RateLimit. Il-ġbid tal-artikli għal build huwa biss ftit talbiet, għalhekk dan mhux limitu li għandek tilħaq bi żball.

Żomm il-key barra mir-repository tiegħek

Taqra dak kollu li ktibna għall-kont tiegħek, inklużi artikli li għadhom mhumiex ippubblikati. Poġġiha fil-build environment tiegħek, mhux fis-source tiegħek. Irrevokaha hawnhekk malli tkun xi mkien fejn m’għandhiex tkun, u kull ħaġa li tużaha tieħu 401 minnufih.

Hemm ħamsa minnhom, u erbgħa jaqraw biss.

Kollox jinsab taħt /api/v1/. Il-verżjoni tinsab fil-path mill-ewwel jum, sabiex xi darba tkun tista’ teżisti v2 mingħajr ma tkisser dak li tikteb illum.

GET /api/v1/me

Il-kont li tappartjeni għalih iċ-ċavetta, u l-isem u l-prefiss taċ-ċavetta nnifisha. Utli fuq kollox għal ħaġa waħda: biex jgħidilkom li ċ-ċavetta f'dan l-ambjent hija ċ-ċavetta li taħsbu li hi.

Eżempju

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

Kull sit fil-kont. L-id huwa dak li fuqu tiffiltra l-lista tal-artikli, u d-domain qiegħed hemm biex build script tkun tista’ tqabbel ma’ xi ħaġa li diġà taf minflok iġġorr id.

Eżempju

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

Paġna ta’ artikli, bl-aktar bidla reċenti l-ewwel, mingħajr il-kitba. Ordnat skont meta kull artiklu nbidel l-aħħar aktar milli meta nkiteb, u għalhekk updated_since huwa utli: artiklu li diġà huwa live jista’ jiġi editjat aktar tard, meta link tiddaħħal fih jew terġa’ titneħħa.

Parametri tal-query

Isem X'jagħmel
site_id Sit wieħed, minn /sites. Id li mhijiex fil-kont tiegħek twieġeb 404.
status Waħda minn draft, qa, needs_repair, review, ready, published, failed. Kull ħaġa oħra hija 422 aktar milli paġna vojta. "ready" huwa dak li trid build: miktub, iċċekkjat, u għadu mkien imkien.
updated_since ISO 8601, bħal 2026-09-06T00:00:00Z. Ftakar it-timestamp tal-aħħar run tiegħek u erġa’ għaddih f’dik ta’ wara.
per_page Sa 100. Il-valur awtomatiku huwa 25.
page Minn 1. meta.has_more jgħidlek jekk għandekx titlob oħra.

Eżempju

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}

Artiklu wieħed, b’dak kollu li l-lista tħalli barra: content_markdown, content_html, il-FAQ bħala schema.org JSON-LD lest biex tiddaħħlu fil-paġna, u kull exchange link li jġorr l-artiklu.

L-ismijiet tal-oqsma huma dawk li jibgħat il-webhook tagħna, apposta. Jekk diġà għandkom riċevitur tal-webhook, l-istess parser jaqra dan.

Eżempju

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

Intom ittellgħu l-artiklu live; hawnhekk tgħidulna l-indirizz. Dan huwa l-uniku endpoint li jibdel xi ħaġa.

Tagħmel eżatt dak li tagħmel delivery li nagħmlu aħna stess: l-artiklu jiġi mmarkat bħala published, l-allokazzjoni ta’ kull xahar tiegħek tgħoddu, l-oġġett tal-pjan jingħalaq, kull exchange link fih tieħu l-indirizz li l-verifier kien qed jistenna, u l-Facebook Page tiegħek tiġi infurmata jekk tkun qabbadt waħda. Mingħajr din is-sejħa l-artiklu jibqa’ ready għal dejjem u xejn minn dan ma jiġri.

Eżempju

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

Tweġibiet

200 Irreġistrat. L-artiklu jiġi lura bl-istatus il-ġdid tiegħu, published_at u published_url.
422 Il-url nieqes jew mhuwiex indirizz sħiħ http:// jew https://.
409 L-artiklu mhux lest biex jiġi ppubblikat, jew diġà huwa rreġistrat bħala ppubblikat. L-artiklu jerġa' jiġi bir-rifjut, sabiex tkunu tistgħu taraw liema minnhom hu.

Qabbad "REST API" bħala l-magna tal-pubblikazzjoni tiegħek.

Taħt Settings, imbagħad Publishing, hemm magna bl-isem "REST API: you fetch and publish". Ma titlob xejn, għax m'hemm xejn x'nibagħtu. Dak li tbiddel huwa dak li jiġri kull filgħodu: artiklu lest jibqa' lest mingħajr indirizz, u l-iskrin tal-pubblikazzjoni jgħid li qed jistenna li tiġbruh intom minflok ma juri kunsinna li falliet.

Tistgħu tużaw l-API mingħajr ma tqabbduha: l-endpoints jaħdmu għal kwalunkwe kont b'ċavetta. Li tqabbduha huwa kif il-bqija tal-prodott ikun jaf li għandu jieqaf jistenna indirizz tiegħu stess, u huwa dak li jwaqqaf artiklu li tippubblikaw intom stess milli jingħadd bħala falliment.

Kull falliment huwa JSON b’messaġġ.

Forma waħda kullimkien, kemm jekk ir-rifjut ġie mill-key check kif ukoll mill-validator, sabiex xejn ma jkollu jparseja tnejn.

{ "message": "That API key is not valid, or it has been revoked." }
Status Xi jfisser
401 L-ebda key, key li mhijiex tagħna, jew key li ġiet revokata.
404 M’hemmx artiklu jew sit bħal dan f’dan il-kont. Artiklu ta’ kont ieħor iwieġeb 404 aktar milli 403: API li tgħid "dak mhux tiegħek" tkun ikkonfermat li l-ħaġa teżisti.
409 It-talba kienet tajba iżda l-artiklu mhux fi stat adattat għal dan.
422 Parametru nieqes jew ħażin. It-tweġiba ġġorr errors object li jsemmi l-field, kif ukoll il-messaġġ.
429 Qabeż ir-rate limit. Retry-After jgħid kemm għandek tistenna.

X'jagħmel verament pass tal-build.

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

Iċċekkja li kunsinna ġiet mingħandna.

Kull kunsinna ta’ webhook fil-format tagħna ġġorr tliet headers mill-ispeċifikazzjoni Standard Webhooks. Iċċekkjahom biċ-ċavetta tal-firma mis-settings ta’ Publishing, u rrifjuta kull ħaġa li ma taqbilx jew li għandha aktar minn ħames minuti.

webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
Header X’inhu
webhook-id Il-kunsinna, l-istess valur bħal delivery_id fil-body. Ftakar f’dawk li diġà pproċessajt, u wieġeb ripetizzjoni b’200 mingħajr ma terġa’ tagħmel ix-xogħol.
webhook-timestamp Meta ffirmajnieh, f’sekondi sħaħ mill-1970. Irrifjuta kull ħaġa li tkun aktar minn ħames minuti differenti mill-arloġġ tiegħek, biex kunsinna qadima ma tkunx tista’ tintbagħatlek mill-ġdid.
webhook-signature v1, imbagħad il-base64 HMAC-SHA256 tal-id, it-timestamp u l-body mhux ipproċessat magħqudin b’punti, magħmul biċ-ċavetta tal-firma tiegħek. Jista’ jkun hemm aktar minn waħda, separati bi spazji. Aċċetta l-kunsinna jekk taqbel xi waħda minnhom.

Iċ-ċavetta tal-firma

Tibda b’whsec_. Neħħi dak u agħmel base64-decode tal-bqija: dawk il-bytes huma ċ-ċavetta HMAC, mhux it-test li kkupjajt. Tintwera darba biss, meta tqabbad il-webhook jew tagħfas "New signing key" fil-paġna ta’ Publishing, u aħna qatt ma nibagħtuha.

Iċċekkja l-body mhux ipproċessat

Ikkalkula l-firma mill-bytes eżatt kif waslu, qabel ma xi ħaġa tipproċessahom. JSON li ġie decoded u encoded mill-ġdid mhumiex l-istess bytes, u l-firma tiegħu ma taqbilx.

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

Jew uża librerija

Il-libreriji uffiċjali ta’ Standard Webhooks, għal PHP, JavaScript, Python, Go, Ruby u oħrajn, jagħmlu dan kollu b’sejħa waħda. Agħtihom iċ-ċavetta tal-firma kif intwerietlek, inkluż whsec_. github.com/standard-webhooks

Il-headers l-antiki

Il-kunsinni jġorru wkoll is-sigriet tal-firma bħala bearer token, u X-AutoSEO-LT-Signature, HMAC tal-body biss magħmul b’dak is-sigriet. Dawn għadhom jintbagħtu biex endpoints mibnija fuqhom jibqgħu jaħdmu. Endpoint ġdid għandu minflok jiċċekkja webhook-signature: il-firma l-qadima ma tkoprix ħin u lanqas id, u ċ-ċavetta tagħha tivvjaġġa f’kull talba.

Il-formats AutoSEO, Outrank u BabyLoveGrowth jibagħtu eżatt dak li bagħtu dawk is-servizzi, għalhekk ma jġorrux dawn il-headers.

Agħmel key u ppruvaha.

Il-prova hija biżżejjed biex taqraw l-API: l-artikli miktuba matulha huma artikli reali, u jerġgħu jiġu lura minn dawn l-endpoints bħal kwalunkwe oħrajn.

Ibda prova bla ħlas