Publisering via API
Publiser artikler på ditt eget nettsted eller CMS med signerte API-kall.
Papigrows sender signerte POST-forespørsler til HTTPS-mottakeren du drifter. Mottakeren lagrer og publiserer artikkelen og svarer deretter med dens offentlige URL.
Konfigurer API-publiseringKoble til publiseringsendepunktet
Bygg en mottaker som lagrer og publiserer artikler på nettstedet ditt. Legg til endepunkt og delt hemmelighet i CMS-innstillingene. Test og lagre sender connection.test med tomme data; svar med HTTP 200 og ok: true. Bare eiere og administratorer kan endre tilkoblingen.
Bruk et offentlig HTTPS-endepunkt uten spørringsparametere, fragmenter eller innebygd påloggingsinformasjon.
En tilfeldig hemmelighet er klar til bruk. Du kan erstatte den med din egen på 32 til 256 tegn. Kopier den til mottakeren og oppbevar den på serveren.
{
"version": 1,
"event": "connection.test",
"id": "<event-id>",
"data": {}
}
HTTP/1.1 200 OK
Content-Type: application/json
{ "ok": true }La mottakeren godta både den gamle og den nye hemmeligheten mens du tester og lagrer. Papigrows bruker den lagrede hemmeligheten til den nye tilkoblingen har bestått testen og er lagret. Fjern den gamle når pågående leveranser er ferdige.
Forespørselshoder
Disse hodene følger både tilkoblingstesten og artikkelleveransene. Hodenavn skiller ikke mellom store og små bokstaver.
| Hodenavn | Eksempelverdi | Betydning |
|---|---|---|
Content-Type | application/json | Forespørselskropp i JSON. |
Accept | application/json | En JSON-bekreftelse forventes. |
X-Papigrows-Event-Id | <event-id> | Samsvarer med konvoluttens id. |
X-Papigrows-Timestamp | <unix-seconds> | Unix-tid i sekunder for dette forsøket. |
X-Papigrows-Signature | v1=<hex-hmac-sha256> | v1= etterfulgt av heksadesimal HMAC-SHA256 av tidsstempelet + '.' + den nøyaktige rå forespørselskroppen. |
Idempotency-Key | <event-id> | Samme stabile konvolutt-id ved nye forsøk. |
Verifiser hver forespørsel
Verifiser v1-signaturen med HMAC-SHA256 over tidsstempelet, et punktum og den nøyaktige rå forespørselskroppen før JSON tolkes. Sammenlign i konstant tid, avvis tidsstempler som er mer enn fem minutter gamle eller i fremtiden, og behold hemmeligheten på serveren. HTTPS og signatur erstatter ikke beskyttelse mot duplikater.
Eksempel på forespørsel
POST /your-publishing-endpoint
Content-Type: application/json
X-Papigrows-Event-Id: <event-id>
X-Papigrows-Timestamp: <unix-seconds>
X-Papigrows-Signature: v1=<hex-hmac-sha256>
Idempotency-Key: <event-id>
{
"version": 1,
"event": "article.upsert",
"id": "<event-id>",
"data": {
"id": "<permanent-article-id>",
"version": 7,
"title": "Example",
"html": "<p>...</p>",
"slug": "example",
"excerpt": "...",
"coverImageUrl": "https://example.com/cover.jpg",
"locale": "en",
"hreflangAlternates": [
{ "locale": "sv", "url": "https://example.com/sv/blog/example" }
]
}
}Felter i forespørselskroppen
Feltene beskriver article.upsert. For connection.test er data tomt. Protokollversjonen i konvolutten er atskilt fra artikkelens innholdsversjon.
| Feltnavn | Verditype | Inkludert | Betydning |
|---|---|---|---|
version | integer | Alltid | Protokollversjon: 1 for det opprinnelige formatet eller 2 etter en uttrykkelig oppgradering av mottakeren. |
event | string | Alltid | Hendelsestypen: article.upsert eller connection.test. |
id | string | Alltid | Stabil leverings-id som deles med begge identitetshodene. Lagre den for å hindre doble skrivinger. |
data | object | Alltid | Hendelsens nyttelast. Tom for connection.test. |
data.id | string | Alltid | Permanent id for denne språkvarianten. Opprett eller oppdater med denne nøkkelen. |
data.version | integer | Alltid | Lagret innholdsrevisjon som et ikke-negativt heltall. Avvis eldre revisjoner. |
data.title | string | Alltid | Artikkelens tittel. |
data.html | string | Alltid | HTML som skal publiseres, inkludert tilgjengelige hreflang-lenker. |
data.slug | string | Når tilgjengelig | Foreslått URL-slug. Behold en stabil offentlig URL ved senere endringer. |
data.excerpt | string | Når tilgjengelig | Artikkelutdrag når det finnes. |
data.coverImageUrl | string | Når tilgjengelig | Forsidebildets URL når den finnes. |
data.locale | string | Når tilgjengelig | Språkkode for denne artikkelvarianten. |
data.hreflangAlternates | array | Når tilgjengelig | Andre publiserte språkvarianter når de finnes. |
data.hreflangAlternates[].locale | string | For hver språkvariant | Språkkode for den lenkede varianten. |
data.hreflangAlternates[].url | string | For hver språkvariant | Den publiserte variantens offentlige URL. |
Versjon 2 og kompatibilitet
Versjon 2 inkluderer ren tekst, arbeidsområde- og målgruppe-ID-er, sammendrag, seksjoner og tagger. Mottakeren må uttrykkelig bekrefte versjon 2 i tilkoblingstesten. Eksisterende mottakere beholder versjon 1 til du velger en annen versjon og tester og lagrer den.
{
"version": 2,
"event": "connection.test",
"id": "<event-id>",
"data": {}
}
HTTP/1.1 200 OK
Content-Type: application/json
{ "ok": true, "version": 2 }POST /your-publishing-endpoint
Content-Type: application/json
X-Papigrows-Event-Id: <event-id>
X-Papigrows-Timestamp: <unix-seconds>
X-Papigrows-Signature: v1=<hex-hmac-sha256>
Idempotency-Key: <event-id>
{
"version": 2,
"event": "article.upsert",
"id": "<event-id>",
"data": {
"id": "<permanent-article-id>",
"groupId": "<permanent-article-group-id>",
"workspaceId": "<owning-workspace-id>",
"primaryAudienceId": "<saved-audience-id>",
"businessAreaId": "<saved-business-area-id>",
"text": "...",
"summary": "...",
"section": "Advice",
"tags": ["Families"],
"version": 7,
"title": "Example",
"html": "<p>...</p>",
"slug": "example",
"excerpt": "...",
"coverImageUrl": "https://example.com/cover.jpg",
"locale": "en",
"hreflangAlternates": [
{ "locale": "sv", "url": "https://example.com/sv/blog/example" }
]
}
}Versjon 2 beholder de opprinnelige artikkelfeltene og legger til disse obligatoriske feltene. Målgruppe-ID-er er null for eldre artikler uten lagret målretting.
| Feltnavn | Verditype | Betydning |
|---|---|---|
data.groupId | UUID | Stabil gruppe delt av språkvariantene. |
data.workspaceId | UUID | ID for artikkelens lagrede arbeidsområde. |
data.primaryAudienceId | UUID | null | Lagret primærmålgruppe-ID, eller null for en eldre artikkel uten målgruppe. |
data.businessAreaId | UUID | null | Lagret virksomhetsområde-ID, eller null når det mangler. |
data.text | string | Ren tekst fra renset artikkel-HTML, ikke opprinnelig Markdown. Lenker hentes ikke. |
data.summary | string | null | Lagret artikkelsammendrag, eller null når det mangler. |
data.section | string | null | Seksjon lagret for denne publiseringen, eller null. |
data.tags | string[] | Tagger lagret for denne publiseringen; en tom liste betyr ingen tagger. |
Offentlig innholdsstrøm
Aktiver Papigrows API-tilkoblingen for å vise publiserte artikler. Be uttrykkelig om versjon 2; uten versjonsvalg beholdes det opprinnelige svaret. Private arbeidsområder og upubliserte artikler er utilgjengelige.
/api/content/feed/<workspace-slug>?version=2&locale=en&limit=20Hver artikkel inneholder lagrede ID-er, innholdsversjon, ren tekst og sammendrag. Seksjon og tagger kommer bare fra det lagrede publiseringsmålet. Tagger er null når dokumentasjon mangler. Behold version=2 og locale i hver forespørsel med sidemarkør.
I den offentlige strømmen er text null når konvertering er utilgjengelig. textUnavailable er body_too_large over 1 MiB eller conversion_failed ved konverteringsfeil; ellers er verdien null. HTML og sideinndeling bevares.
Bekreft publisering
Når artikkelen er offentlig, svar med HTTP 200 eller 201, samme ID, status published og offentlig HTTPS-URL. Købekreftelser, HTTP 202 eller 204, omdirigeringer, utkast og ugyldige svar teller ikke som publisering. Ikke bekreft før lagring og publisering er fullført.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "<permanent-article-id>",
"status": "published",
"url": "https://example.com/blog/example"
}Levering og nye forsøk
article.upsert oppretter eller oppdaterer én språkvariant med data.id som permanent nøkkel. Lagre data.version og hendelses-ID atomisk med endringen; duplikater får den lagrede bekreftelsen, og eldre versjoner må aldri overskrive nyere innhold. Det gjøres opptil tre forsøk med fem sekunders frist hver. Nettverksfeil, HTTP 429 og HTTP 5xx prøves igjen etter 250 ms og 1 sekund. Kropp og hendelses-ID forblir like, mens tidsstempel og signatur fornyes.
Leveringskroppen er begrenset til 1 MiB og JSON-svaret til 16 KiB. Begrens den rå forespørselskroppen før du tolker den.
Hva integrasjonen dekker
Bare artikler, inkludert endringer publisert på nytt fra Papigrows. Emnehendelser, sletting, bakgrunnskøer og tilbakelesing av eksterne endringer er ikke inkludert. Svar-til-redigering kan ikke trygt oppdatere en webhook; rediger og publiser i Papigrows. Eksisterende abonnements-, kvalitets- og automatiseringsinnstillinger gjelder. Kontroller mottakeren før et manuelt nytt forsøk etter en usikker feil; det finnes ingen endeløs løkke.
Eksempel på signaturverifisering
Denne Node.js-hjelperen verifiserer bare autentisitet. Mottakeren må også begrense forespørselsstørrelse, validere skjemaet, håndtere duplikater og versjoner i varig lagring, publisere artikkelen og returnere bekreftelsen nedenfor.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(headers, rawBody, secret) {
const timestamp = headers.get("x-papigrows-timestamp") ?? "";
const signature = headers.get("x-papigrows-signature") ?? "";
if (!/^\d{10}$/.test(timestamp)) return false;
const age = Math.floor(Date.now() / 1000) - Number(timestamp);
if (age < 0 || age > 300) return false;
if (!/^v1=[a-f0-9]{64}$/.test(signature)) return false;
const expected = createHmac("sha256", secret)
.update(timestamp + ".").update(rawBody).digest();
const received = Buffer.from(signature.slice(3), "hex");
if (received.length !== expected.length
|| !timingSafeEqual(received, expected)) return false;
try {
const event = JSON.parse(rawBody.toString());
return typeof event.id === "string"
&& /^[a-f0-9]{64}$/.test(event.id)
&& headers.get("x-papigrows-event-id") === event.id
&& headers.get("idempotency-key") === event.id;
} catch { return false; }
}