Confusables API
Confusables detects potentially misused words and returns suggestions. The review-before-post workflow uses /v1/check: the application presents detections and the author decides whether to edit. The API also supports an optional /v1/autocorrect endpoint that returns a string with suggestions applied. Both accept one text field as JSON over HTTP.
Quick start
Send text, get detections back
This example uses the public endpoint without an API key. Paste it into a terminal to see the response.
curl -X POST https://api.confusables.io/v1/check \
-H "Content-Type: application/json" \
-d '{"text": "Its been a long day and your right."}'
{
"original": "Its been a long day and your right.",
"corrected": "It's been a long day and you're right.",
"rulesApplied": {
"Likely confusion between its/it's": 1,
"Likely confusion between your/you're": 1
},
"issues": [
{
"start": 0,
"end": 3,
"original": "Its",
"suggestion": "It's",
"confidence": 0.95,
"reason": "Likely confusion between its/it's"
},
{
"start": 24,
"end": 28,
"original": "your",
"suggestion": "you're",
"confidence": 0.95,
"reason": "Likely confusion between your/you're"
}
],
"processingTimeMs": 2
}
The issues array contains detections with positions and suggested words. The original field preserves your input; corrected is a preview with the suggestions applied. Returning that preview does not edit a draft or publish anything. The application controls what it shows and what the author approves.
Detect and review
Reports detections, positions and suggestions for review. This is the endpoint the demo uses before posting. A person can edit the original draft or proceed as written; receiving a detection does not change what gets posted. The response also includes a suggested-text preview in corrected, which the demo does not use as its publication payload.
start, end | Offsets of the word in the string you sent, counted in UTF-16 code units. The note below covers callers outside JavaScript. |
|---|---|
original | The word as it was written. |
suggestion | A proposed replacement, including apostrophe style. The corrected preview contains these suggestions applied to a copy of the input. |
confidence | 1.0 structural, 0.95 strong cue, 0.90 heuristic. Only detections meeting the configured threshold are returned. |
reason | Which rule fired, in words. |
rulesApplied | A count of returned detections per rule. The field name does not mean an author has accepted or applied a suggestion. |
processingTimeMs | Server-side time for this request. |
curl
curl -X POST https://api.confusables.io/v1/check \
-H "Content-Type: application/json" \
-d '{"text": "Its been a long day and your right."}'JavaScript
const res = await fetch("https://api.confusables.io/v1/check", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Its been a long day and your right." })
});
const { issues } = await res.json();
for (const issue of issues) {
console.log(issue.start, issue.original, "->", issue.suggestion);
}Python
import requests
response = requests.post(
"https://api.confusables.io/v1/check",
json={"text": "Its been a long day and your right."},
timeout=2,
)
response.raise_for_status()
for issue in response.json()["issues"]:
print(issue["start"], issue["original"], "->", issue["suggestion"])Response:
{
"original": "Its been a long day and your right.",
"corrected": "It's been a long day and you're right.",
"rulesApplied": {
"Likely confusion between its/it's": 1,
"Likely confusion between your/you're": 1
},
"issues": [
{
"start": 0,
"end": 3,
"original": "Its",
"suggestion": "It's",
"confidence": 0.95,
"reason": "Likely confusion between its/it's"
},
{
"start": 24,
"end": 28,
"original": "your",
"suggestion": "you're",
"confidence": 0.95,
"reason": "Likely confusion between your/you're"
}
],
"processingTimeMs": 2
}
text.slice(start, end) uses this unit directly. Java and C# use the same unit with their own substring APIs. Python normally indexes Unicode code points, while Go and Rust string offsets use UTF-8 bytes; other languages also have their own indexing conventions.
# Python: convert once, slice in the same unit the API used
units = text.encode("utf-16-le")
word = units[issue["start"] * 2 : issue["end"] * 2].decode("utf-16-le")
Non-ASCII text can require conversion even without emoji: in é Your, Your starts at UTF-16 offset 2 but UTF-8 byte offset 3. Characters outside the Basic Multilingual Plane, including many emoji, occupy two UTF-16 units.Optional autocorrect response
This endpoint applies selected suggestions to a returned copy of the input. It is separate from the author-controlled detection workflow used by the demo. The API does not edit a draft or publish text; an application that uses this returned string chooses to use the replacements. No reordering, synonyms or general editing is performed.
| Request | text, a string. Required. |
|---|---|
| Returns | text, a string with selected suggestions applied, and applied, the number of replacements in that returned string. |
curl
curl -X POST https://api.confusables.io/v1/autocorrect \
-H "Content-Type: application/json" \
-d '{"text": "Its been a long day and your right."}'JavaScript
const res = await fetch("https://api.confusables.io/v1/autocorrect", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Its been a long day and your right." })
});
const { text, applied } = await res.json();
// text: "It's been a long day and you're right."
// applied: 2Python
import requests
response = requests.post(
"https://api.confusables.io/v1/autocorrect",
json={"text": "Its been a long day and your right."},
timeout=2,
)
response.raise_for_status()
suggested_text = response.json()["text"]Response:
{
"text": "It's been a long day and you're right.",
"applied": 2
}
applied is 0 and text is exactly what you sent. The detection-and-review workflow leaves any edit to the author.What detection skips
Recognized protected regions are skipped
The engine detects supported confusable words and suggests alternatives. Recognized protected regions are skipped even when the same word is detected elsewhere in the sentence. These examples describe the detections returned by /v1/check; the input remains the author’s text.
in Its been a long day at its-been.com
find Its → It's the sentence is flagged,
the domain is skipped
in Read more at https://example.com/its-been-great?your=1
find none URLs
in Email me at its.been@example.com
find none addresses
in #itsbeen trending with @your_friend
find none hashtags and handles
in She said "lets go" and left.
find none quoted speech
in if (x) { your_var = 1; }
find none identifiers and code
in The colour was bright.
find none British spellings
Protected text does not supply detection cues outside its span. Mixed-case identifiers are the exception: they are not flagged but can supply context, so cOuLd of done it receives the suggestion of → have. Text with more than 30 consecutive non-spacing combining marks receives no detections.
their family, they're family and your losing patience receive no detections, because each reads correctly under some interpretation. Other ambiguous wording can still receive a wrong suggestion; these examples are not a guarantee for every sentence.Errors
What comes back other than a 200
| 400 | The text field is missing, null or not a JSON string, or the body is not valid JSON. |
|---|---|
| 413 | The decoded text or the request body exceeds its limit. |
| 429 | Over the rate limit. Carries Retry-After in seconds. |
| 5xx | The service is having a problem. Treat it the way you would treat a timeout. |
{"text": ""}, is valid and returns no detections. Applying the returned issue suggestions at their original UTF-16 offsets reconstructs the corrected preview, including apostrophe styling; it does not imply that an author accepted them.For allowed browser origins, error responses carry CORS headers and Retry-After is exposed to JavaScript. Preflight requests do not require a key.
A 429 says how long to wait, in the header and again in the body, so a client backing off does not have to guess:
HTTP/1.1 429
Retry-After: 42
{"error": "Too many requests. Limit is 60 requests per minute.",
"retryAfterSeconds": 42}
Limits and the demo host
What api.confusables.io is
This is the public endpoint used by the homepage demo. It is available for demonstration and evaluation within the limits below, without an availability commitment.
| Maximum input | 50,000 UTF-16 code units, roughly 8,000 words of English |
|---|---|
| Maximum request body | 304,096 bytes by default, checked before JSON decoding |
| Rate limit | 60 requests per minute per caller |
| Authentication | Optional. A key raises the limit and meters it to you |
The cap counts UTF-16 code units rather than bytes, so scripts whose characters cost several bytes each, Cyrillic and Greek and Arabic and Hebrew and the common CJK ranges among them, get the full allowance. Characters outside the Basic Multilingual Plane cost two units rather than one, so a body made entirely of emoji reaches the cap at 25,000 of them. The text and byte limits are configurable when self-hosted. The default body limit allows six bytes per UTF-16 unit plus 4,096 bytes for JSON overhead.
Sending a key
The public endpoint accepts requests without a key. Anonymous callers share an IP-based allowance, so requests behind the same network address share the 60/minute limit. An API key provides a separate caller allowance.
curl -X POST https://api.confusables.io/v1/check \
-H "Content-Type: application/json" \
-H "X-API-Key: $CONFUSABLES_KEY" \
-d '{"text": "Its been a long day and your not wrong."}'
Authorization: Bearer $CONFUSABLES_KEY is also supported. An unrecognized key receives HTTP 401 rather than falling back to anonymous access.
Keys are arranged by email at contact@confusables.io, with the allowance discussed for each evaluation. They are not issued automatically, and there is no signup form or dashboard.
Running it yourself
Self-hosted deployment
The API service runs on Java 21 and is distributed as a container image. Its documented deployment uses 512 MB of memory on a single core. The engine needs no GPU, database or external service, and makes no outbound network calls at runtime, so it can run with egress blocked.
- The same two endpoints, on your own host and your own limits.
- The deployment threshold and optional affect/effect rule are configurable. The other nine rule sets are enabled together.
- Ten rules are supported, with nine enabled by default. Affect/effect is optional and enabled in the demo, which runs all ten.
- Submitted text is processed inside your network without being sent to us.
The FAQ describes the supported words, how the rules work and the limits of detection.
Read the FAQUsing the public service
The Terms of Service describe permitted evaluation, content rights and service limits. This section provides the operational guidance.
Public evaluation service
The public endpoint is for demonstration and evaluation, including programmatic requests and temporary integration prototypes within the documented limits. Production use, resale and self-hosted evaluation require separate written permission. It has no service level agreement and no availability commitment, is rate limited as described above, and can change or be withdrawn. The self-hosted image is available to discuss for evaluation if your application needs a deployment you operate: contact us.
Limits of the suggestions
The engine can flag wording the author intended, miss an error or return no detections. Suggestions need review, and the author remains responsible for what they publish. The accuracy section describes the available measurements and their limits.
How wrong depends heavily on the kind of text. A September 11, 2026 evaluation detected an error in 273 of 50,000 sentences of informal comments. Of 100 reviewed suggested replacements, 60 were judged correct and 40 damaging. A September 19, 2026 pilot on an earlier build using 1,000 sentences detected errors in seven sentences: four useful detections and three damaging detections. Both evaluations used earlier builds and do not measure the latest production version. The pilot was developer-reviewed and is too small for a general accuracy estimate. We do not currently recommend it for unattended detection of informal conversational text. For informal text, we recommend /v1/check with author review of suggestions before publication. The supporting figures are in the accuracy section.
Please don't send it sensitive material
Please do not submit confidential, personal, regulated, credential or security-sensitive material to the public endpoint. Privacy and Data Handling describes how requests are processed. A self-hosted deployment is an option when text needs to remain inside your infrastructure.
Conditions of public access
- Don't work around the rate limits, by rotating addresses, forging caller identity headers, or any other route.
- Don't deliberately overload or disrupt the service.
- Don't use it for unlawful activity.
Access to the public endpoint may be limited or revoked for abuse. Security reports are welcome at contact@confusables.io; “security” in the subject helps us identify them.