API rewriter.io
Pull published articles from your project through a simple, well-documented read-only API - straight to your own blog. REST, JSON, API key, filtering, sorting and pagination.
Introduction
The API base URL is https://public-api.rewriter.io/v1. All responses are returned in JSON format (snake_case keys), and the article body is available as markdown and html. Every request requires an API key. The API supports read access to published content only.
In the test environment, use the address https://dev-hooks.rewriter.io/v1.
For AI agents and LLMs we publish a machine-readable index of these docs at llms.txt.
Authentication
Pass the key in the Authorization header as a Bearer token. The key is tied to a single project.
# Check which project the key belongs to curl https://public-api.rewriter.io/v1/me \ -H "Authorization: Bearer rw_live_3f8a…"
Limits and versioning
The API is versioned in the path (/v1). The request limit depends on the plan and is counted separately for each API key.
| Plan | Limit / min |
|---|---|
| Start | 60 |
| Solo | 150 |
| Pro | 300 |
| Max | 1,000 |
Responses for limited plans include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. On an unlimited plan, X-RateLimit-* headers are not sent.
- GET requests are accessible from the browser (CORS).
- All timestamps are in ISO 8601 format (UTC).
Errors
Errors are returned with the appropriate HTTP code and a consistent structure:
{
"error": {
"code": "not_found",
"message": "Article not found."
}
}400- invalid query parameters401- missing or invalid API key403- your plan does not include API access404- not found (article unpublished or outside the project)429- rate limit exceeded500- server error
Pagination and sorting
Lists are returned in an envelope with data fields and page metadata:
{
"data": [
"…"
],
"page": 1,
"per_page": 10,
"total": 42
}You control pagination with the page (from 1) and limit (default 10, max. 100) parameters. You set sorting with the sort parameter - a “-” prefix means descending order. Allowed values: published_at, -published_at, title, -title, updated_at, -updated_at.
Project
Returns information about the project tied to the key and the available languages. Useful for verifying the key.
{
"id": "k17a9c…",
"name": "Kawiarnia Pod LipÄ…",
"primary_language": "pl",
"available_languages": [
"pl",
"en"
],
"plan": "Pro",
"rate_limit_per_minute": 300
}Topics
Returns the list of project topics. Use the id field as the value for the topic_id filter on the article list.
{
"data": [
{
"id": "j57b2…",
"title": "Lokalne SEO"
},
{
"id": "j58c1…",
"title": "Porady baristĂłw"
}
]
}Categories
Returns the list of project categories. Use the id field as the value for the category_id filter on the article list. Names follow the language parameter (defaults to the primary language).
{
"data": [
{
"id": "k20a1…",
"name": "Pielęgnacja ogrodu",
"slug": "pielegnacja-ogrodu"
},
{
"id": "k20a2…",
"name": "Porady",
"slug": "porady"
}
]
}List articles
Returns a paginated list of published articles (summary fields, without the body).
Query parameters
topic_id- string, optional. Filter by topic identifier.category_id- string, optional. Filter by category identifier.language- string, optional. Language code; defaults to the project's primary language. A different code returns published translations.q- string, optional. Search by title (case-insensitive).published_after- ISO 8601 date, optional. Only articles published on or after this date.published_before- ISO 8601 date, optional. Only articles published up to this date.sort- string, default -published_at. See the Pagination and sorting section.page- number, default 1.limit- number, default 10, max. 100.
Simply omit a parameter you do not use - an empty value (for example sort=) is treated exactly like a missing parameter.
{
"data": [
{
"id": "m93f7…",
"title": "10 sposobĂłw na lokalne SEO",
"slug": "10-sposobow-na-lokalne-seo",
"language": "pl",
"topic_id": "j57b2…",
"category": {
"id": "k20a1…",
"name": "Pielęgnacja ogrodu",
"slug": "pielegnacja-ogrodu"
},
"status": "published",
"seo_score": 96,
"readability_score": 72,
"fact_check_status": "passed",
"cover_image": "https://cdn.example.com/articles/m93f7/cover.png",
"published_at": "2026-05-21T09:12:00.000Z",
"updated_at": "2026-05-22T10:00:00.000Z"
}
],
"page": 1,
"per_page": 10,
"total": 42
}Get article
Returns the full body of a published article along with SEO metadata. The body is available as Markdown and as an HTML fragment. The :id parameter accepts the full article id; the first-party rewriter.io blog also accepts a slug-8char reference.
Query parameters
language- string, optional. Language code to fetch a published translation instead of the primary-language version.format- markdown or html, optional. By default we return both body fields.
{
"id": "m93f7…",
"title": "10 sposobĂłw na lokalne SEO",
"slug": "10-sposobow-na-lokalne-seo",
"topic_id": "j57b2…",
"category": {
"id": "k20a1…",
"name": "Pielęgnacja ogrodu",
"slug": "pielegnacja-ogrodu"
},
"language": "pl",
"status": "published",
"body_markdown": "## Wstęp\n\nLokalne SEO to…",
"body_html": "<h2>Wstęp</h2>\n<p>Lokalne SEO to…</p>",
"cover_image": "https://cdn.example.com/articles/m93f7/cover.png",
"meta_title": "Lokalne SEO dla kawiarni",
"meta_description": "Praktyczny przewodnik po lokalnym SEO.",
"seo_score": 96,
"readability_score": 72,
"readability_label": "łatwy",
"fact_check_status": "passed",
"uniqueness_score": 98,
"alternate_slugs": [
{
"language": "pl",
"slug": "10-sposobow-na-lokalne-seo"
},
{
"language": "en",
"slug": "10-ways-to-improve-local-seo"
}
],
"json_ld": {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "10 sposobĂłw na lokalne SEO",
"inLanguage": "pl",
"wordCount": 1240,
"…": "…"
},
"published_at": "2026-05-21T09:12:00.000Z",
"updated_at": "2026-05-22T10:00:00.000Z"
}Article in an HTML template
Returns a ready-made HTML page of the published article, wrapped in the project template (text/html instead of JSON). Supports the language parameter. Ideal for embedding directly on a site.
curl https://public-api.rewriter.io/v1/articles/m93f7…/html \
-H "Authorization: Bearer rw_live_3f8a…"Webhooks
Webhooks send signed POST requests to your URL when content events happen in your project. Configure them in the dashboard, under Account → API & integrations.
You can also register them programmatically with just an API key - no dashboard step. See Webhook subscriptions.
Available events
article.published- an article was published.article.scheduled- an article was scheduled for publication.article.unpublished- an article was unpublished.article.archived- an article was archived.article.restored- an article was restored from the archive.article.translated- an article translation was published.topic.created- a new topic was created.headline.created- new article suggestions (Headlines) were generated.
Request body
Every request carries a JSON envelope with id (the delivery id - use it to deduplicate), event, created_at, project_id and data with the event details:
{
"id": "whd_4f2a…",
"event": "article.published",
"created_at": "2026-05-21T09:12:00.000Z",
"project_id": "k17a9c…",
"data": {
"id": "m93f7…",
"title": "10 sposobĂłw na lokalne SEO",
"slug": "10-sposobow-na-lokalne-seo",
"language": "pl",
"status": "published",
"topic_id": "j57b2…",
"category": {
"id": "k20a1…",
"name": "Pielęgnacja ogrodu",
"slug": "pielegnacja-ogrodu"
},
"seo_score": 96,
"readability_score": 72,
"fact_check_status": "passed",
"published_at": "2026-05-21T09:12:00.000Z",
"updated_at": "2026-05-21T09:12:00.000Z"
}
}Signature verification
Every request carries the header X-Rewriter-Webhook-Signature with an HMAC-SHA256 signature over the timestamp (the X-Rewriter-Webhook-Timestamp header), a dot, and the request body, formatted as sha256=<hex>. Recompute it with your secret and compare in constant time.
const ts = req.headers["x-rewriter-webhook-timestamp"];
const sig = req.headers["x-rewriter-webhook-signature"];
const expected = "sha256=" + crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(ts + "." + rawBody)
.digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));Retries and auto-disable
Failed deliveries are retried with increasing backoff (up to 5 attempts), sending the same delivery id on every attempt. After many consecutive failures the webhook is disabled automatically - re-enable it in the dashboard.
Webhook subscriptions
You can register and remove webhook callbacks programmatically with just an API key - no dashboard step. These endpoints power the instant triggers in our Zapier and Make.com apps, and you can use them from your own integration too.
POST and GET additionally require a plan that includes webhooks - otherwise they return 403 forbidden. DELETE is authorized by the API key alone, so tearing a subscription down keeps working after a plan change.
Create a subscription
Registers one callback URL for exactly one event and returns the subscription object with status 201 (it is not wrapped in data). Store the returned id - you pass it back to delete the subscription.
Request body
target_url- required. An HTTPS callback URL on one of the allowed hosts.event- required. One event from the list above.
curl -X POST https://public-api.rewriter.io/v1/webhooks \ -H "Authorization: Bearer rw_live_3f8a…" \ -H "Content-Type: application/json" \ -d '{"target_url":"https://hook.eu2.make.com/abc123","event":"article.published"}'
{
"id": "n41c8…",
"event": "article.published",
"target_url": "https://hook.eu2.make.com/abc123",
"status": "enabled"
}Subscription object
id- the subscription id - pass it to DELETE.event- the event the subscription is registered for.target_url- the registered callback URL.status- enabled or disabled - a subscription is disabled automatically after repeated failed deliveries.
Allowed target_url hosts
target_url must be an HTTPS automation-platform hook URL on one of these hosts:
hooks.zapier.com- Zapier catch hook URLs.hook.make.com, hook.<zone>.make.com- Make.com webhooks, for example hook.eu2.make.com.
Any other host, a non-HTTPS URL, a URL with credentials, or a private, loopback or metadata address is rejected with 400 bad_request.
Sending the same target_url and event pair again does not create a duplicate: the existing subscription is re-enabled and returned with the same id. A project can hold up to 20 subscriptions per platform.
List subscriptions
Returns the subscriptions created with this API key in this project. Subscriptions created with another key, and webhooks configured in the dashboard, are never returned.
{
"data": [
{
"id": "n41c8…",
"event": "article.published",
"target_url": "https://hook.eu2.make.com/abc123",
"status": "enabled"
}
]
}Delete a subscription
Deletes the subscription with the given id. Idempotent: an unknown, already deleted or foreign id returns 200 with deleted: false instead of 404, so teardown never fails.
{
"id": "n41c8…",
"deleted": true
}Errors
400 bad_request- target_url or event missing, unknown event, or a target_url outside the allowed hosts.401 unauthorized- missing or invalid API key.403 forbidden- the plan does not include webhooks, or the limit of 20 subscriptions was reached.
These responses carry the same X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers as the read endpoints.