Voice Compare API: compare writing samples with no profile

By Jack Stovell X · Instagram · LinkedIn · published 2026-10-29

The Voice Compare API checks whether pieces of writing share a voice, and it does it without a profile. You send a main piece and one to three others; it scores each against the main one. Developers, agents and assistant users call it. It's free on every plan.

At a glance

What it does: Scores up to three pieces against a main piece with no profile at all: is this page still in the same voice?

Endpoint`POST /v1/compare-voice`
MCP tool`compare_voice`
CostFree on every plan
Limits1 main piece and 1 to 3 others; 120+ words each, read up to 3,500 words; text or a public URL

How the comparison works

You post to POST /v1/compare-voice. The main piece gets measured and judged into a temporary baseline. That baseline lives only for the check. Then each of the 1 to 3 others is scored against it, using the same feature-delta engine as voice match.

Each piece is pasted text or a public URL, not both. With URLs, menus and footers are skipped, so you're measuring the writing and not the navigation. Pages are fetched directly, with Firecrawl as a fallback on paid plans.

Two limits matter. Each piece needs 120+ words, because less than that gives the engine too little to measure. Past 3,500 words it's truncated.

The text you send is not kept, and no profile gets built. ScriptGrain measures writing; it is never trained on it.

Reading the bands

Every result lands in one of three bands:

  1. on_voice at 0.80 or above
  2. drifting at 0.55 or above
  3. off_voice for anything lower

These bands are pairwise-calibrated, and that makes them a different scale from the profile score. A profile score asks how closely a piece matches a full 45-attribute profile built from a writer's own samples. A pairwise band asks something smaller: do these two pieces sound alike? So don't compare the numbers across the two.

The bar for on_voice is high on purpose. About 1 in 10 pairs by different writers reach it.

The live example: ScriptGrain's own pricing page against its /why page scored 0.75, which is drifting. The top difference was semicolons, 5.2 vs 1.1 per 1,000 words. Same company, same brand, and still a measurable gap. That's the point of the check.

Each result carries top_differences and top_matches with measured numbers, so you can see why. The notes field is always present. With a URL, it cautions that marketing copy reads brand voice only weakly. Fair enough; take that one seriously.

When to use it

Three situations come up most.

Checking a site before you have a profile. You've got a homepage, a pricing page and a blog post, and you want to know if they sound like one organisation. Paste the URLs, pick the piece you trust most as the main one, and read the bands. No setup, no profile, no cost.

Several writers on one brand. Make the lead writer's piece the baseline and score everyone else's against it. The top_differences list tells you where each writer pulls away, whether that's sentence length, punctuation habits or something else you can measure. For a bigger group, the multi-client voice drift check covers that ground.

A quick drift check in an agent. The REST API and the MCP server at https://mcp.scriptgrain.com/mcp are on every plan, Free included. An agent can call it after drafting and flag anything that lands in drifting or off_voice. Auth is an sg_live_ key minted at Settings, then API, sent as Authorization: Bearer. The rate limit is 60 requests a minute per key, and calls draw on the same credits as the app. There's no separate API quota.

Want to try it by hand first? The same check runs free in the browser at compare writing samples.

Request and response

Called live on 29 October 2026: ScriptGrain's pricing page compared with its /why page, both by URL (top difference only).

curl -X POST https://auth.scriptgrain.com/functions/v1/api-v1/v1/compare-voice \
  -H "Authorization: Bearer sg_live_..." \
  -H "Content-Type: application/json" \
  -d '{"main": {"label": "Why page", "url": "https://scriptgrain.com/why"},
       "others": [{"label": "Pricing", "url": "https://scriptgrain.com/pricing"}]}'
{
  "results": [
    {
      "label": "Pricing",
      "score": 0.75,
      "band": "drifting",
      "compared": 25,
      "top_differences": [
        {
          "feature": "semicolon_frequency",
          "label": "semicolon use",
          "detail": "semicolon use (this piece 5.2 vs main piece 1.1 per 1,000 words)",
          "similarity": 0
        }
      ]
    }
  ],
  "summary": {
    "average": 0.75,
    "on_voice": 0,
    "drifting": 1,
    "off_voice": 0
  }
}

In Claude, ChatGPT or Cursor

Over MCP this is the tool `compare_voice`. Connect `https://mcp.scriptgrain.com/mcp` once (a custom connector in claude.ai, developer mode in ChatGPT on the web, the MCP settings in Cursor, or `claude mcp add` in Claude Code), then ask in plain words:

Compare our about page with these two blog posts and tell me which one drifts furthest from its voice, with the numbers.

It needs no profile, so it also works through the keyless `/try` endpoint (5 checks an hour per network).

Get a key

API and MCP access is on every plan, Free included. Create a free account (no card), then mint a key at Settings, then API: it starts `sg_live_`, is shown once, and goes in an `Authorization: Bearer` header. Every request, field and error code is in the OpenAPI spec.

Questions

Do I need a voice profile to use the Voice Compare API?

No. That's the whole idea. The main piece is measured into a temporary baseline for the length of the check, and the others are scored against it. No profile is built or kept, so you can compare pieces without one.

How is a compare band different from a profile score?

They're calibrated differently. Compare bands are pairwise: on_voice at 0.80 or above, drifting at 0.55 or above, otherwise off_voice. A profile score measures a piece against a full profile of 45 measured attributes across eight layers. So a 0.75 in compare isn't the same claim as a 0.75 elsewhere.

What can I send, and what are the limits?

Each piece is pasted text or a public URL, never both. Send a main piece plus 1 to 3 others. Each needs 120+ words and is truncated past 3,500. For URLs, menus and footers are skipped. Fetching is direct, with Firecrawl as a fallback on paid plans.

Does it cost anything, and how is it authenticated?

The check itself is free on every plan. Access is through the REST API or the MCP server, both on every plan, Free included. You mint an sg_live_ key at Settings, then API, and send it as Authorization: Bearer. There's a limit of 60 requests a minute per key, and payments never happen through the API.

Related