create_editorial_velocity
Measure how many articles a website publishes per month and per week (free tool)
Reads the sitemap of one website and the date shown on each of its pages, then counts the
articles published over the last 12 full months (UTC), per month, per section, per language,
and how many articles were updated. Works with any CMS.
**What is counted.** Only articles, dated by the publication date the site itself shows, read in this
order: structured data (JSON-LD `datePublished`), meta tags, an `itemprop` attribute, a labelled date in the
visible text such as "Published on" or "Publié le", the first line of a press release ("City, March 12,
2026"), then the first `<time>` element ("Updated on" is never a publication date). A page is an
article when it is typed as one (JSON-LD Article, BlogPosting, NewsArticle and similar types, or an
`article:published_time` meta tag), when its date comes from such a visible label or from the first line of
a press release (no inference needed), or when it belongs to a section without article markup whose pages
read are mostly dated by an `itemprop` attribute or a `<time>` element: that section is listed in
`method.inferredSections` (the exact rule is described there) and its dated pages are counted (never its
future dates, never an agenda or event section). Other dated pages are not counted, their number is
returned as `publications.otherDatedPages`.
Articles that share the same day are counted like any other: a section where at least 80% of the pages
counted share one day is only reported in `method.sameDaySections` (grouped publication, or dates changed
in bulk), never excluded.
Pages read without any date are never counted, their number is returned as `method.undatedPages`; a page
served but not HTML (a PDF) is one of them, not a failure in `method.pagesFailed`. An article republished
with a new date counts as a publication.
**Asynchronous, poll the result.** This call only starts the analysis and answers HTTP 202 with an
`analysisId` and a `pollUrl`. Call `GET /api/v1/tools/editorial-velocity?analysisId=...` every 5 seconds
until `status` is `done` or `failed`; an analysis takes from 1 to 45 minutes depending on the size of the
site. MCP clients must call the `get_editorial_velocity` tool again until the status is terminal.
**Up to 1500 pages read.** Below that, every page of the sitemap modified in the window is read. Above it,
pages are sampled section by section (`method.strata` sections, a section being the first path segment):
a few pages of each section are read first, a section whose pages show no publication date is left out
and listed in `method.undatedSections`, and the rest of the 1500 pages is shared among the dated sections.
Counts are then extrapolated, `method.sampled` is true and `publications.ci95` gives the 95 % confidence
interval of the yearly total. A section whose reading was stopped by the time or page budget, or by too
many failed pages, before any dated page was read is listed in `method.undecidedSections`: it is counted nowhere, neither in the total
nor in `ci95`, and the result is then a sample. A `total` of 0 with `ci95` present, or with
`method.undecidedSections` not empty, comes from a sample: it does not prove that the website published
nothing.
**Same domain, reused for 7 days.** An analysis of the same domain finished less than 7 days ago is
returned as is, and an analysis of the same domain still running is shared rather than started twice.
HTTP 200 with `reused` true when it is already done; nothing is read again, and your daily quota is given
back in both cases (`quotaReleased` true; false in the rare case where giving it back failed).
**Domain.** Send a host name or any URL of the site, it is reduced to its host (`https://www.example.com/blog/`
becomes `example.com`). Subdomains are kept, `blog.example.com` is not `example.com`. IP addresses, local
names and unknown suffixes return 400 INVALID_DOMAIN.
**Tiers, per UTC day.**
- Without an API key, free anonymous tier: 1 analysis per day per IP address.
- Signed-in account, or API key of an account whose plan includes API access: 3 analyses per day.
- Anti-loop ceiling, every tier: 50 analysis requests per day per account, or per IP address without one,
reused answers included, never given back except after SERVICE_BUSY. Beyond it, 429 RATE_LIMITED with
details.reason CREATE_ATTEMPTS_CEILING.
The API key of an account whose plan does NOT include API access does not authenticate, and the
call returns 401. Remove the `Authorization` header to use the free anonymous tier instead.
**Paid analysis beyond the free ones, 20 tokens, ONLY WITH THE USER'S EXPLICIT AGREEMENT.** When an
account has used its free analyses of the day, the call returns 429 RATE_LIMITED with
`details.paidOption` (`tokens`, the price, and `balance`, the token balance of the account, null when
unknown). So does the 429 FREE_CAPACITY_REACHED of an account: paid analyses do not count towards the
daily capacity shared by all accounts, and `paid` true on that refusal pays for the analysis instead. Never retry with `paid` true on your own: tell the user the price and their balance, and send
the same request again with `"paid": true` only after they agree. It then charges 20 tokens and starts
the analysis (`tokensCharged` in the answer). The tokens are given back when the analysis service
refuses the request, when the answer is reused (an analysis of the same domain less than 7 days old,
or already running: `tokensRefunded` true), and when the analysis fails (at the first read that sees
`failed`, or within the next hours if nobody reads it). With free analyses left, `paid` true uses a free
one and charges nothing. Without an account, `paid` true returns 401. A balance below the price returns
402 INSUFFICIENT_TOKENS and nothing is charged or started. A 500 TOKENS_NOT_CONSUMED means the outcome of
the debit could not be confirmed and nothing was started; if tokens were taken, they are given back
automatically within the next hours. Do not retry with `paid` true before telling the user.
Quota headers: `X-RateLimit-Limit`, `X-RateLimit-Used`, `X-RateLimit-Remaining`,
`X-RateLimit-Reset` (Unix time of the next UTC midnight). `Retry-After` is set on 429 and 503.
A 502 or 503 answer gives the consumed quota back, and so does a 429 FREE_CAPACITY_REACHED
and a reused answer; SERVICE_UNAVAILABLE consumes none.
**Request format.** Send the body as JSON with `Content-Type: application/json`: any other
content type returns 415. Without an `Authorization` header, a request sent by a web page of
another site (an `Origin` other than the SERPmantics app, or `Sec-Fetch-Site: cross-site`)
returns 403. Server-to-server calls send no `Origin` header and are not affected.
create_editorial_velocity_comparison
Compare the editorial velocity of competitors, paid in tokens (account only)
Measures how many articles each site publishes per month, for a list of sites or for the sites ranking
in the Google top 10 of a keyword, then returns the median of the competitors and a recommendation for
your own site. Each site is an editorial velocity analysis (same engine as createEditorialVelocity).
**Paid, ONLY WITH THE USER'S EXPLICIT AGREEMENT.** 20 tokens per site analysed. With `sites`, the price is
20 tokens times the number of distinct sites, your site included. With `keyword`, the number of sites is
only known once Google is read, so the maximum is charged, 5 tokens for the search results plus 20 tokens
for each of the 10 places (plus one for `ownSite`), 205 or 225 tokens, and the unused places are given back
as soon as the results are read. Before calling, tell the user the price and their token balance, and call
only after they agree. Never retry on your own after an error.
**What is given back.** The tokens of a site that cannot be measured (blocked, no sitemap, no dates,
unreachable, timeout, daily capacity reached), of a site served by an analysis of the same domain less than
7 days old or already running (nothing was read for you), of an unused place, and everything when the
search results cannot be read or the comparison cannot start. Refunds happen at the first read that sees them, whoever reads, or within the next hours if nobody
reads. `charged` in the answer is what stays charged after the refunds already made by this request.
**Asynchronous, poll the result.** This call answers HTTP 202 with a `comparisonId` and a `pollUrl`. Call
`GET /api/v1/tools/editorial-velocity/comparisons?comparisonId=...` every 15 seconds until `status` is `done`
or `failed`. Sites are analysed a few at a time, a comparison takes from a few minutes to 3 hours. MCP
clients must call the `get_editorial_velocity_comparison` tool again until the status is terminal.
**Input.** Exactly one of `sites` (2 to 11 distinct host names or URLs after normalisation, your site
included) or `keyword` with `lang` (a market code such as fr-fr, en-us, de-de, es-es, it-it). `ownSite`
is optional; it is added to the list when missing, counted once, and left out of the median.
**Errors.** 401 without an account. 402 INSUFFICIENT_TOKENS when the balance is below the price,
nothing is charged or started. 400 for an invalid body, nothing is charged. 429 RATE_LIMITED when 3
comparisons of the account are still running or 20 were started in the current UTC hour, nothing is
charged. A 503 means the comparison was
not started, and the tokens are given back (details.billing). A 502 means the analysis service did not
answer in time: the tokens are given back (details.billing), and the comparison may still have started, in
which case it is free (its comparisonId is not known, do not retry without the user). A 500 TOKENS_NOT_CONSUMED
means the outcome of the debit could not be confirmed and nothing was started; if tokens were taken, they
are given back automatically within the next hours.
**Request format.** JSON body with `Content-Type: application/json` (415 otherwise). Without an
`Authorization` header, a request sent by a web page of another site returns 403.
get_editorial_velocity
Read an editorial velocity analysis, poll until it is done
Returns the analysis started by `POST /api/v1/tools/editorial-velocity`. While `status` is `pending` or
`running`, call again every 5 seconds; `progress` gives the current stage (robots, sitemap, pages,
summary) and `pagesRead` out of `pagesTotal`. When `status` is `failed`, `failureCode` says why
(BLOCKED, the site refuses to be read; NO_SITEMAP; NO_DATES, pages read but none shows a readable
publication date, neither structured data, nor a date tag, nor a "Published on" mention; UNREACHABLE;
TIMEOUT; INTERNAL) and `publications` is null. A failed analysis is never a zero. A NO_DATES analysis may
carry `sitemapCeiling`, an upper bound read from the sitemap modification dates: at most `total` pages were
published or modified in the window. It is never a count of articles nor a pace, and the status stays failed.
When `status` is `done`, `publications.total` articles were published in `window` (12 full months, UTC),
`perMonth` is total / 12 and `perWeek` total / 52.14. `ci95` is only meaningful when the analysis was sampled
(`method.sampled`). A `total` of 0 with `ci95` present comes from a sample: it does not prove that the
website published nothing, and neither does a 0 with `method.undecidedSections` not empty (sections whose
reading stopped before a verdict, counted nowhere). Otherwise, a 0 covers what was read in full: the whole
site, or only its dated sections when `method.undatedSections` is not empty. A 0 with
`publications.otherDatedPages` above 0 means that dated pages were read, but nothing shows that they are
articles (product pages, event calendars): it adds to the meaning of the 0 above, it never replaces it.
`method.inferredSections` names the sections without article markup counted from the dates read in their
pages (`itemprop` attribute or `<time>` element). `method.sameDaySections` names the sections where at
least 80% of the pages counted share the same day: those pages are counted, the list is only a hint to check
the website (grouped publication, or dates changed in bulk). `method.discovery` says what the news pages
linked from the home page and the RSS or Atom feeds added to the sitemap; its `articlesCounted` is the part
of `total` that comes from addresses absent from the sitemap. When `method.discovery.truncated` is true, the
discovery stopped before the end and `total` is a minimum (a 0 is then not a proven zero, and `ci95` is not an
upper bound). `total` is a minimum too when `method.sitemapFilesFailed` is above 0 (sitemap files that could
not be read, even after a second try). `method.rateLimited` is true when the website refused page reads and the
reading was slowed down. Editorial subdomains (newsroom, blog, news and the like, 3 at most, linked from the home page
or answering at newsroom. or blog.) are read too and counted in `total` (`scope.hosts` lists the hosts read, the main host first):
`publications.byHost` splits the articles counted by host, main host included, and
`publications.mainHostOnly` gives `total`, `perMonth`, `perWeek`, `ci95` and `monthly` without the
subdomains (null when no subdomain has a counted article). In `publications.bySection`, the sections of a
subdomain are prefixed by its host (`newsroom.example.com/press-releases`). A subdomain that blocked the
reading is named in `scope.hostsFailed`: its articles could not be looked for, so a `total` of 0 is then
not a proven zero. `publications.byDeclaredType` splits the articles counted by the page type the website
declares itself (`og:type`, as the website writes it (case and accents kept, never translated); types equal but for case and accents are one row; beyond 10 rows the smallest declared types join the null row), `null` for no declared type; the same
split is in `publications.mainHostOnly`. It does not change what `total` counts. `updates` counts the articles updated more than one day
after their publication, or says why this is not measurable (DYNAMIC_DATES, dates generated when the page
is shown; SOURCES_DISAGREE, page and sitemap dates contradict each other; TOO_FEW_DATES).
When a failed analysis had been paid with tokens, `billing` says what became of them (`tokens`, the amount
charged for it, `refunded`, true once they were given back to the account that paid). It names no account,
and is absent for a free analysis or while the analysis is not failed.
No quota is consumed. The `analysisId` is random, whoever holds it can read the analysis, which is what
makes share links work. Analyses expire after 30 days. Reads that are not attributed to an account are
capped at 1000 per IP address and per UTC day, far above any legitimate polling, and crossing it returns
429 READ_RATE_LIMITED with `Retry-After`.