Confusables logo

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

01

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

02
POST/v1/check

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, endOffsets of the word in the string you sent, counted in UTF-16 code units. The note below covers callers outside JavaScript.
originalThe word as it was written.
suggestionA proposed replacement, including apostrophe style. The corrected preview contains these suggestions applied to a copy of the input.
confidence1.0 structural, 0.95 strong cue, 0.90 heuristic. Only detections meeting the configured threshold are returned.
reasonWhich rule fired, in words.
rulesAppliedA count of returned detections per rule. The field name does not mean an author has accepted or applied a suggestion.
processingTimeMsServer-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
}
Offsets are into the string you sent, so you can highlight in place without looking for the word again. The same input returns the same detections with the same engine version and configuration; timing can vary.
The unit is UTF-16 code units. JavaScript 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

03
POST/v1/autocorrect

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.

Requesttext, a string. Required.
Returnstext, 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: 2

Python

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
}
A detection can be wrong. The engine can flag correct or ambiguous wording and suggest an unsuitable replacement. In this optional response, selected suggestions are applied to the returned string. When none are selected, applied is 0 and text is exactly what you sent. The detection-and-review workflow leaves any edit to the author.

What detection skips

04

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
Recognized quotations are protected. Detection has limits: a plural possessive apostrophe inside a single-quoted passage can be mistaken for its closing quote, leaving later words unprotected.

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.

Many recognized ambiguities are skipped. 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

05

What comes back other than a 200

400The text field is missing, null or not a JSON string, or the body is not valid JSON.
413The decoded text or the request body exceeds its limit.
429Over the rate limit. Carries Retry-After in seconds.
5xxThe service is having a problem. Treat it the way you would treat a timeout.
Text must be a JSON string. Numbers, booleans, arrays and objects receive HTTP 400. An empty string, {"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

06

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 input50,000 UTF-16 code units, roughly 8,000 words of English
Maximum request body304,096 bytes by default, checked before JSON decoding
Rate limit60 requests per minute per caller
AuthenticationOptional. 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.

For a deployment that needs its own capacity and availability arrangements, the self-hosted image is available to discuss for evaluation: contact us. Your team manages the limits, authentication and availability, and submitted text is processed within your network.

If it is unavailable

07

Handling an unavailable check

Confusables has no detection queue or automatic retry and does not persist submitted text by default. Cached word lists and in-memory rate-limit windows are operational state. The calling application decides how to handle a timeout. For an optional check before posting, it can let the original text proceed while explaining that the check was unavailable.

async function detectBeforePosting(original) {
  try {
    const res = await fetch("https://api.confusables.io/v1/check", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text: original }),
      signal: AbortSignal.timeout(500)
    });
    if (res.ok) return await res.json();
  } catch {
    // Network failure or timeout: the check was unavailable.
  }
  return null;
}

This helper returns a detection response or null when the check is unavailable. The calling application validates the response and its issue offsets against the current draft, presents any suggestions for author review, and keeps the original text as the publication payload unless the author edits it. A failed check can let the original through with an honest unavailable notice; it is not an all-clear. The helper never uses corrected as a publication payload.

A timeout bounds the integration's wait. Engine timings exclude network and hosting delays and are not a response-time guarantee.

Running it yourself

08

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 self-hosted image is available to discuss for evaluation. A note to contact@confusables.io saying what you want to find out is enough to start.

The FAQ describes the supported words, how the rules work and the limits of detection.

Read the FAQ

Using the public service

The Terms of Service describe permitted evaluation, content rights and service limits. This section provides the operational guidance.

09

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.