SEO Analysis API
The SEO endpoint analyzes a page or article and returns everything needed to publish it well: deterministic on-page stats (word, sentence and paragraph counts, reading time, readability), keyword density for the target keywords you pass in, the top terms the page actually reads as being about, and LLM-generated suggestions — title tags, meta descriptions, a URL slug and focus keywords — steered toward your target keywords and written in the language of the text.
It is built for content teams, CMS and headless-CMS integrations, and marketing tooling that wants to check keyword coverage and generate metadata as part of a publishing workflow. For plain text statistics without keywords or suggestions, see the statistics endpoint.
Sample Code
- cURL
- JavaScript
- Python
- Python (Sapling client)
curl -X POST https://api.sapling.ai/api/v1/seo \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "text":"A grammar checker does more than fix typos. Modern grammar checker tools flag tone, clarity, and passive voice, not just grammar.\n\nTeams use a grammar checker to keep customer messaging consistent. It works in your browser, in Google Docs, and in emails.\n\nIs a grammar checker worth it? For busy teams that spend the day writing, a grammar checker pays for itself quickly.", "keywords": ["grammar checker"]}'
import axios from 'axios';
async function run(text) {
try {
const response = await axios.post(
'https://api.sapling.ai/api/v1/seo',
{
key: '<api-key>',
text,
keywords: ['grammar checker'],
},
);
const {status, data} = response;
console.log({status});
console.log(JSON.stringify(data, null, 4));
} catch (err) {
const msg = err.response?.data?.msg || err.message;
console.log({err: msg});
}
}
run('A grammar checker does more than fix typos. Modern grammar checker tools flag tone, clarity, and passive voice, not just grammar.\n\nTeams use a grammar checker to keep customer messaging consistent. It works in your browser, in Google Docs, and in emails.\n\nIs a grammar checker worth it? For busy teams that spend the day writing, a grammar checker pays for itself quickly.');
import requests
from pprint import pprint
text = (
"A grammar checker does more than fix typos. Modern grammar checker tools flag tone, "
"clarity, and passive voice, not just grammar.\n\n"
"Teams use a grammar checker to keep customer messaging consistent. It works in your "
"browser, in Google Docs, and in emails.\n\n"
"Is a grammar checker worth it? For busy teams that spend the day writing, a grammar "
"checker pays for itself quickly."
)
response = requests.post(
"https://api.sapling.ai/api/v1/seo",
json={
"key": "<api-key>",
"text": text,
"keywords": ["grammar checker"],
}
)
if 200 <= response.status_code < 300:
pprint(response.json())
else:
print('Error: ', response.status_code, response.text)
from sapling import SaplingClient
api_key = '<api-key>'
client = SaplingClient(api_key=api_key)
text = (
"A grammar checker does more than fix typos. Modern grammar checker tools flag tone, "
"clarity, and passive voice, not just grammar.\n\n"
"Teams use a grammar checker to keep customer messaging consistent. It works in your "
"browser, in Google Docs, and in emails.\n\n"
"Is a grammar checker worth it? For busy teams that spend the day writing, a grammar "
"checker pays for itself quickly."
)
result = client.seo(text, keywords=['grammar checker'])
print(result['stats']['words'], 'words,', result['stats']['reading_time_min'], 'min read')
for kw in result['keywords']:
print(kw['keyword'], '|', kw['count'], 'uses |', f"{kw['density']}% |", 'early' if kw['in_first_100_words'] else 'late')
print('Title:', result['suggestions']['titles'][0])
print('Description:', result['suggestions']['meta_descriptions'][0])
print('Slug:', result['suggestions']['slug'])
Sample Response
{
"stats": {
"chars": 372,
"words": 63,
"sentences": 6,
"paragraphs": 3,
"reading_time_min": 1,
"flesch_reading_ease": 66.4,
"flesch_kincaid_grade": 7.2
},
"keywords": [
{"keyword": "grammar checker", "count": 5, "density": 7.94, "in_first_100_words": true}
],
"top_terms": [
{"term": "grammar", "count": 6},
{"term": "checker", "count": 5},
{"term": "grammar checker", "count": 5},
{"term": "teams", "count": 2}
],
"suggestions": {
"titles": [
"Grammar Checker Guide: What Modern Tools Catch",
"How a Grammar Checker Improves Team Writing",
"Is a Grammar Checker Worth It? A Practical Look"
],
"meta_descriptions": [
"Learn what a modern grammar checker catches beyond typos, how teams use one to keep customer messages consistent, and whether it pays off. Try it today.",
"A practical guide to grammar checkers: tone, clarity and passive voice detection, browser and Google Docs support, and the payoff for busy teams.",
"Grammar checkers now flag tone and clarity as well as typos. See how teams keep messaging consistent with one, and decide if it is worth it for you."
],
"slug": "grammar-checker-guide",
"keywords": ["grammar checker", "grammar checker for teams", "passive voice checker"]
}
}
With suggestions: false, the response contains only stats, keywords and top_terms.
Request Parameters
POST to https://api.sapling.ai/api/v1/seo
key: String
32-character API key. Can also be supplied via the Authorization header as a bearer token; if both are provided, the key parameter takes precedence.
text: String
The page or article body, up to 20,000 characters (measured on the raw input, before any HTML is stripped).
Plain text or HTML: <script>, <style>, <noscript> and <template> bodies are dropped, block-level tags
become line breaks, inline tags are removed, and HTML entities are decoded.
Text that is empty after stripping returns a 400.
keywords: List[String], optional
Target keyword phrases, most important first — the first entry is treated as the primary keyword.
Up to 10 phrases of 1–100 characters each; each must contain at least one letter or digit.
Phrases are whitespace-trimmed and exact duplicates are dropped. Keywords are both measured in the
text (see keywords in the response) and steered into the generated suggestions.
suggestions: Boolean, optional, defaults to true
Whether to generate the LLM-based suggestions (titles, meta descriptions, slug, focus keywords).
Set to false for a deterministic stats-only response, which is not billed.
Non-boolean values return a 400.
lang: String, optional, defaults to "en"
ISO 639-1 language code of the text (a region subtag is allowed, e.g. pt-BR; auto is also accepted).
This only selects the readability formulas: flesch_reading_ease and flesch_kincaid_grade are computed
for en, de, es, fr, it, nl, pl and ru, and are null for any other code. Any well-formed
code is accepted, so pages in other languages still get stats, keywords and suggestions; malformed codes return a 400.
Response Parameters
stats: Object
Deterministic on-page statistics for the (stripped) text:
chars: character count.words: word count.sentences: sentence count.paragraphs: paragraph count.reading_time_min: estimated reading time in whole minutes,ceil(words / 265).flesch_reading_ease: Flesch reading-ease score, clamped to 0–100 (one decimal); higher is easier to read.flesch_kincaid_grade: Flesch-Kincaid US grade level, at least 0 (one decimal).
The readability formulas are the same ones used by the statistics endpoint, selected by lang.
Both readability values are null when lang is not one of en, de, es, fr, it, nl, pl, ru,
or if the text contains no words. Sentence counting uses the English splitter regardless of lang.
keywords: List[Object]
One entry per submitted target keyword, in the same order (an empty list when no keywords were sent). Each has:
keyword: the phrase as submitted (trimmed).count: number of case-insensitive, whole-word, non-overlapping occurrences of the phrase in the text.density:count / words × 100, rounded to two decimals.in_first_100_words:trueif the first occurrence starts within the first 100 words of the text.
top_terms: List[Object]
Up to 10 of the most frequent content terms (single words and two-word phrases) in the text —
what the page reads as being about. English stopwords are removed, tokens must be at least 3 characters
and appear at least twice, and ties are broken by first appearance. Terms are lower-cased. Each has:
term: the word or two-word phrase.count: number of occurrences.
May be empty for very short text.
suggestions: Object
Only present when suggestions is true (the default). Generated in the language of the text and steered
toward the submitted keywords:
titles: 1–5 suggested title tags, targeting ≤60 characters each (90 characters is the hard cap — the longest you will ever receive).meta_descriptions: 1–5 suggested meta descriptions, targeting 120–155 characters each (hard cap 220).slug: a lowercase ASCII kebab-case URL slug of at most 80 characters, ornullwhen no ASCII slug could be derived (for example purely CJK content).keywords: 0–8 suggested focus-keyword phrases, most important first.
Errors
| Status | Meaning |
|---|---|
400 | Validation error — missing or oversized text, text empty after stripping, or invalid keywords / suggestions. The body is {"msg": "..."}. |
429 | Rate limit exceeded or key over capacity. |
502 | {"msg": "Unexpected error generating SEO suggestions."} — the suggestion generation failed; safe to retry. Not billed and not cached. |
Tips
- Search engines typically display about 60 characters of a title tag and 120–155 characters of a meta description before truncating; the suggestions target these ranges, but check the lengths before publishing.
- Put your primary keyword first in
keywords— it gets the most weight in the generated titles, descriptions and slug, and itsin_first_100_wordsflag tells you whether the article leads with it. - Pass
suggestions: falsefor a free, fast, deterministic check of counts, keyword density and readability (for example on every save in an editor), and request suggestions only when the draft is ready. - Results for identical
textandkeywordsare cached on Sapling's side for about three days, so re-submitting the same draft returns the same suggestions; change the text or keywords to get new ones. - Pass
langfor non-English pages:flesch_reading_easeandflesch_kincaid_gradeneed per-language syllable rules and are only computed foren,de,es,fr,it,nl,plandru(nullotherwise). Stats, keyword density and suggestions work for any language, and suggestions are written in the language of the text.