Text Analysis v1.0 1 credit / call
Word counts, readability grades and language detection for any block of text — rule-based, no ML.
- Base path
- /api/v1/text-analysis
- Methods
- GET or POST — parameters are read identically from the query string or a JSON body
- Auth
- X-Api-Key header (never Authorization: Bearer)
- Price
- 1 credit per call · about $0.0010 at 1,000 credits/USD
- Free tier
- 100 credits, refilled every month — drawn down before your paid balance
- Test mode
- Supported — ksty_test_ keys call this API free of charge
- Rate limit
- 60 calls/minute per key by default, raisable per key
Text Analysis API
Three things about a block of text, in one call: how big it is, how hard it is to read, and what language it is in.
Everything is computed from the text itself using published formulas and character statistics. No model is loaded, no external service is contacted. That has two consequences worth planning around: latency is flat and predictable regardless of load, and the same input always returns the same numbers — so you can store a score and compare it against one computed months later.
POST /api/v1/text-analysis
X-Api-Key: ksty_test_...
Content-Type: application/json
{"text": "The quick brown fox jumps over the lazy dog. It was a sunny day."}
{
"counts": { "words": 14, "sentences": 2, "syllables": 17, "reading_time_seconds": 5 },
"readability": {
"reliable": true,
"scores": { "flesch_reading_ease": 92.4, "flesch_kincaid_grade": 2.1 },
"consensus_grade": 2.4,
"interpretation": { "ease": "very easy", "school_level": "2nd grade" }
},
"language": { "language": "en", "name": "English", "confidence": 0.71 }
}
Counts
Words keep their internal apostrophes and hyphens — "don't" is one word, not two, because that is what a reader speaks and what the readability formulas assume when they divide by word count.
Sentence splitting masks common abbreviations first. Without that, "Dr. Smith went home." counts as two sentences, which shortens the average sentence length and makes the text look far simpler than it is. Every grade-level formula divides by sentence count, so that single mistake moves all of them at once.
Reading time assumes 200 words per minute silently; speaking time assumes 130.
Readability
Six formulas, not one:
| Score | Reads as |
|---|---|
flesch_reading_ease |
0–100, higher is easier. 60–70 is standard prose. |
flesch_kincaid_grade |
US school grade |
gunning_fog |
US school grade, weights 3+ syllable words |
smog_index |
US school grade, common in health writing |
coleman_liau_index |
US school grade, from letters rather than syllables |
automated_readability_index |
US school grade, from characters |
They weigh the same two ingredients — sentence length and word weight — but disagree on the weighting, so they routinely differ by a grade or two on the same text. consensus_grade averages the five grade-level scores. The spread between them is itself information: when they cluster, the text is consistently pitched; when they scatter, it mixes short sentences with heavy vocabulary (or the reverse), and no single number describes it well.
Below 10 words, reliable is false and scores is null. A formula built on averages over sentences cannot say anything trustworthy about a fragment, and returning a confident-looking number for "Hello there" would be worse than returning nothing.
These formulas are calibrated on English prose. Run them on French and you will get numbers, but not meaningful ones — which is why the language result is returned alongside, so you can check before trusting the grade.
Language detection
Two stages, cheapest and most decisive first.
Script. Most languages are settled by the Unicode block their letters occupy — Devanagari is Hindi, Hangul is Korean, Thai is Thai. Near-certain from a handful of characters, no statistics needed. Detected this way: Japanese, Korean, Chinese, Russian, Arabic, Hindi, Hebrew, Greek, Thai. The response reports "method": "script".
Function words and n-grams. English, Spanish, French, German, Italian, Portuguese and Dutch share an alphabet, so they need evidence. Three signals combine: function words (the, de, der, het), character trigrams that capture morphology (sch, ção, ijk), and diacritics (ß, ñ). Function words carry the most weight because they survive topic changes — a Spanish text about football and one about medicine share almost no vocabulary except de, la, que. Reported as "method": "ngram".
confidence is the winner's share of the total evidence, so it says how far clear of the runner-up it finished — not how certain the answer is in absolute terms. alternatives lists the next-best candidates, which is where you look when confidence is middling.
Accuracy & limitations
- Short text is hard. Under 5 words,
reliableisfalse. "No" is valid in several of these languages, and nothing can decide between them. - Related languages compete. Spanish and Portuguese, or Dutch and German, share function words and diacritics. Expect lower confidence on a single sentence and check
alternatives. - One language per call. A text mixing two languages returns whichever dominates, not a breakdown.
- Syllables are approximated. English syllable counting uses vowel-group heuristics; an exact count needs a pronunciation dictionary. It is within one syllable on the overwhelming majority of words, and the formulas themselves were calibrated on the same approximation.
When to use it
- Enforcing a reading level on help-centre or policy copy before it ships.
- Routing user-submitted content to the right language queue.
- Showing "N min read" on articles, from a real word count.
- Screening a CMS import for text that is far denser than the rest of the corpus.
Endpoints
Full analysis — counts, readability and language
Runs all three analyses and returns them together. The focused endpoints below cost the same, so use them only when you want a smaller payload.
curl -X POST "https://ksty.ch/api/v1/text-analysis" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"The quick brown fox jumps over the lazy dog."}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/text-analysis');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "text": "The quick brown fox jumps over the lazy dog."}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/text-analysis', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"text": "The quick brown fox jumps over the lazy dog."
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/text-analysis',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"text": "The quick brown fox jumps over the lazy dog."
},
)
data = response.json()
{
"counts": {
"words": 9,
"sentences": 1,
"syllables": 11,
"reading_time_seconds": 3
},
"readability": {
"reliable": false,
"note": "Fewer than 10 words — readability scores need a paragraph or so to mean anything, so they are omitted.",
"scores": null
},
"language": {
"language": "en",
"name": "English",
"confidence": 0.68,
"reliable": true
}
}
Word, sentence and character counts only
curl -X POST "https://ksty.ch/api/v1/text-analysis.counts" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Hello world. This is a test."}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/text-analysis.counts');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "text": "Hello world. This is a test."}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/text-analysis.counts', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"text": "Hello world. This is a test."
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/text-analysis.counts',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"text": "Hello world. This is a test."
},
)
data = response.json()
{
"characters": 28,
"characters_no_spaces": 23,
"words": 6,
"unique_words": 6,
"sentences": 2,
"paragraphs": 1,
"syllables": 7,
"avg_word_length": 3.83,
"avg_sentence_length": 3,
"reading_time_seconds": 2,
"speaking_time_seconds": 3
}
Readability grades and a plain-language interpretation
curl -X POST "https://ksty.ch/api/v1/text-analysis.readability" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"The committee subsequently determined that the implementation of the proposed methodology would necessitate substantial additional expenditure."}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/text-analysis.readability');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "text": "The committee subsequently determined that the implementation of the proposed methodology would necessitate substantial additional expenditure."}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/text-analysis.readability', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"text": "The committee subsequently determined that the implementation of the proposed methodology would necessitate substantial additional expenditure."
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/text-analysis.readability',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"text": "The committee subsequently determined that the implementation of the proposed methodology would necessitate substantial additional expenditure."
},
)
data = response.json()
{
"reliable": true,
"scores": {
"flesch_reading_ease": 8.2,
"flesch_kincaid_grade": 19.8,
"gunning_fog": 24.6,
"smog_index": 19.1,
"coleman_liau_index": 21.4,
"automated_readability_index": 23.1
},
"consensus_grade": 21.6,
"interpretation": {
"ease": "very difficult",
"school_level": "College graduate",
"audience": "Academic or specialist register; expect general readers to struggle."
}
}
Detect the language of the text
curl -X POST "https://ksty.ch/api/v1/text-analysis.language" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Le chat noir dort sur le tapis dans le salon."}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/text-analysis.language');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "text": "Le chat noir dort sur le tapis dans le salon."}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/text-analysis.language', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"text": "Le chat noir dort sur le tapis dans le salon."
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/text-analysis.language',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"text": "Le chat noir dort sur le tapis dans le salon."
},
)
data = response.json()
{
"language": "fr",
"name": "French",
"script": "Latin",
"confidence": 0.42,
"reliable": true,
"method": "ngram",
"alternatives": [
{
"language": "es",
"name": "Spanish",
"confidence": 0.21
}
]
}
Errors
Errors are JSON with a stable error.code — branch on the code, never on
the message. Everything below carries the X-Request-Id of the failed call.
Requests rejected by this API are not charged, and a 502 after a
charge is refunded automatically.
Response headers
Every successful call reports its own cost, so you never have to guess what a request spent or reconcile it later.