Documentația API
Aceleași operațiuni ca în interfață, prin cheie de API: catalog, articole, publicări, campanii, rapoarte, redactare. Fiecare rută cu permisiunea cerută, câmpurile și un exemplu de răspuns. Aceeași sursă ca fișierul OpenAPI.
Autentificare
Cheia se trimite în antetul `Authorization`. Se emite din pagina de integrări și se vede o singură dată — noi păstrăm doar amprenta ei, exact ca la parole.
Excepție: rutele din `/public/*` merg fără nicio cheie — sunt cele patru din „Catalogul public”, marcate „fără cheie”. În `openapi.json` au `security: []`, iar documentul n-are `security` global.
curl --location 'https://app.comunicate.eu/api/v1/partner/balance' \
--header 'Authorization: Bearer bk_live_…'- Adresa de bază
https://app.comunicate.eu/api/v1- Domeniile
- API-ul răspunde identic pe toate gazdele platformei: app.comunicate.eu, app.comunicate.top. Canonică e app.comunicate.eu — ea e scrisă și în `servers` din `openapi.json`, oricare gazdă ai folosi ca să iei fișierul. Celelalte sunt aliasuri: aceleași rute, aceleași răspunsuri, fără redirecționare.
- Limita de cereri
- Per cheie, pe minut — scrisă pe cheie când o emiți. O integrare scăpată de sub control nu blochează celelalte chei ale aceluiași cont. Depășirea întoarce 429 cu `Retry-After`, nu 403: singurul caz în care reîncercarea are sens.
- Rutele cu AI
- Scrierea, importul de documente și completarea metadatelor au o limită în plus: șaizeci pe oră, pe organizație, indiferent câte chei ai.
- Organizația
- Vine din cheie, nu din adresă. Nu există niciun `organizationId` de trimis, și nici de greșit.
- Erorile
- Cod HTTP real — 400, 401, 403, 404, 409 — și un corp cu `message`. La validare, `errors` spune care câmp și de ce.
Un răspuns de eroare
{
"message": "Datele trimise nu sunt valide",
"errors": [{ "path": "tags", "code": "invalid_type", "message": "Expected array, received string" }]
}Permisiuni
Fiecare cheie primește doar permisiunile de care are nevoie. O integrare care citește catalogul n-are cum să publice, oricât ar încerca.
CATALOG_READCitirea catalogului de publicații și a tipurilor de campanie.ARTICLES_READCitirea articolelor organizației.ARTICLES_WRITECrearea și modificarea articolelor, importul de documente și din Drive, cererea de redactare.MEDIA_WRITEÎncărcarea de imagini în galerie.PUBLICATIONS_READCitirea publicărilor și a stării lor.PUBLICATIONS_WRITECererea de publicare — singura permisiune care cheltuie credite sau bani.CAMPAIGNS_READCitirea campaniilor.CAMPAIGNS_WRITECrearea campaniilor.BALANCE_READCitirea soldului și a pachetelor deținute.REPORTS_READCitirea rapoartelor pe interval.
Catalogul public
Patru rute care merg fără nicio cheie: nișele rețelei, o nișă cu publicații de probă, cifrele catalogului și statistica pieței. Sunt aceleași date pe care le arată site-ul public, servite ca JSON — bune pentru comparații, pentru un asistent care vrea să știe ce e în rețea, sau pentru cine vrea să vadă cum arată datele înainte să-și facă cont. Nu există rută publică pentru lista întreagă de publicații, deliberat: inventarul nu se descarcă dintr-o cerere.
GET/public/statsCifrele cataloguluifără cheie
Câte publicații active are rețeaua și pe câte nișe. Fără cheie.
Exemplu
curl 'https://app.comunicate.eu/api/v1/public/stats'Câmpuri din răspuns
publications | integer | Publicații active. |
niches | integer | Nișe cu publicații. |
Răspuns
{ "publications": 3822, "niches": 22 }GET/public/nichesNișele rețeleifără cheie
Toate nișele, cu numărul de publicații și autoritatea medie a fiecăreia. Fără cheie.
Parametri
locale | 'ro' | 'en' | Limba numelor și descrierilor. Implicit `ro`. |
Exemplu
curl 'https://app.comunicate.eu/api/v1/public/niches?locale=ro'Câmpuri din răspuns
slug | string | Identificatorul nișei, folosit în ruta următoare. |
name | string | Numele nișei în limba cerută. |
description | string | Ce fel de materiale primesc publicațiile din nișă. |
publicationCount | integer | Câte publicații active are nișa. |
averageDa | integer | Autoritatea de domeniu medie (Moz DA). |
Răspuns
[
{
"slug": "stiri",
"name": "Știri",
"description": "Publicațiile de actualitate generală din rețea…",
"publicationCount": 1299,
"averageDa": 38
}
]GET/public/niches/{slug}O nișă, cu publicații de probăfără cheie
Nișa cerută și câteva publicații din ea, cu autoritate, termen și preț. E o mostră, nu inventarul: lista întreagă se vede din cont.
Parametri
locale | 'ro' | 'en' | Limba numelor și descrierilor. Implicit `ro`. |
Exemplu
curl 'https://app.comunicate.eu/api/v1/public/niches/stiri?locale=ro'Câmpuri din răspuns
slug | string | Identificatorul nișei. |
name | string | Numele nișei. |
publicationCount | integer | Câte publicații are nișa în total. |
averageDa | integer | Autoritatea medie a nișei. |
publications | object[] | Mostra: `domain`, `language`, `metricDa`, `metricPa`, `deliveryDays`, `priceCents`, `currency`. |
Răspuns
{
"slug": "stiri",
"name": "Știri",
"publicationCount": 1299,
"averageDa": 38,
"publications": [
{
"domain": "corrierefiorentino.corriere.it",
"language": "it",
"metricDa": 92,
"metricPa": 62,
"deliveryDays": 2,
"priceCents": 1296200,
"currency": "RON"
}
]
}GET/public/statistici-piataStatistica piețeifără cheie
Cifrele agregate ale catalogului: câte publicații, câte din rețeaua proprie, distribuția autorității, termenele și prețul pe fiecare tip de campanie. Datele din spatele paginii „Cât costă un advertorial".
Exemplu
curl 'https://app.comunicate.eu/api/v1/public/statistici-piata'Câmpuri din răspuns
publicatii | integer | Publicații active cu preț public. |
retea | integer | Câte dintre ele sunt ale noastre. |
autoritate | object | Minim, maxim, mediană, medie și distribuția pe intervale. |
termene | object[] | Câte publicații livrează în câte zile. |
preturi | object[] | Pe fiecare tip de campanie: preț, minim, maxim și câte publicații îl acceptă. |
Răspuns
{
"publicatii": 3822,
"retea": 109,
"autoritate": { "minim": 1, "maxim": 92, "mediana": 35, "medie": 34 },
"termene": [{ "zile": 2, "cate": 3822 }],
"preturi": [
{ "tip": "PRESS_RELEASE", "lei": 35, "minim": 35, "maxim": 14868, "publicatii": 1737 }
]
}Cheia și permisiunile
Prima cerere a oricărei integrări: cine e cheia și ce are voie. Tot aici se vede dacă organizația a pornit „asistenții AI pot comanda și publica" — comutatorul care decide ce poate face un asistent MCP cu banii contului.
GET/partner/meCine suntorice cheie
Organizația din spatele cheii, permisiunile ei și comutatorul pentru asistenți AI. Nu cere nicio permisiune: orice cheie validă poate întreba.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/me' \
--header 'Authorization: Bearer bk_live_…'Câmpuri din răspuns
organization | object | `id` și `name`. |
scopes | string[] | Permisiunile cheii. |
aiPublishing | boolean | `true` dacă asistenții AI pot comanda publicări și redactare (Integrări → Asistenți AI). |
Răspuns
{
"organization": { "id": "e0b5…", "name": "Agenția Exemplu SRL" },
"keyPrefix": "bk_live_a1b2",
"scopes": ["CATALOG_READ", "ARTICLES_WRITE"],
"aiPublishing": false
}Catalog
Ce publicații există, cât costă pe fiecare tip de campanie și dacă le poți plăti din pachetele pe care le ai.
GET/partner/catalogLista publicațiilorCATALOG_READ
Catalogul paginat, cu aceleași filtre ca în interfață. Fiecare intrare spune și cum se poate plăti: din pachet, din sold, sau deloc.
Parametri
page | int | Pagina cerută, de la 1. |
pageSize | int | Câte intrări pe pagină, cel mult 100. |
search | string | Caută în nume și domeniu. |
campaign | string | Doar publicațiile care acceptă tipul dat (vezi `campaign-types`). |
serviceType | string | `PUBLISH_ONLY`, `WRITE_AND_PUBLISH`, `HOMEPAGE_PLACEMENT` sau `FACEBOOK_SHARE`. Pentru celelalte trei, doar site-urile cu o ofertă activă de tipul cerut apar în listă — publicarea simplă e serviciul de bază, îl are orice site cu preț. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/catalog?page=1&campaign=SEO' \
--header 'Authorization: Bearer bk_live_…'Câmpuri din răspuns
entries[].id | uuid | Id-ul publicației. Cu el se cere publicarea. |
entries[].metrics | object | DR, DA, PA și numărul de backlinkuri. `null` unde nu avem citire. |
entries[].audit | object | null | `flagged` e singurul câmp care schimbă o decizie: un site marcat de Google ca periculos n-ar trebui cumpărat. |
entries[].preturiCampanii | object | Prețul publicării, pe fiecare tip de campanie acceptat, în bani. |
entries[].access | object | `OWN` (site propriu), `CREDIT` (acoperit de un pachet), `MONEY` (se plătește din sold), `UNAVAILABLE`. Când `packages` are mai multe intrări, publicarea cere `packageTypeId`. |
entries[].masurat | object | Ce am măsurat noi din propriile comenzi: media orelor până la apariție, câte publicări verificate mai sunt în regulă. `null` unde n-am publicat destul. |
Răspuns
{
"entries": [
{
"id": "3f1c…",
"name": "Exemplu.ro",
"url": "https://exemplu.ro",
"language": "ro",
"niches": ["Auto"],
"metrics": { "dr": 34, "da": 41, "pa": 38, "backlinks": 12045 },
"audit": { "flagged": false, "threatTypes": [], "perfMobile": 62, "crux": "AVERAGE" },
"acceptedCampaigns": ["SEO", "BRAND_AWARENESS"],
"preturiCampanii": { "SEO": 18000, "BRAND_AWARENESS": 15000 },
"deliveryDays": 2,
"automat": true,
"linkPolicy": "DOFOLLOW",
"maxLinks": 2,
"access": { "kind": "MONEY", "packages": [], "priceCents": 18000, "currency": "RON", "affordable": true }
}
],
"page": 1,
"pageSize": 25,
"total": 184
}GET/partner/catalog/{siteId}O publicațieCATALOG_READ
Aceeași formă ca o intrare din listă, plus detaliile care nu încap în listare: condițiile pe fiecare tip de campanie, temele acceptate, ofertele active — redactare, plasare pe homepage, distribuire pe Facebook. Lipsa unei oferte dintr-un tip înseamnă că site-ul nu-l vinde.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/catalog/3f1c…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"id": "3f1c…",
"name": "Exemplu.ro",
"acceptedTopics": "Fără politică, fără jocuri de noroc.",
"offers": [
{ "serviceType": "WRITE_AND_PUBLISH", "words": 700, "priceCents": 25000, "currency": "RON" },
{ "serviceType": "HOMEPAGE_PLACEMENT", "words": null, "priceCents": 5000, "currency": "RON" }
]
}GET/partner/campaign-typesTipurile de campanieCATALOG_READ
Tipul nu e o etichetă, ci un set de condiții pe care articolul le trece sau nu la trimitere. Verificarea lor înainte scutește un lanț de respingeri, una câte una.
Ce tipuri acceptă un site anume e altă întrebare, și stă în catalog, la `acceptedCampaigns`.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/campaign-types' \
--header 'Authorization: Bearer bk_live_…'Răspuns
[
{
"id": "SEO",
"rezumat": "Advertorial optimizat: articolul susține un cuvânt-cheie și trimite către pagina promovată.",
"obligatoriu": [
"Cel puțin un link către site-ul promovat",
"Cuvânt-cheie declarat, prezent în text",
"Minimum 300 de cuvinte"
],
"interzis": ["Ancore fără conținut („click aici")"]
},
{
"id": "CASINO",
"rezumat": "Jocuri de noroc: se publică doar cu marcajele cerute de lege, pe site-urile care acceptă.",
"obligatoriu": ["Marcajul 18+", "Avertismentul de joc responsabil", "Licența ONJN a operatorului"],
"interzis": ["Promisiuni de câștig"]
}
]Articole
Un articol intră în platformă pe patru căi: scris de tine în HTML, importat dintr-un document, luat dintr-un folder Drive, sau scris de platformă de la un brief. Toate produc același lucru — o ciornă care se poate publica.
POST/partner/articlesCreează un articol din HTMLARTICLES_WRITE
Calea directă, când ai deja textul. Marcajul trece prin aceeași sanitizare ca importul: scripturile, stilurile inline și elementele care nu-și au locul într-un articol se elimină, iar ce s-a scos apare în răspuns.
Câmpurile de optimizare se normalizează, nu se resping: dacă trimiți cinci cuvinte-cheie despărțite prin virgulă, se păstrează primul; dacă trimiți zece etichete, se păstrează primele trei. Un articol susține un singur termen, iar douăzeci de etichete înseamnă pagini de arhivă cu un singur text.
Corpul cererii
title· obligatoriu | string(3–300) | Titlul articolului. |
contentHtml· obligatoriu | string(≤500 000) | Corpul articolului, în HTML. |
focusKeyword | string | Cuvântul-cheie pentru care se optimizează. **Unul singur** — dintr-o listă se păstrează primul. |
tags | string[] | Cel mult trei, oricâte s-ar trimite. Se acceptă și lista despărțită prin virgulă într-un singur element. |
metaDescription | string(≤320) | Descrierea din rezultatele căutării. |
slug | string | Adresa propusă a articolului. WordPress o poate schimba la coliziune; ce a ieșit efectiv se citește înapoi și se păstrează pe publicare. |
excerpt | string(≤1000) | Rezumatul folosit de teme în listări și pe pagina de categorie. |
featuredImageAlt | string(≤300) | Textul alternativ al imaginii reprezentative. Lipsa lui e una dintre constatările pe care le raportează analiza SEO. |
seoTitle | string(≤200) | Titlul din rezultatele căutării, când diferă de cel al articolului. |
featuredImageUrl | string | Imaginea reprezentativă. Poate fi adresa întoarsă de `POST /partner/media`. |
campaignId | uuid | Campania în care intră articolul. |
idempotencyKey | string(≤200) | Aleasă de tine — un UUID generat local e suficient. Trimisă din nou, cu aceeași organizație, întoarce articolul deja creat în loc să facă unul nou. Fără efect la `PATCH`. Recomandat pentru orice cod care poate retrimite cererea după un timeout. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{
"title": "Ce verifici la o firmă de amenajări înainte să semnezi",
"contentHtml": "<p>Un contract de amenajare se semnează o dată…</p>",
"focusKeyword": "amenajări interioare",
"tags": ["amenajari", "interioare", "design"],
"idempotencyKey": "9f2c7e40-9b1e-4c2a-8b0e-2e6a1f9c3d10"
}'Răspuns
{
"id": "2668…",
"title": "Ce verifici la o firmă de amenajări înainte să semnezi",
"status": "DRAFT",
"focusKeyword": "amenajări interioare",
"tags": ["amenajari", "interioare", "design"],
"idempotencyKey": "9f2c7e40-9b1e-4c2a-8b0e-2e6a1f9c3d10",
"version": 1
}POST/partner/articles/importImportă un documentARTICLES_WRITE
Se acceptă documentele Word (`.docx`, `.docm`, `.doc`, `.dot`, `.dotx`), LibreOffice și OpenOffice (`.odt`, `.ott`, `.fodt`, `.sxw`), `.rtf`, `.wpd`, `.wps`, plus `.html` și `.htm`. Tot ce nu e `.docx` trece prin LibreOffice, deci durează câteva secunde în plus.
**Ordinea fișierelor contează**: primul e documentul, al doilea — opțional — imaginea reprezentativă. Nu se poate distinge după numele câmpului, fiindcă fiecare își numește fișierele cum vrea.
Imaginile din corpul documentului se aduc în galerie și se rescriu în text; cele care nu se pot aduce apar în `images.skipReasons`, cu motivul. Metadatele — descriere, etichete — se completează în fundal, după import.
Formular (multipart/form-data)
(primul fișier)· obligatoriu | file | Documentul. |
(al doilea fișier) | file | Imaginea reprezentativă, pentru documentele care n-au niciuna în corp. |
campaignId | uuid | Campania în care intră articolul. |
folderId | uuid | Folderul din galerie în care ajung imaginile documentului. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles/import' \
--header 'Authorization: Bearer bk_live_…' \
--form 'document=@articol.docx' \
--form 'imagine=@coperta.jpg' \
--form 'campaignId=8b2e…'Câmpuri din răspuns
articles[] | array | Articolele create, cu titlu și număr de cuvinte. |
images | object | Câte imagini au intrat, câte s-au refolosit, câte au fost convertite și de ce au fost ignorate celelalte. |
sanitized | object | Ce a eliminat sanitizarea. Dacă articolul arată altfel decât documentul, aici scrie de ce. |
Răspuns
{
"articles": [{ "id": "2668…", "title": "Ce verifici la o firmă de amenajări", "wordCount": 712 }],
"images": { "imported": 3, "reused": 1, "skipped": 1, "converted": 1, "skipReasons": ["WMF nu poate fi convertit"] },
"sanitized": { "elements": ["script"], "attributes": ["style"], "links": 2, "relativeLinks": 1 },
"warnings": []
}GET/partner/articles/driveCe e într-un folder DriveARTICLES_WRITE
Folderul trebuie partajat „oricine cu linkul": platforma îl citește cu contul ei, clientul nu se loghează nicăieri.
Listarea e un pas separat de import dinadins. Un folder are adesea și ciorne, și versiuni vechi, și documentul bun — iar importul a tot ce se găsește produce zece articole din care se șterg nouă.
Parametri
link· obligatoriu | string | Linkul folderului, așa cum îl dă Google. |
Exemplu
curl --location --get 'https://app.comunicate.eu/api/v1/partner/articles/drive' \
--data-urlencode 'link=https://drive.google.com/drive/folders/1AbC…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"folderId": "1AbC…",
"fisiere": [
{ "id": "1x…", "nume": "Advertorial final.docx", "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "fel": "document", "marimeOcteti": 84213 },
{ "id": "1y…", "nume": "coperta.jpg", "mimeType": "image/jpeg", "fel": "imagine", "miniatura": "https://…" }
]
}POST/partner/articles/driveImportă din folderul DriveARTICLES_WRITE
Cel mult cincizeci de documente odată. Un document stricat nu oprește restul: rezultatul are o intrare per fișier, cu numărul de articole sau cu eroarea lui.
Imaginea aleasă se descarcă o singură dată și se folosește pentru toate documentele care n-au niciuna în corp.
Corpul cererii
link· obligatoriu | string | Același link de folder ca la listare. Fiecare fișier cerut se verifică împotriva lui: descărcarea se face cu contul platformei, care vede și folderele altor clienți, deci ruta nu acceptă identificatori Drive oarecare. |
fisiere· obligatoriu | array | Documentele alese, cu `id`, `nume` și `mimeType` din listare. |
imagineId | string | Id-ul unei imagini din același folder, folosită ca reprezentativă. |
campaignId | uuid | Campania în care intră articolele. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles/drive' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{
"link": "https://drive.google.com/drive/folders/1AbC…",
"fisiere": [{ "id": "1x…", "nume": "Advertorial final.docx", "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }],
"imagineId": "1y…"
}'Răspuns
{
"rezultate": [
{ "nume": "Advertorial final.docx", "articole": 1 },
{ "nume": "Ciorna veche.doc", "eroare": "Fișierul nu e un document valid sau e protejat cu parolă" }
]
}PATCH/partner/articles/{articleId}Modifică un articolARTICLES_WRITE
Toate câmpurile de la creare, toate opționale. Se trimit doar cele care se schimbă. Aceleași normalizări: un cuvânt-cheie, cel mult trei etichete.
`version` crește **doar când se schimbă titlul sau conținutul**, nu la orice modificare. Ea intră în cheia de idempotență a publicării: un articol corectat și retrimis e o publicare nouă, pe când o etichetă schimbată nu produce alt articol pe site.
Un articol aflat în curs de publicare nu se poate modifica: se întoarce `409`. Worker-ul lucrează pe conținutul citit la începutul procesului, iar o modificare acum ar pune pe site altceva decât ce e în platformă.
Exemplu
curl --location --request PATCH 'https://app.comunicate.eu/api/v1/partner/articles/2668…' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{ "metaDescription": "Ce verifici înainte să semnezi contractul." }'Răspuns
{
"id": "2668…",
"metaDescription": "Ce verifici înainte să semnezi contractul.",
"version": 2
}GET/partner/articlesLista articolelorARTICLES_READ
Articolele organizației, cel mai recent modificat primul. Paginată pe cursor: `nextCursor` din răspuns se retrimite ca `cursor` pentru pagina următoare, `null` la ultima.
Parametri
cursor | uuid | Id-ul ultimului articol văzut. Lipsă la prima pagină. |
limit | int (implicit 50, cel mult 200) | Câte articole pe pagină. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles?limit=50' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"items": [
{ "id": "2668…", "title": "Ce verifici la o firmă de amenajări", "status": "DRAFT", "version": 2 }
],
"nextCursor": "9a11…"
}GET/partner/articles/{articleId}Un articolARTICLES_READ
Articolul întreg, cu textul lui. Aici se vede și dacă o ciornă cerută prin `redactare` s-a terminat: `redactedAt` completat înseamnă gata, iar `redactionFindings` spune ce a rămas nerezolvat.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles/e056…' \
--header 'Authorization: Bearer bk_live_…'Câmpuri din răspuns
redactedAt | datetime | null | Când s-a terminat de scris. `null` cât timp e la coadă. |
redactionFindings | array | Ce n-a ieșit bine, cu `cod` și `mesaj`. Un articol cu constatări blocante se poate trimite, dar are șanse mari să fie respins de publisher. |
suggestedCampaignType | string | null | Ce fel de articol pare a fi, după ce l-am citit. Separat dinadins de tipul comandat: un articol clasificat „SEO" dar cumpărat ca mențiune de brand e o întrebare de pus înainte de plată, nu o corectură făcută pe la spate. |
classificationConfidence | int | null | Din 100. Regulile singure dau siguranță mare; când a fost nevoie de model ca să despartă doi candidați, e mai mică — și se vede. |
classificationReason | string | null | Motivul, scris pentru un om. Se poate contrazice. |
version | int | Crește doar la schimbarea titlului sau a conținutului. Intră în cheia de idempotență a publicării. |
status | string | `DRAFT`, `IN_REVIEW`, `APPROVED`, `SCHEDULED`, `PUBLISHING`, `PUBLISHED`, `FAILED`, `REJECTED`, `ARCHIVED`. |
createdViaApi | boolean | A intrat printr-o cheie, nu din interfață. Tot ce creezi prin API apare în cont ca orice altceva — la Articole, la Campanii, la Publicări — cu semnul ăsta lângă, ca să se vadă ce a făcut integrarea. |
Răspuns
{
"id": "e056…",
"title": "Cum alegi un service auto autorizat pentru garanția mașinii",
"contentHtml": "<h2>…</h2>",
"redactedAt": "2026-09-03T14:22:10.000Z",
"redactionFindings": [
{ "cod": "cifre-nesustinute", "mesaj": "Textul dă cifre care nu se află printre datele primite: 250, 400.", "blocant": true }
]
}Comenzi de redactare
Prin API nu se pornește nicio generare. Trimiți o comandă, o vedem, o scriem sau o refuzăm cu motiv, iar tu afli rezultatul pe aceeași cale. Motivul e simplu: o cheie rulează într-un script și poate cere o mie de articole peste noapte. Iar un text scris de model, livrat automat și nevăzut de nimeni de la noi, pleacă spre publisheri sub numele nostru.
POST/partner/redactareComandă un articolARTICLES_WRITE
Răspunsul e comanda, cu `status: "NOUA"`. Când e gata, aceeași comandă are `status: "LIVRATA"` și `articleId` completat.
**Se plătește din sold, nu din credite.** Un credit e „o publicare inclusă" dintr-un pachet, nu ore de scris. Prețul se rezervă în clipa în care intră comanda și se consumă la livrare; un refuz, o anulare sau o scriere eșuată la noi îl eliberează integral. Fără sold, comanda e respinsă cu `400` și nu se creează deloc — mai bine un refuz la intrare decât un articol scris pe datorie.
`fapte` e singurul câmp măsurat care schimbă calitatea textului. Cu datele clientului în față, articolul le folosește cu cifrele lor exacte; fără ele rămâne general — corect, dar mai puțin util. Regula e strictă: **doar ce apare acolo are voie să apară în articol ca cifră sau ca nume**.
Corpul cererii
tema· obligatoriu | string(10–200) | O frază, cum ai spune-o unui redactor. Din ea iese și titlul. |
campaignType· obligatoriu | string | Unul dintre tipurile din `campaign-types`. |
cuvinte· obligatoriu | 500 | 700 | 1000 | 1500 | Lungimea țintă. |
cuvantCheie | string | Unul singur, una–patru cuvinte. |
brand | string | Brandul promovat. |
adresaPromovata | string | Adresa către care duce articolul. |
fapte | string[] (≤20) | Câte un lucru pe rând: prețuri, ani de activitate, certificări, cine poate fi citat. |
campaignId | uuid | Campania în care intră articolul livrat. |
note | string(≤2000) | Orice altceva vrei să ne spui despre comandă. |
idempotencyKey | string(≤200) | Aleasă de tine — un UUID generat local e suficient. Trimisă din nou, cu exact aceleași date, întoarce comanda deja creată în loc să facă una nouă. Recomandat pentru orice cod care poate retrimite cererea după un timeout: fără cheie, o retrimitere creează o a doua comandă și o a doua rezervare de bani. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/redactare' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{
"tema": "Cum alegi un service auto autorizat pentru garanția mașinii",
"campaignType": "SEO",
"cuvinte": 700,
"cuvantCheie": "service auto autorizat",
"brand": "Service Exemplu",
"fapte": ["Service-ul lucrează din 2009.", "Revizia de bază costă 350 de lei."],
"idempotencyKey": "9f2c7e40-9b1e-4c2a-8b0e-2e6a1f9c3d10"
}'Răspuns
{
"id": "7c1f…",
"status": "NOUA",
"tema": "Cum alegi un service auto autorizat pentru garanția mașinii",
"campaignType": "SEO",
"cuvinte": 700,
"priceCents": 3900,
"currency": "RON",
"chargedCents": null,
"articleId": null,
"rejectionReason": null,
"idempotencyKey": "9f2c7e40-9b1e-4c2a-8b0e-2e6a1f9c3d10",
"createdAt": "2026-09-03T12:00:00.000Z",
"deliveredAt": null
}GET/partner/redactare/{orderId}Starea unei comenziARTICLES_READ
Stările: `NOUA` (a intrat), `IN_LUCRU` (cineva de la noi a luat-o), `LIVRATA` (`articleId` completat, `chargedCents` spune cât s-a încasat), `REFUZATA` (cu `rejectionReason`), `ANULATA`, `ESUATA` (scrierea n-a reușit la noi). În ultimele trei, suma rezervată se întoarce în sold.
`articleId` se întoarce **numai la livrare**. Cât timp se scrie, ciorna e a noastră: dacă modelul a scos ceva slab, se rescrie sau se refuză, fără ca cineva să fi apucat s-o trimită mai departe.
Un webhook pe `redaction.delivered` scutește interogarea în buclă — între cerere și articol trece o revizie făcută de un om, iar durata ei nu se poate prezice.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/redactare/7c1f…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"id": "7c1f…",
"status": "LIVRATA",
"articleId": "e056…",
"priceCents": 3900,
"chargedCents": 3900,
"deliveredAt": "2026-09-03T15:20:00.000Z"
}GET/partner/redactareComenzile taleARTICLES_READ
`status` filtrează. Cele mai noi primele.
Parametri
status | string | `NOUA`, `IN_LUCRU`, `LIVRATA`, `REFUZATA`, `ANULATA`. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/redactare?status=LIVRATA' \
--header 'Authorization: Bearer bk_live_…'Răspuns
[{ "id": "7c1f…", "status": "LIVRATA", "articleId": "e056…" }]POST/partner/redactare/{orderId}/anuleazaRenunță la o comandăARTICLES_WRITE
Numai cât timp e `NOUA`. Odată ce cineva de la noi a luat-o, munca e făcută sau în curs, iar anularea nu mai e a ta: se întoarce `409`, iar închiderea rămâne un refuz cu motiv, din partea noastră.
Exemplu
curl --location --request POST 'https://app.comunicate.eu/api/v1/partner/redactare/7c1f…/anuleaza' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{ "id": "7c1f…", "status": "ANULATA" }Verificări
Ce se poate spune despre un articol fără să cheme niciun model: analiza SEO și potrivirea cu tipul de campanie. Amândouă sunt pe reguli, deci nu costă, nu sunt limitate și dau același răspuns de fiecare dată. Ele **raportează**; nu corectează nimic. Completarea rămâne a ta, printr-un `PATCH`.
GET/partner/articles/{articleId}/seoAnaliza SEOARTICLES_READ
Scor, constatări, statistici și structura de titluri. Scorul e procentul de verificări trecute, nu o promisiune de poziționare.
Ce contează în răspuns e `issues`: fiecare are un `code`, o severitate și un mesaj care spune ce lipsește. Ce se repară — titlu prea lung, descriere lipsă, imagini fără text alternativ — se scrie cu `PATCH /partner/articles/{id}`.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles/2668…/seo' \
--header 'Authorization: Bearer bk_live_…'Câmpuri din răspuns
score | int | 0–100, procentul verificărilor trecute. |
issues[] | array | `code`, `severity` și `message`. |
stats | object | Cuvinte, titluri, imagini fără text alternativ, linkuri interne și externe, densitatea cuvântului-cheie, lungimile titlului, descrierii și slugului. |
outline | array | Structura de titluri, cu nivelul fiecăruia. |
Răspuns
{
"score": 78,
"issues": [
{ "code": "meta-description-missing", "severity": "warning", "message": "Articolul n-are descriere meta.", "autoFixable": false }
],
"stats": { "wordCount": 712, "headingCount": 5, "imageCount": 2, "imagesWithoutAlt": 1, "internalLinks": 0, "externalLinks": 2, "keywordCount": 6, "keywordDensity": 0.84, "titleLength": 74, "metaLength": 0, "slugLength": 0 },
"outline": [{ "level": 2, "text": "Devizul, pe articole" }]
}GET/partner/articles/{articleId}/potrivireSe potrivește cu tipul de campanie?ARTICLES_READ
Altă întrebare decât analiza SEO: aceea spune cât de bine e scris textul, asta spune dacă e articolul **comandat**. Un text impecabil SEO e un refuz sigur într-o campanie de mențiune de brand, fiindcă poartă linkuri.
Verificarea oprește oricum comanda la trimitere. Fără ruta asta, singura cale de a afla era să încerci — articol cu articol, din erori. Răspunsul include și `cerinte`, ca mesajul către client să nu fie doar „nu se potrivește".
Când articolul e într-o campanie legată de un tip, verdictul vine oricum pe articol, în `potrivireCampanie`; asta e pentru un tip pe care încă nu l-ai ales.
Parametri
campaignType | string | Tipul verificat. Implicit `SEO`. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles/2668…/potrivire?campaignType=BRAND_MENTION' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"potrivit": false,
"constatari": [
{ "severitate": "BLOCANT", "mesaj": "Articolul are 2 linkuri externe; o mențiune de brand nu duce niciun link." }
],
"cerinte": {
"rezumat": "Mențiune: brandul apare în text, fără niciun link.",
"obligatoriu": ["Numele brandului în text", "Minimum 200 de cuvinte"],
"interzis": ["Orice link extern"]
}
}GET/partner/articles/{articleId}/revisionsVersiunile anterioareARTICLES_READ
Textul întreg al fiecărei versiuni înlocuite. Se scrie o revizie la fiecare schimbare de titlu sau conținut — adică exact când crește `version`.
Există pentru momentul în care publisherul spune că articolul de pe site nu mai seamănă cu ce a acceptat: răspunsul cere textul întreg, nu o mostră din jurnal.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/articles/2668…/revisions' \
--header 'Authorization: Bearer bk_live_…'Câmpuri din răspuns
curenta | object | Versiunea de acum, ca să existe cu ce compara prima revizie. |
revizii[] | array | Cel mult cincizeci, de la cea mai recentă. `changedBy` e gol când schimbarea a venit printr-o cheie de API. |
Răspuns
{
"curenta": { "version": 3, "title": "Titlul de acum", "contentHtml": "<p>…</p>" },
"revizii": [
{ "id": "aa11…", "version": 2, "title": "Titlul dinainte", "contentHtml": "<p>…</p>", "changedBy": null, "createdAt": "2026-09-01T10:00:00.000Z" }
]
}DELETE/partner/articles/{articleId}Șterge un articolARTICLES_WRITE
Răspunde `204` la reușită. Refuzat cu `409` dacă articolul are publicări: istoricul unei publicări trebuie să poată arăta ce s-a trimis, iar un articol publicat se scoate din listă, nu din bază.
Exemplu
curl --location --request DELETE 'https://app.comunicate.eu/api/v1/partner/articles/2668…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
(fără corp — 204 No Content)Galerie
POST/partner/mediaÎncarcă o imagineMEDIA_WRITE
JPEG, PNG, GIF, WebP sau AVIF. Tipul se determină din conținut, nu din extensie sau din antet: un fișier deghizat e respins.
Aceeași imagine încărcată de două ori nu se dublează — se recunoaște după amprentă și se întoarce cea existentă. Adresa din răspuns se poate pune direct în `featuredImageUrl` la un articol.
Formular (multipart/form-data)
(fișier)· obligatoriu | file | Imaginea. |
folderId | uuid | Folderul din galerie. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/media' \
--header 'Authorization: Bearer bk_live_…' \
--form 'file=@coperta.jpg'Răspuns
{
"id": "4e4a…",
"filename": "coperta.jpg",
"url": "/api/v1/organizations/adb4…/media/4e4a…/content",
"mimeType": "image/jpeg",
"sizeBytes": 84213,
"width": 1200,
"height": 630
}Publicări
O publicare leagă un articol de o publicație. E singurul loc din API care cheltuie credite sau bani.
POST/partner/publicationsCere publicareaPUBLICATIONS_WRITE
Un articol, una sau mai multe publicații.
**Ambiguitatea de plată nu se ghicește.** Când mai multe pachete deținute acoperă același site, cererea se respinge cu `409` și lista opțiunilor; integrarea alege și reia cu `packageTypeId`. A ghici ar însemna să consumăm din pachetul greșit, iar asta nu se poate întoarce.
Corpul cererii
articleId· obligatoriu | uuid | Articolul de publicat. |
siteIds· obligatoriu | uuid[] | Publicațiile alese din catalog. |
campaignType· obligatoriu | string | Tipul campaniei. Trebuie să fie printre `acceptedCampaigns` ale site-ului. |
packageTypeId | uuid | Din ce pachet se plătește, când mai multe acoperă site-ul. |
licenseNumber | string(3–100) | Licența ONJN a operatorului. Obligatorie la `campaignType: "CASINO"` — publicitatea la jocuri de noroc trebuie s-o afișeze, iar platforma o scrie singură pe articol înainte de trimitere. |
extrasBySite | object | Extrase cerute per site — `"HOMEPAGE_PLACEMENT"`, `"FACEBOOK_SHARE"`, cel mult amândouă. Cheia e `siteId`, valoarea o listă de tipuri; fiecare cerut trebuie să aibă o ofertă activă pe site (vezi `GET /partner/catalog`), altfel cererea se respinge. |
scheduledFor | datetime | Când să apară, dacă nu imediat. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/publications' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{
"articleId": "2668…",
"siteIds": ["3f1c…"],
"campaignType": "SEO",
"extrasBySite": { "3f1c…": ["HOMEPAGE_PLACEMENT"] }
}'Răspuns
{
"publications": [
{
"id": "9a3b…",
"siteId": "3f1c…",
"status": "AWAITING_APPROVAL",
"amountCents": 18000,
"paymentSource": "MONEY",
"extras": [
{ "type": "HOMEPAGE_PLACEMENT", "priceCents": 5000, "currency": "RON", "chargedCents": 5000 }
]
}
]
}GET/partner/publicationsLista publicărilorPUBLICATIONS_READ
Toate publicările organizației. `status` filtrează. Paginată pe cursor: `nextCursor` din răspuns se retrimite ca `cursor` pentru pagina următoare, `null` la ultima.
Pentru urmărirea stării, webhookurile sunt de preferat: `publication.published` vine cu adresa articolului în clipa în care apare, iar interogarea în buclă consumă din limita de cereri fără să afle nimic în plus.
Parametri
status | string | `DRAFT`, `AWAITING_CONTENT`, `AWAITING_APPROVAL`, `APPROVED`, `QUEUED`, `UPLOADING_MEDIA`, `CREATING_POST`, `VERIFYING`, `SCHEDULED`, `PUBLISHED`, `RETRYING`, `FAILED`, `CANCELLED`. |
cursor | uuid | Id-ul ultimei publicări văzute. Lipsă la prima pagină. |
limit | int (implicit 50, cel mult 200) | Câte publicări pe pagină. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/publications?status=PUBLISHED&limit=50' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"items": [
{ "id": "9a3b…", "status": "PUBLISHED", "publishedUrl": "https://exemplu.ro/articol", "publishedAt": "2026-09-01T08:14:00.000Z" }
],
"nextCursor": null
}GET/partner/publications/{publicationId}O publicarePUBLICATIONS_READ
Publicarea întreagă, cu istoricul ei de evenimente și cu motivul, când a eșuat sau a fost respinsă.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/publications/9a3b…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"id": "9a3b…",
"status": "FAILED",
"lastError": "Site-ul a răspuns 401 la autentificare",
"attempts": 3,
"publishedUrl": null
}Campanii
Gruparea articolelor și a publicărilor pe client sau pe proiect. Echivalentul „proiectelor" din alte platforme.
GET/partner/campaignsLista campaniilorCAMPAIGNS_READ
`status` filtrează. Paginată pe cursor: `nextCursor` din răspuns se retrimite ca `cursor` pentru pagina următoare, `null` la ultima.
Parametri
status | string | `DRAFT`, `ACTIVE`, `PAUSED`, `COMPLETED`, `ARCHIVED`. |
cursor | uuid | Id-ul ultimei campanii văzute. Lipsă la prima pagină. |
limit | int (implicit 50, cel mult 200) | Câte campanii pe pagină. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/campaigns?limit=50' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"items": [{ "id": "8b2e…", "name": "Client Exemplu — Q3", "status": "ACTIVE" }],
"nextCursor": null
}POST/partner/campaignsCreează o campanieCAMPAIGNS_WRITE
Corpul e strict: un câmp pe care nu-l cunoaște e respins cu `400`, nu ignorat în tăcere. Mai bine o eroare la prima încercare decât o setare care pare aplicată și nu e.
Corpul cererii
name· obligatoriu | string(2–150) | Numele campaniei. |
campaignType | string | Leagă campania de un tip. Fiecare articol care intră în ea e verificat automat împotriva cerințelor tipului, iar verdictul apare pe articol în `potrivireCampanie`. Nu blochează scrierea — refuzul rămâne la comandă, unde e și plata. |
clientName | string(≤150) | Eticheta clientului final. E text liber: clienții n-au cont în platformă. |
objective | string(≤500) | Ce urmărește campania. |
notes | string(≤2000) | Însemnări interne. |
startsAt | datetime | ISO 8601. Trebuie să fie înaintea lui `endsAt`. |
endsAt | datetime | ISO 8601. |
budgetCents | int | Buget de urmărit, în bani. Nu e un portofel separat: banii stau în contul organizației. |
budgetCredits | int | Limită de credite consumate în campanie. |
allowedSiteIds | uuid[] (≤500) | Site-urile permise. Gol înseamnă „oricare din catalog". |
idempotencyKey | string(≤200) | Aleasă de tine — un UUID generat local e suficient. Trimisă din nou, cu aceeași organizație, întoarce campania deja creată în loc să facă una nouă. Recomandat pentru orice cod care poate retrimite cererea după un timeout. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/campaigns' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{
"name": "Client Exemplu — Q3",
"clientName": "Exemplu SRL",
"campaignType": "SEO",
"idempotencyKey": "9f2c7e40-9b1e-4c2a-8b0e-2e6a1f9c3d10"
}'Răspuns
{
"id": "8b2e…",
"name": "Client Exemplu — Q3",
"clientName": "Exemplu SRL",
"campaignType": "SEO",
"status": "ACTIVE",
"idempotencyKey": "9f2c7e40-9b1e-4c2a-8b0e-2e6a1f9c3d10",
"articleCount": 0
}GET/partner/campaigns/{campaignId}O campanieCAMPAIGNS_READ
Campania, cu articolele și publicările ei.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/campaigns/8b2e…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{ "id": "8b2e…", "name": "Client Exemplu — Q3", "articles": 12, "publications": 34 }PATCH/partner/campaigns/{campaignId}Modifică o campanieCAMPAIGNS_WRITE
Toate câmpurile de la creare, toate opționale. Se trimit doar cele care se schimbă.
Cel mai des folosit pentru `campaignType`: leagă o campanie deja creată de un tip, sau o scoate cu `null`. Fiecare articol din campanie e reverificat automat împotriva noului tip — vezi `potrivireCampanie` pe articol.
Exemplu
curl --location --request PATCH 'https://app.comunicate.eu/api/v1/partner/campaigns/8b2e…' \
--header 'Authorization: Bearer bk_live_…' \
--header 'Content-Type: application/json' \
--data '{ "campaignType": "BRAND_AWARENESS" }'Răspuns
{ "id": "8b2e…", "name": "Client Exemplu — Q3", "campaignType": "BRAND_AWARENESS", "articleCount": 12 }DELETE/partner/campaigns/{campaignId}Șterge o campanieCAMPAIGNS_WRITE
Răspunde `204` la reușită. Refuzată cu `409` dacă e activă și are articole — aceeași regulă ca în interfață: o campanie cu istoric se arhivează (`PATCH` cu `status: "ARCHIVED"`), nu se șterge.
Exemplu
curl --location --request DELETE 'https://app.comunicate.eu/api/v1/partner/campaigns/8b2e…' \
--header 'Authorization: Bearer bk_live_…'Răspuns
(fără corp — 204 No Content)Sold și rapoarte
GET/partner/balanceSoldul disponibilBALANCE_READ
Pachetele deținute, cu creditul rămas pe fiecare, și soldul în bani. Se verifică înainte de a cere publicarea — altfel integrarea află din eroare, după ce a construit deja articolul.
`available` scade rezervările: o publicare în curs a blocat deja creditul, chiar dacă nu l-a consumat.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/balance' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"credits": [
{ "packageTypeId": "c1…", "packageName": "Pachet Regional 20", "available": 14, "total": 20 }
],
"money": { "availableCents": 125000, "currency": "RON" }
}GET/partner/reports/summaryRezumatul unei perioadeREPORTS_READ
Implicit ultimele 30 de zile. O integrare care nu trimite interval primește ceva util, nu tot istoricul.
Parametri
from | date | Începutul intervalului. |
to | date | Sfârșitul intervalului. |
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/reports/summary?from=2026-08-01&to=2026-08-31' \
--header 'Authorization: Bearer bk_live_…'Răspuns
{
"from": "2026-08-01T00:00:00.000Z",
"to": "2026-08-31T00:00:00.000Z",
"publications": 42,
"published": 39,
"failed": 3,
"distinctSites": 21,
"creditsUsed": 18,
"moneySpentCents": 372000,
"successRate": 0.93
}GET/partner/reports/publicationsPublicările din intervalREPORTS_READ
Rând cu rând, pentru raportarea către client.
Exemplu
curl --location 'https://app.comunicate.eu/api/v1/partner/reports/publications?from=2026-08-01' \
--header 'Authorization: Bearer bk_live_…'Răspuns
[
{ "publishedAt": "2026-08-14T09:02:00.000Z", "article": "Ce verifici la o firmă de amenajări", "site": "exemplu.ro", "url": "https://exemplu.ro/articol", "campaign": "Client Exemplu — Q3" }
]Webhookuri
Interogarea în buclă a stării unei publicări consumă din limita ta de cereri și află, de cele mai multe ori, că nu s-a schimbat nimic. Un webhook ajunge în clipa în care articolul apare, cu adresa lui. Capetele se configurează din pagina de integrări, nu prin API — cine își schimbă destinația evenimentelor trebuie să fie autentificat ca persoană.
Reîncercări
Un endpoint care nu răspunde cu 2xx (sau nu răspunde deloc) e reîncercat automat, cu pauze din ce în ce mai mari: 30 de secunde, apoi 1, 2, 4, 8, 16, 32 de minute — șapte încercări în total. Dacă tot eșuează, livrarea rămâne vizibilă în pagina de integrări, cu răspunsul primit și motivul, și se poate reîncerca manual de acolo — dacă și reîncercarea manuală eșuează, intră din nou în rotația automată.
Cum se verifică semnătura
Fiecare livrare are `X-Birou-Signature: t=<timp>,v1=<hmac>`. HMAC-SHA256 se calculează peste `<timp>.<corpul brut>`, cu secretul capătului — nu doar peste corp: fără marca de timp, o livrare interceptată ar putea fi retrimisă oricând cu aceeași semnătură validă. Verifică și că `t` e recent.
const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
// comparație în timp constant, apoi: Math.abs(Date.now() / 1000 - Number(t)) < 300Evenimente
article.createdUn articol a fost creat, prin orice cale.article.generatedO ciornă cerută din interfață s-a terminat de scris. Comenzile trimise prin API folosesc `redaction.delivered`, fiindcă între cerere și articol trece și o revizie făcută de un om.article.failedScrierea unei ciorne a eșuat.publication.submittedPublicarea a fost trimisă către publisher.publication.publishedArticolul a apărut pe site. Aici vine adresa lui.publication.failedPublicarea a eșuat sau a fost respinsă.indexing.updatedStarea de indexare a unui articol publicat s-a schimbat.order.acceptedPublisherul a acceptat comanda.redaction.deliveredO comandă de redactare a fost livrată. Vine cu `orderId` și `articleId` — de aici se citește articolul.redaction.rejectedO comandă de redactare a fost refuzată. Vine cu motivul.
Mai departe