Start for free
API documentation · v1

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.

Tip: you can generate an API key in the dashboard, under Account → API & integrations. Keep it server-side - do not embed it in front-end code.

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.

terminal
# 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.

PlanLimit / min
Start60
Solo150
Pro300
Max1,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
{
  "error": {
    "code": "not_found",
    "message": "Article not found."
  }
}
  • 400 - invalid query parameters
  • 401 - missing or invalid API key
  • 403 - your plan does not include API access
  • 404 - not found (article unpublished or outside the project)
  • 429 - rate limit exceeded
  • 500 - server error

Pagination and sorting

Lists are returned in an envelope with data fields and page metadata:

GET /v1/articles
{
  "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

GET/v1/me

Returns information about the project tied to the key and the available languages. Useful for verifying the key.

GET /v1/me
{
  "id": "k17a9c…",
  "name": "Kawiarnia Pod LipÄ…",
  "primary_language": "pl",
  "available_languages": [
    "pl",
    "en"
  ],
  "plan": "Pro",
  "rate_limit_per_minute": 300
}

Topics

GET/v1/topics

Returns the list of project topics. Use the id field as the value for the topic_id filter on the article list.

GET /v1/topics
{
  "data": [
    {
      "id": "j57b2…",
      "title": "Lokalne SEO"
    },
    {
      "id": "j58c1…",
      "title": "Porady baristĂłw"
    }
  ]
}

Categories

GET/v1/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).

GET /v1/categories
{
  "data": [
    {
      "id": "k20a1…",
      "name": "Pielęgnacja ogrodu",
      "slug": "pielegnacja-ogrodu"
    },
    {
      "id": "k20a2…",
      "name": "Porady",
      "slug": "porady"
    }
  ]
}

List articles

GET/v1/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.

GET /v1/articles?sort=-published_at&limit=10
{
  "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

GET/v1/articles/:id

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.
GET /v1/articles/m93f7…
{
  "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

GET/v1/articles/:id/html

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.

terminal
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:

POST (webhook)
{
  "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.

verify (node)
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

POST/v1/webhooks

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.
terminal
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"}'
201 Created
{
  "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

GET/v1/webhooks

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.

GET /v1/webhooks
{
  "data": [
    {
      "id": "n41c8…",
      "event": "article.published",
      "target_url": "https://hook.eu2.make.com/abc123",
      "status": "enabled"
    }
  ]
}

Delete a subscription

DELETE/v1/webhooks/:id

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.

DELETE /v1/webhooks/n41c8…
{
  "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.