
Each local area page used to take us half a day to create and optimize.
With SEOmatic, we can create hundreds of pages in the same time, which helps our clients make the best use of their budget.
It's transformed how we deliver scalable SEO solutions.
Will Hawkins
Marketing Director, Digi-Business UK
Agents read your Search Console data, do the work, and prove what actually moved. You decide what ships.
14-Day Free Trial. $1 card check, refunded. Cancel Anytime.
Run SEOmatic from Apify: a technical SEO audit of up to 10 URLs for $1, or a researched, quality-gated SEO article for $9 ($0.50 when it starts, $8.50 on delivery). You need no SEOmatic account.
SEOmatic SEO Tools is a public actor on the Apify Store (seomatic/seomatic-seo-tools). It runs two of SEOmatic's one-off products:
Run it from the Apify console, the Apify API, a schedule, or any Apify integration, like any other actor.
| Field | For | Rules |
|---|---|---|
product | both | audit (the default) or article. |
urls | audit | 1 to 10 different http or https URLs. Pages from the same site let the audit find duplicates and broken links across them. |
topic | article | The keyword or topic to write about, 3 to 200 characters. |
{
"product": "audit",
"urls": ["https://example.com", "https://example.com/pricing"]
}{
"product": "article",
"topic": "how to choose trail running shoes"
}Pay per event on Apify, no subscription. An audit costs $1. An article costs $9: $0.50 when it starts and $8.50 when it is delivered.
| Event | Price | When it is charged |
|---|---|---|
audit-completed | $1.00 | Once per run, when the audit report is delivered. Covers 1 to 10 URLs. |
article-started | $0.50 | When an article run starts. If you abort the run or the quality gate rejects the article, this is the only charge. |
article-delivered | $8.50 | Once per run, when the finished article is delivered. |
Before any work, the actor checks that the run's maximum cost covers what the run can charge, so it never starts work it cannot bill. Set Maximum cost per run to at least $1 for an audit and $9 for an article.
Authenticate with your Apify API token (Apify console, Settings, API & Integrations). An audit fits the synchronous endpoint, which waits for the run and returns the dataset items; it answers 408 if a run takes longer than 300 seconds, in which case use the asynchronous flow below.
export APIFY_TOKEN=apify_api_...
curl -X POST "https://api.apify.com/v2/acts/seomatic~seomatic-seo-tools/run-sync-get-dataset-items?maxTotalChargeUsd=2&view=audits" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"product": "audit", "urls": ["https://example.com", "https://example.com/pricing"]}'An article takes minutes, so start the run, wait for it, then read the dataset:
# 1. Start the run: 30-minute timeout, $10 cost limit.
curl -X POST "https://api.apify.com/v2/acts/seomatic~seomatic-seo-tools/runs?timeout=1800&maxTotalChargeUsd=10" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"product": "article", "topic": "how to choose trail running shoes"}'
# data.id is the run id, data.defaultDatasetId the dataset id.
# 2. Wait. Repeat until data.status is SUCCEEDED, FAILED, TIMED-OUT or ABORTED.
curl "https://api.apify.com/v2/actor-runs/$RUN_ID?waitForFinish=60" \
-H "Authorization: Bearer $APIFY_TOKEN"
# 3. Read the article (all fields, including html).
curl "https://api.apify.com/v2/datasets/$DATASET_ID/items" \
-H "Authorization: Bearer $APIFY_TOKEN"When a run fails, its data.statusMessage holds the message from the errors table below, including what was charged.
With the official apify-client package (npm):
import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
// call() starts the run and waits for it to finish.
const run = await client.actor("seomatic/seomatic-seo-tools").call(
{ product: "audit", urls: ["https://example.com", "https://example.com/pricing"] },
{ maxTotalChargeUsd: 2 },
);
if (run.status !== "SUCCEEDED") throw new Error(run.statusMessage);
const { items } = await client
.dataset(run.defaultDatasetId)
.listItems({ view: "audits" });
const [audit] = items;
console.log(audit.status, audit.issue_count, audit.issues);For an article, pass { product: "article", topic: "..." } with { maxTotalChargeUsd: 10, timeout: 1800 } and read the items without a view to get the HTML too.
With the official apify-client package (PyPI, version 3, Python 3.11 or later):
import os
from datetime import timedelta
from decimal import Decimal
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
# call() starts the run and waits for it to finish.
run = client.actor("seomatic/seomatic-seo-tools").call(
run_input={"product": "article", "topic": "how to choose trail running shoes"},
max_total_charge_usd=Decimal("10"),
run_timeout=timedelta(minutes=30),
)
if run is None or run.status != "SUCCEEDED":
raise RuntimeError(run.status_message if run else "run did not start")
article = client.dataset(run.default_dataset_id).list_items().items[0]
print(article["title"], article["word_count"])
with open("article.html", "w") as f:
f.write(article["html"] or "")For an audit, pass run_input={"product": "audit", "urls": [...]} with max_total_charge_usd=Decimal("2") and read list_items(view="audits").
Each run adds one item to its default dataset: an audit report or a finished article. An item is written only when it is charged.
| Field | Type | Description |
|---|---|---|
product | string | "audit" |
status | string | done, or partial when the crawl time budget ran out |
urls | string[] | The URLs audited, normalized and de-duplicated |
pages_crawled | integer | Pages fetched |
pages_failed | object[] | { url, reason } for each page that could not be fetched |
issue_count | integer | Total issues across every type |
partial | string | null | How many pages were not crawled, when the time budget ran out |
issues | object | Issues grouped by type (below) |
issues holds one list per type:
| Type | Each entry |
|---|---|
deadPages | { url, status } for pages answering 400 or above |
brokenInternalLinks | { target, status, linkedFrom[] } for internal links to a dead page |
duplicateTitles | { value, pages[] } for a title shared by several pages |
duplicateMetaDescriptions | { value, pages[] } for a shared meta description |
missingMetaDescription | URLs with no meta description |
badH1 | { url, h1Count } for pages without exactly one H1 |
thinContent | { url, wordCount } for pages under 300 words |
noindexPages | URLs marked noindex |
canonicalMismatch | { url, canonical } where the canonical points elsewhere |
redirectedPages | { url, finalUrl } for URLs that redirect |
slowPages | { url, loadTimeMs } for pages slower than 3 seconds |
| Field | Type | Description |
|---|---|---|
product | string | "article" |
status | string | "ready" |
topic | string | The topic you gave |
title | string | null | The article title |
word_count | integer | null | Length of the article |
ai_search_score | number | null | SEOmatic's AI-search score for the article |
featured_image_url | string | null | The generated featured image |
markdown | string | The article in markdown |
html | string | null | The article in HTML |
The article is also saved as article.md and article.htmlin the run's key-value store, ready to download.
The run's Output tab links to the audit report, the article, or all results. The two views are also available to the API as view=audits and view=articles:
| View | API name | Fields |
|---|---|---|
| Audits | audits | status, urls, pages_crawled, issue_count, partial, issues |
| Articles | articles | status, topic, title, word_count, ai_search_score, featured_image_url, markdown |
The Articles view leaves out the HTML. Read the items without a view, or download article.html, to get it.
A failed run shows one of these messages as its status message. Each one also says what was charged, at the end or where <what was charged> stands: Nothing was charged. or Only the article start fee ($0.5) was charged; it covers the work already begun.
| Message | What happened | Fix |
|---|---|---|
Not a valid URL: "<url>". Use full URLs like https://example.com/page. | A URL in urls cannot be parsed. | Use full URLs, including https://. |
Only http and https URLs can be audited: "<url>". | A URL uses another scheme, such as ftp:. | Audit http or https pages only. |
An audit needs 1 to 10 URLs. | urls is empty. | Add at least one URL. |
An audit takes at most 10 different URLs per run (got N). | More than 10 different URLs after de-duplication. | Split the list across several runs. |
An article needs a topic of 3 to 200 characters. | topic is missing, too short or too long. | Give a topic of 3 to 200 characters. |
Your run's maximum cost does not cover one audit ($1). Raise "Maximum cost per run" and try again. | The run's cost limit does not cover the price of the result. For an article the message says $9 (the $0.50 start fee plus $8.50 on delivery). | Set Maximum cost per run (maxTotalChargeUsd) to at least $1 for an audit and $9 for an article. |
Articles need a run timeout of at least 25 minutes; this run would stop before the article is ready. Raise the timeout and try again. | The run has less than 22 minutes left when the article would start. | Set the run timeout to 25 minutes or more (1800 seconds is the default). |
Too many of your article runs failed in the last 24 hours. Nothing was charged. Please try again tomorrow. | Your Apify account reached its daily limit on failed articles. Other users' runs never count toward yours. | Wait a day, and check the topics that failed. |
SEOmatic cannot take new audits right now. Nothing was charged. Please try again later. | SEOmatic is not accepting new audits or articles at that moment (the same message says articles for an article run). | Run it again later. |
None of the pages could be crawled (check that the URLs are public and reachable). | Every URL failed to load. | Check that the pages are public and load in a browser. |
The article could not be generated or did not pass the quality check. | Generation failed, or the article was rejected by the quality gate. Only the $0.50 start fee is charged; the $8.50 delivery event is not. | Try again, or rephrase the topic. |
The article was not ready in time for this run. <what was charged> Please try again with a run timeout of at least 25 minutes. | The article took more than 20 minutes, or the run was about to time out. The unfinished article is stopped. | Run it again with a longer timeout. |
The article was ready, but your run's maximum cost was reached before it could be delivered. Raise "Maximum cost per run" and try again. | The run's cost limit was used up before delivery. The same message exists for an audit. | Raise Maximum cost per run. |
The run could not be completed because of a temporary problem on SEOmatic's side. <what was charged> Please try again later. | An unexpected error on SEOmatic's side. | Run it again later. |
No. You run the actor with your Apify account and pay through Apify. The actor calls SEOmatic for you.
The audit and article events are charged only when the result is delivered, in the same step. A run that cannot finish (an invalid URL, pages that cannot be crawled, a failed or rejected article, a timeout) does not charge them. An article costs a $0.50 start fee plus $8.50 on delivery ($9 total); if the quality gate rejects it or you abort the run, only the $0.50 start fee is charged. Every failure message says exactly what was charged.
No. Charges are checked against Apify's own charged-event counts, which survive a migration or restart. A restarted article run resumes the same article, and a finished audit is never crawled again.
The unfinished article is stopped on SEOmatic's side. Only the $0.50 start fee is charged; the $8.50 delivery event is not.
Before any work the actor checks that the run's maximum cost covers what it can charge, and that an article run has enough time left. If not, it stops with a message saying what to raise, and nothing is charged. Set Maximum cost per run to at least $1 for an audit and $9 for an article, and the timeout to 25 minutes or more for an article.
No. It returns the audit or the article to your Apify dataset, and the article also as article.md and article.html in the run's key-value store. Publishing and fixing pages on your own site is what SEOmatic's agents do once you connect your CMS.
English.