Skip to main content

Style Guide Compliance API

The styleguide endpoint checks a text for compliance with style rules you define in the request — house style, brand voice, editorial guidelines, terminology policy. Send the text together with 1–20 rule names (optionally with a short description of each), and the endpoint returns every violation it finds: the verbatim offending passage with its character offsets, the violated rule, a brief note, and a rewrite suggestion that complies with your rules.

Only the rules you list are enforced — the endpoint never volunteers grammar, factuality or style opinions outside them. For general grammar and spelling, see the edits endpoint; for a generic writing-quality rubric, see quality.

You can also define your house style once as a saved ruleset and check against it by name ("ruleset": "House Style") instead of re-sending the rules on every request.

Every reported passage is grounded: it is quoted verbatim from your text and located in it, so you can highlight or auto-fix violations directly from the offsets. The text is checked exactly as submitted (markup is not stripped — your rules may legitimately govern formatting), which means start/end always index the string you sent.

Sample Code​

curl -X POST https://api.sapling.ai/api/v1/styleguide \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "text":"Our synergy-driven solution was leveraged by the team. It is really great!!", "rules": ["no corporate jargon", "active voice", {"name": "no exclamation marks", "description": "Never use exclamation marks, even single ones."}]}'

Sample Response​

{
"violations": [
{
"rule": "no corporate jargon",
"text": "synergy-driven solution",
"start": 4,
"end": 27,
"note": "\"Synergy-driven\" is a corporate buzzword.",
"suggestion": "effective product"
},
{
"rule": "active voice",
"text": "was leveraged by the team",
"start": 28,
"end": 53,
"note": "Passive construction (and \"leveraged\" is itself jargon).",
"suggestion": "the team used"
},
{
"rule": "no exclamation marks",
"text": "It is really great!!",
"start": 55,
"end": 75,
"note": "Uses exclamation marks.",
"suggestion": "It is really great."
}
],
"rules": ["no corporate jargon", "active voice", "no exclamation marks"],
"compliant": false
}

A text with no violations returns "violations": [] and "compliant": true — that is a successful, billable check, not an error.

Batch Requests​

To check many short texts against the same style guide — a queue of support replies, a set of product descriptions — in one request, send texts (a list of 1–10 strings) instead of text. The same rules apply to every item, and the combined length of all items may be up to 10,000 characters (the same cap as a single text). Exactly one of text and texts must be provided.

curl -X POST https://api.sapling.ai/api/v1/styleguide \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "texts": ["Our synergy-driven solution!!", "The team shipped the feature."], "rules": ["no corporate jargon", "no exclamation marks"]}'

The batch response is {"results": [...]} with one entry per input, in input order; each entry has exactly the single-response shape above, and each entry's violation offsets index that item's own text:

{
"results": [
{
"violations": [
{
"rule": "no corporate jargon",
"text": "synergy-driven solution",
"start": 4,
"end": 27,
"note": "\"Synergy-driven\" is a corporate buzzword.",
"suggestion": "effective product"
},
{
"rule": "no exclamation marks",
"text": "Our synergy-driven solution!!",
"start": 0,
"end": 29,
"note": "Uses exclamation marks.",
"suggestion": "Our synergy-driven solution."
}
],
"rules": ["no corporate jargon", "no exclamation marks"],
"compliant": false
},
{
"violations": [],
"rules": ["no corporate jargon", "no exclamation marks"],
"compliant": true
}
]
}

Items are billed individually (a batch of N costs the same as N single requests), and each item is cached the same way as a single request — batches and single calls share the cache. If any item fails to process, the whole request returns a 502 and nothing is billed; the items that did succeed are already cached, so a retry only re-runs the failures.

Saved Rulesets​

Instead of re-sending your rules on every check, save them once as a named ruleset and pass "ruleset": "<name>" in place of "rules" on the check endpoint (both the single and the batch form). A resolved ruleset behaves exactly as if you had sent its rules inline — same model behavior, same caching, same billing — so the two forms are interchangeable per request.

Rulesets belong to your API key's account: keys scoped to a team share one team-wide namespace (define the house style once, every caller of the team's keys can check against it), and other keys use their user's personal namespace. Names are matched case-insensitively, and each account can hold up to 100 rulesets. Managing rulesets requires a per-account API key and is not billed; it also works while the key is otherwise blocked (e.g. depleted credits) — it is account configuration, not usage.

Create or replace (POST) — the name is the identity (case-insensitive), so posting an existing name replaces its rules:

curl -X POST https://api.sapling.ai/api/v1/styleguide/rulesets \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "name": "House Style", "rules": ["no corporate jargon", {"name": "no exclamation marks", "description": "Never use exclamation marks, even single ones."}]}'
{
"ruleset": {
"name": "House Style",
"rules": [
{"name": "no corporate jargon", "description": ""},
{"name": "no exclamation marks", "description": "Never use exclamation marks, even single ones."}
],
"created_at": "2026-09-23T12:00:00",
"updated_at": "2026-09-23T12:00:00"
},
"created": true
}

name is up to 100 characters (surrounding and duplicate whitespace is normalized); rules has exactly the shape and bounds of the check endpoint's rules parameter and is stored normalized. created is false when an existing ruleset was replaced.

List (GET):

curl "https://api.sapling.ai/api/v1/styleguide/rulesets?key=<api-key>"
{"rulesets": [{"name": "House Style", "rules": [...], "created_at": "...", "updated_at": "..."}]}

Delete (DELETE) — the name may ride the query string (some HTTP stacks drop DELETE bodies) or a JSON body:

# By query string
curl -X DELETE "https://api.sapling.ai/api/v1/styleguide/rulesets?key=<api-key>&name=House%20Style"

# By JSON body
curl -X DELETE https://api.sapling.ai/api/v1/styleguide/rulesets \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "name": "House Style"}'
{"deleted": "House Style"}

Deleting a name that does not exist returns a 404. All three operations return a 400 for the shared public demo credential — saved rulesets need a per-account key. They also return a 403 for a key's public (client-side) form: listing, saving and deleting rulesets require the private API key, since a public key ships in browser code. Checking text against a saved ruleset still works with either form.

Checking against a saved ruleset — pass ruleset in place of rules (exactly one of the two):

curl -X POST https://api.sapling.ai/api/v1/styleguide \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "text": "Our synergy-driven solution!!", "ruleset": "House Style"}'

An unknown ruleset name returns a 404 ({"msg": "No saved ruleset named \"...\"."}) and nothing is billed. The response is identical to the inline form, with rules echoing the saved rule names.

Request Parameters​

POST to https://api.sapling.ai/api/v1/styleguide

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 text to check, up to 10,000 characters. Checked exactly as submitted — markup is not stripped (style rules may govern formatting), so the returned offsets always index this string. Empty or whitespace-only text, or text containing a NUL character (U+0000), returns a 400 — NUL cannot be stripped without shifting the returned offsets. The text is treated as untrusted data: instructions inside it are ignored and it is judged on its content alone. Provide exactly one of text and texts.

texts: List[String]
Batch form (see Batch Requests): 1–10 texts to check in one request, each treated exactly like text above, with a combined length of up to 10,000 characters. An empty item, or an item containing a NUL character, returns a 400 naming its index. The response becomes {"results": [...]}, one entry per item in input order, each item's offsets indexing that item's own text.

rules: List[String | Object]
The style rules to enforce: between 1 and 20 entries. Each entry is either a string (the rule name) or an object {"name": "...", "description": "..."} where description is optional. Names are trimmed, internal whitespace is collapsed, and they must be at most 80 characters and case-insensitively unique; descriptions are at most 400 characters and carry the nuance the model follows (what the rule requires or forbids, and any exceptions). Names are returned in this normalized form in the response, so match your application logic on the normalized names. Provide exactly one of rules and ruleset.

ruleset: String
The name of a saved ruleset to check against, in place of inline rules. Matched case-insensitively within your API key's account (its team, for team-scoped keys); an unknown name returns a 404 and nothing is billed. Provide exactly one of rules and ruleset.

Response Parameters​

violations: List[Object]
Up to 20 violations, most important first. May be empty (the text complies). Each has:

  • rule: the violated rule's name — always one of the submitted rule names (as normalized).
  • text: the offending passage, quoted verbatim from the submitted text (at most 200 characters).
  • start / end: the passage's character offsets in the submitted text (text equals the exact slice submitted_text[start:end]). Offsets count Unicode code points (Python str indexing) — not UTF-16 code units or bytes — so in JavaScript slice with Array.from(text) if the input may contain characters outside the Basic Multilingual Plane (e.g. emoji), whose UTF-16 string indices would otherwise misalign. Both are null in the rare case the passage cannot be located in the submitted text; the violation is still real and text still shows the passage.
  • note: one short sentence explaining how the passage violates the rule.
  • suggestion: a rewrite of the passage that complies with all the rules. May be an empty string when no rewrite applies (e.g. the fix is a deletion).

When the same problem repeats many times, the first few occurrences are reported and the note says so.

rules: List[String]
The checked rule names, normalized (trimmed, internal whitespace collapsed), in the order submitted.

compliant: Boolean
true exactly when violations is empty.

Errors​

StatusMeaning
400Validation error — missing, empty, oversized or NUL-containing text (or a texts item, named by index), both or neither of text/texts, both or neither of rules/ruleset, or invalid rules (e.g. {"msg": "Invalid rules: Needs between 1 and 20 rules."}). The body is {"msg": "..."}.
401 / 403Missing or invalid API key.
404ruleset names no saved ruleset in your account ({"msg": "No saved ruleset named \"...\"."}). Nothing is billed.
429Rate limit exceeded or key over capacity.
502{"msg": "Unexpected error checking text against the style guide."} — the checking model failed; safe to retry. Not billed and not cached (on the batch form, the whole request: see Batch Requests).

Tips​

  • Describe your rules. Plain names work for common conventions (active voice, no exclamation marks), but a description is what carries YOUR house style: what counts as jargon for you, which spellings you standardize on, the exceptions ("Use sentence case in headings, except for product names").
  • One rule per concern. Violations name the rule they break, so separate rules (no jargon, serial comma, spell out numbers under ten) give you per-rule reporting and let editors filter — a single mega-rule collapses everything into one bucket.
  • Auto-fix from the offsets. text is always the exact start:end slice of what you sent, so you can splice suggestion over [start, end) directly (apply from the last violation to the first so earlier offsets stay valid). Offsets are Unicode code-point indices, so in JavaScript index over Array.from(text) rather than the raw string when the input may contain non-BMP characters like emoji. Skip violations whose offsets are null.
  • A terminology policy works well as rules — e.g. {"name": "product naming", "description": "The product is 'Acme Studio', never 'the studio' or 'AcmeStudio'"}. For mechanical find-and-replace pairs, custom mappings on the edits endpoint are cheaper.
  • Results are cached on Sapling's side for about three days, keyed on the text and the full rule set (names and descriptions) — re-running the same document against the same rules is fast and returns the same violations; changing any rule re-checks.