Postelio Website Publishing API Specification
Technical requirements for connecting a custom website to Postelio for automated SEO article publishing.
This documentation describes the requirements for integrating a website with Postelio to enable automated SEO article publishing.
Postelio v1 trusts the receiving site's contract. Postelio does not currently fetch or verify sitemap inclusion after publishing.
Overview
The Website channel is for custom websites that implement Postelio Website Publishing API v1.
Postelio is the HTTP client. Your site is the receiving API. After a workspace connects a Website channel, Postelio publishes structured SEO articles to:
POST {endpoint_url}/api/postelio/v1/articles/
A successful Postelio publication means all of the following are true before you
return 200 or 201:
- the article is persisted;
- its public canonical HTTPS URL is live;
- the page is indexable;
- SEO metadata is rendered;
- the article is already included in the site's sitemap;
- the API returns a stable remote
idand canonicalurl.
This specification is implementation-neutral. It does not prescribe a CMS, framework, or hosting model.
Base requirements
- The publish endpoint and every published article URL must use HTTPS.
- Every published article must have a public page (not behind login).
- Every article must have a stable canonical URL. Do not change that URL after a successful response.
- Article pages must be indexable by search engines (not blocked by
noindexor robots rules that hide the page). - Sitemap integration is mandatory for Website channel compatibility.
robots.txtmust reference the sitemap or sitemap index.
Authentication
Postelio sends a Bearer token on every publish request:
Authorization: Bearer <api_token>
Requirements:
- Treat
api_tokenas a secret. - Do not accept the token from the query string, request body, or a public URL.
- Compare the presented token against the stored secret using a constant-time comparison.
- Store the token outside source code, using environment-specific secret management.
Reject missing, empty, or invalid credentials with 401 or
403. Do not echo the token in responses or error bodies.
Publishing endpoint
POST /api/postelio/v1/articles/
If the Website channel endpoint_url includes a base path, Postelio
joins it structurally:
Configured endpoint_url |
Request URL |
|---|---|
https://www.example.com |
https://www.example.com/api/postelio/v1/articles/ |
https://www.example.com/postelio |
https://www.example.com/postelio/api/postelio/v1/articles/ |
Request type: multipart/form-data.
Postelio waits up to 15 seconds for the HTTP response. Finish persistence, public-page publication, and sitemap inclusion before you respond. Do not require a redirect; Postelio does not follow redirects.
Request fields
Postelio currently sends exactly these fields. Do not require additional fields
for v1 compatibility. Postelio does not send a slug field in API v1.
The receiving website generates the slug from the article title. See
Article slug.
| Field | Type | Required | Limit | Meaning |
|---|---|---|---|---|
external_id |
text | yes | opaque unique string generated by Postelio (v1 sends a UUID) | Idempotency key for this publication |
title |
text | yes | ≤ 200 characters | Article title and page H1 |
body |
text | yes | HTML; generated within the content format character limit (default 12,000 characters for the global website article format) | Article body HTML |
meta_title |
text | yes | ≤ 70 characters | HTML <title> |
meta_description |
text | yes | ≤ 200 characters | HTML meta description |
excerpt |
text | yes | ≤ 500 characters | Short summary / excerpt |
language |
text | yes | short language code, currently up to 8 characters (example: en) |
Article language |
image |
file | no | at most 1 file; image/jpeg, image/png, or image/webp; prepared size ≤ 10 MiB |
Optional hero / featured image |
body is article body HTML. Render it safely according to your trusted
integration model. The article title is the page H1;
body should not require an additional H1.
image is omitted when Postelio has no image for the publication.
When present, it is a standard multipart file field named image.
Article slug
The receiving website generates the slug from the article title. These rules are mandatory for Website Publishing API v1.
Postelio does not send a slug field in API v1. Do not require one,
and do not reject a valid v1 request because slug is absent.
The slug is the stable path segment of the canonical article URL. This specification does not prescribe the rest of the URL path.
Requirements
- Slugs must contain only lowercase ASCII letters (
a-z), digits (0-9), and hyphens (-). - Non-Latin characters must be transliterated into Latin ASCII.
- Accented Latin characters must be normalized to unaccented ASCII letters. Strip diacritics (
Ü/übecomesu). Do not expand them (ümust not becomeue). - Remove unsupported characters.
- Replace whitespace with hyphens so separate words stay separate.
- Collapse repeated hyphens into a single hyphen.
- Remove leading and trailing hyphens.
- Slugs must not be empty.
- When transliteration produces an empty slug, use the deterministic fallback
article. Do not use a random value, a timestamp, orexternal_id. - Slug collisions must produce unique URLs without overwriting existing articles.
- Once assigned, the canonical URL and slug must remain stable.
Generation steps
Apply these steps in order to title:
- Transliterate non-Latin letters to Latin ASCII.
- Normalize accented Latin letters by removing diacritics.
- Lowercase ASCII letters.
- Replace each whitespace run with a single hyphen.
- Remove every remaining character that is not
a-z,0-9, or-. - Collapse repeated hyphens.
- Remove leading and trailing hyphens.
- If the result is empty, use
article.
The following results are required when that slug is not already assigned to another article:
| Title | Slug |
|---|---|
| Как выбрать брокера | kak-vybrat-brokera |
| Über den Devisenmarkt | uber-den-devisenmarkt |
In the Russian example, ы transliterates to y and the
soft sign ь is omitted.
Collisions
If the candidate slug is already assigned to a different article, append the
smallest integer suffix starting at 2 that yields an unused slug:
kak-vybrat-brokera-2, then kak-vybrat-brokera-3, and so on.
Apply the same suffix rule to the fallback slug article.
Do not overwrite the existing article. Do not change its slug or canonical URL.
Do not reuse its remote id.
A slug collision is only a URL constraint. It does not mean the two publications are the same article.
Stability
Once assigned, keep that slug and canonical URL for the article. An idempotent replay must return the stored URL and must not generate the slug again, even if the title would now produce a different base slug or a different collision suffix.
external_id and idempotency
external_id is created by Postelio. The receiving site must store it.
- It must be unique in the receiving site / integration scope.
- A safe retry with the same
external_idmust not create a duplicate article. - Do not treat Postelio-internal identifiers as part of this contract.
external_idis the only publication identity Postelio sends.
Required behavior:
| Situation | Response |
|---|---|
First valid request for an external_id |
201 Created and persist the article |
Same external_id + same immutable article |
200 OK and return the existing article id / url |
Same external_id but conflicting immutable content |
409 Conflict |
After the first successful publication, the article content bound to that
external_id is immutable. Compare the incoming payload against the
stored article. If it matches, return the existing article. If it differs, return
409. Do not overwrite the existing article.
Publication identity
- A retry with the same
external_idand identical immutable content must return the existing article with HTTP200. - The existing article ID and canonical URL must be reused.
- Never regenerate the slug during an idempotent replay.
- The same
external_idwith different immutable content must return HTTP409. - A previously unseen
external_idrepresents a new article. - Do not deduplicate different
external_idvalues by title, slug, body, or canonical URL. - Successor republish in Postelio creates a new
external_idand therefore a new remote article. Store that article separately. Do not replace an earlier article because the title, body, slug, or canonical URL text matches.
Successful response
Return a JSON object on 200 and 201.
Required fields:
| Field | Rules |
|---|---|
id |
JSON string (no integer coercion). Non-empty after trim. Maximum 128 characters. This is your stable remote article id. |
url |
Absolute https URL. Maximum 2048 characters. No username/password in the URL. Hostname must be a valid DNS name or IP literal. DNS hostnames: no trailing dot; labels 1–63 characters matching [a-z0-9] with optional internal hyphens; total hostname length ≤ 253. Query string and fragment are allowed. |
Optional field:
| Field | Rules |
|---|---|
status |
Optional. If you send it, Postelio stores it only when the value is one of: published, created, ok, success, updated. Other values are ignored. You may omit status. |
Extra JSON keys are ignored. Do not put secrets, tokens, or debug dumps in the success body.
Example:
{
"id": "article_7f3c2a91",
"url": "https://www.example.com/blog/seo-article-slug/"
}
Example with optional status:
{
"id": "article_7f3c2a91",
"url": "https://www.example.com/blog/seo-article-slug/",
"status": "published"
}
url must be the public canonical HTTPS URL of the live article page.
HTTP status contract
| Status | Meaning | Postelio behavior |
|---|---|---|
201 Created |
New article created and fully published | Success when the JSON body is valid |
200 OK |
Idempotent replay of the same external_id and same immutable article |
Success when the JSON body is valid |
400 Bad Request |
Invalid payload | Permanent failure |
401 Unauthorized |
Missing or invalid credentials | Permanent failure |
403 Forbidden |
Credentials rejected | Permanent failure |
409 Conflict |
external_id already exists with different immutable content |
Permanent failure |
429 Too Many Requests |
Rate limited | Retryable. A numeric Retry-After value in seconds is honored |
5xx |
Temporary server error | Retryable |
3xx |
Redirect | Permanent failure. Postelio does not follow redirects |
Other 4xx responses are treated as permanent failures. Do not rely on
a redirect from HTTP to HTTPS, a trailing-slash redirect, or a CDN challenge.
Answer 200 or 201 at the exact request URL.
Article page SEO contract
These requirements apply to the URL returned in the success response.
Requirements
- Public
httpsURL - HTTP
200for ordinary visitors and crawlers - Page is not blocked by
noindex titleis rendered as the article H1meta_titleis rendered as the HTML<title>meta_descriptionis rendered as the HTML meta description- Canonical URL points to the returned article URL
- Language is exposed appropriately by the site (for example
langon<html>or equivalent) - Article
bodyis visible in the rendered HTML - If
imagewas supplied, render it appropriately on the article page
Recommendations
These are recommended for SEO quality. They are not currently verified by Postelio:
- Open Graph metadata (
og:title,og:description,og:url,og:imagewhen an image exists) - Article structured data / JSON-LD
- Accurate published and modified timestamps
- Semantic HTML (
<article>, heading hierarchy after H1)
Sitemap
Sitemap integration is mandatory for Website channel compatibility.
Requirements
- Every successfully published article must be included automatically in the site's sitemap.
- Do not require a manual sitemap update for normal Postelio publishing.
- The article must already be represented in the sitemap before the API returns successful
200or201. - The sitemap must be publicly reachable over HTTPS.
- The sitemap may be a sitemap file or a sitemap index.
- This specification does not prescribe one fixed sitemap path.
robots.txtmust reference the sitemap or sitemap index.- Each article
<loc>must equal the canonical article URL returned asurl. <lastmod>is recommended when the timestamp is correct and current.
Example URL-set entry:
<url>
<loc>https://www.example.com/blog/seo-article-slug/</loc>
<lastmod>2026-09-13</lastmod>
</url>
Important
Postelio v1 does not fetch or verify sitemap inclusion after publishing. The receiving site is responsible for honoring this contract.
robots.txt
robots.txt must include a Sitemap directive pointing at
your actual sitemap or sitemap index location. Do not assume
/sitemap.xml.
User-agent: *
Allow: /
Sitemap: https://www.example.com/<actual-sitemap-location>
Examples of valid locations include /sitemap.xml,
/sitemap_index.xml, or another HTTPS path your site actually serves.
Atomic publication semantics
Required receiving-side sequence, conceptually:
- Authenticate the Bearer token.
- Validate the payload (required fields, types, sizes).
- Resolve
external_id. - Create and persist the article, assigning a unique slug from the title, or reuse the existing article, its slug, and its canonical URL when the retry is identical.
- Make the public canonical page live.
- Ensure the sitemap includes that canonical URL.
- Only then return
201(create) or200(identical replay).
For an idempotent replay, return the existing article only after confirming it is still available under this contract: public canonical page live, indexable, SEO metadata rendered, and sitemap still includes it.
This specification does not mandate a particular database transaction model. Use whatever atomicity your site can implement so that a success response is not returned unless the full contract is met.
Failure behavior
- Return a non-2xx status if the article cannot be published fully.
- Do not return
201or200if persistence, the public page, or sitemap inclusion failed. - Use
409only for anexternal_idconflict with different immutable content. - Use
429for rate limiting. - Use
5xxfor temporary server failures. - Use
400for invalid or incomplete payloads. - Use
401or403for authentication or authorization failure.
Keep error responses stable and free of secrets. You may return JSON, but Postelio does not require a specific error body schema in v1.
Security requirements
- HTTPS only
- Bearer token authentication
- Do not log the Authorization token,
api_token, or other secrets - Validate input sizes and types
- Protect the receiving endpoint from public anonymous write access
- Handle HTML/content safely according to the site's trust model
- Do not echo the Authorization token in responses or errors
- Return stable error responses without secrets
The Website channel is a trusted integration: Postelio sends article HTML your site asked to receive. Sanitize or render that HTML in a way that is safe for your readers.
Reference pseudocode
authenticate(request)
validate_payload(request)
existing = find_article_by_external_id(request.external_id)
if existing:
if same_immutable_content(existing, request):
ensure_public_page_and_sitemap_are_valid(existing)
return 200 {id: existing.id, url: existing.canonical_url}
return 409
article = create_article(request)
article.slug = unique_slug_from_title(request.title)
publish_canonical_page(article)
include_in_sitemap(article)
return 201 {id: article.id, url: article.canonical_url}
unique_slug_from_title follows
Article slug. Call it only when creating an article.
An identical replay returns the stored id and canonical URL and does
not generate the slug again. A different external_id always creates
a separate article, even when the generated slug needs a collision suffix.
Acceptance checklist
Use this list when implementing or reviewing a receiving site:
- [ ] HTTPS endpoint
- [ ] Bearer authentication
- [ ]
POST /api/postelio/v1/articles/ - [ ] Exact request fields supported (
external_id,title,body,meta_title,meta_description,excerpt,language, optionalimage) - [ ] No
slugrequest field; the receiving site generates the slug from the title - [ ] Slug contains only lowercase ASCII letters, digits, and hyphens
- [ ] Non-Latin characters transliterated into Latin ASCII
- [ ] Accented Latin characters normalized
- [ ] Unsupported characters removed, repeated hyphens collapsed, leading and trailing hyphens removed
- [ ] Slug is never empty; empty transliteration uses the deterministic fallback
article - [ ] Slug collisions produce unique URLs without overwriting existing articles
- [ ] Once assigned, the canonical URL and slug remain stable
- [ ]
external_iduniqueness - [ ] Idempotent
200replay reuses the existing article ID, canonical URL, and slug - [ ] Never regenerate the slug during an idempotent replay
- [ ]
409when the sameexternal_idhas different immutable content - [ ] A previously unseen
external_idcreates a new article - [ ] Different
external_idvalues are not deduplicated by title, slug, body, or canonical URL - [ ] Successor republish creates a new
external_idand a new remote article - [ ] Stable public HTTPS canonical URL
- [ ] H1 / title / meta / canonical rendered
- [ ] Page indexable
- [ ] Sitemap automatic
- [ ]
robots.txtreferences sitemap - [ ] Successful response
id/urlvalid - [ ] Optional image handled
- [ ] Redirects not required
- [ ] Secrets not logged
Scope limitations
Website Publishing API v1 does not provide:
- update article
- delete article
- multi-image publishing
- sitemap verification by Postelio
- a client-supplied
slugfield
Do not assume future endpoints or callbacks. Implement the current create/idempotent-replay contract only.