Brand voice API: endpoints, fields and the score that comes back
By Jack Stovell · published 2026-09-21 · checked 2026-09-20
The API builds a voice profile from writing samples or a consented public site, generates new copy in that voice, and scores any piece of text against the profile. Every call returns a voice_match_score between 0 and 1, backed by the per-feature deltas that produced it. Every plan gets this, Free included.
The endpoints
Here's the thing: there's no locked tier hiding the interesting bits. The REST base is `https://auth.scriptgrain.com/functions/v1/api-v1`, auth is a bearer key starting `sg_live_` (minted once in Settings, then API, shown to you exactly once, so save it), and every response comes wrapped in the same envelope: `{ data, meta }`, with a request id in there for support tickets.
Profiles live at `POST /v1/profiles` and `GET /v1/profiles/{id}`. Scoring lives at `POST /v1/voice-match`, free on every plan. Generation lives at `POST /v1/generations`, plus a matching `GET` for listing and fetching, and `POST /v1/generations/{id}/edit` to save your final edited version back in (also free, and it feeds the closed-loop learning that sharpens future drafts). Around those three sit the supporting cast: `/v1/compare-voice` and `/v1/outlines` (both free), `/v1/polish` and `/v1/humanize` (1 credit each, skipped free if the draft already clears the bar), `/v1/ai-detect` (free), `/v1/mimic` (1 brand-mimic credit, consent required), voice monitor endpoints, memory endpoints, and billing routes that always hand off to Stripe in the browser, never through the API directly.
Rate limits: 60 requests a minute per key across the board, with tighter caps on the expensive operations, profile extraction at 5 an hour per account, voice match scoring at 200 an hour, brand mimic at 3 an hour. Go over and you'll get `429 rate_limited`. If you're doing generation work in a script, send an `Idempotency-Key` header (up to 200 characters) on `POST /v1/generations`; retry with the same key and you get the stored response back, no second charge. Fire it again while the first call is still running and you'll get `409 concurrent_request` instead.
There's also an MCP server at `https://mcp.scriptgrain.com/mcp`, streamable HTTP, with legacy SSE at `/sse`. It exposes 30 tools covering the same ground: `create_profile`, `check_voice_match`, `generate_content`, `save_edit`, and so on. If you're wiring this into Claude Code, claude.ai, ChatGPT, Cursor, Gemini CLI, or Meta Muse rather than calling REST directly, that's the door. Full tool list is at /reference/mcp-tool-reference, and the general shape of the MCP setup is covered at /ai-writing-api-and-mcp-server.
| Endpoint | Method | Cost | Returns |
|---|---|---|---|
| /v1/profiles | POST | 1 extraction slot | profile_id, job_id, status processing |
| /v1/profiles/{id} | GET | free | status, progress_percent, then narrative, confidence_score, attributes (45) |
| /v1/voice-match | POST | free | voice_match_score, voice_match_notes, deltas, word_count |
| /v1/compare-voice | POST | free | pairwise scores for up to three pieces against a main piece |
| /v1/outlines | POST | free | an outline to approve |
| /v1/generations | POST | 1 credit per variant | content, word_count, within_tolerance, voice_match_score, notes |
| /v1/generations/{id}/edit | POST | free | saves the final; feeds learning |
| /v1/polish | POST | 1 credit (free skip) | best revision toward the target, scored |
| /v1/humanize | POST | 1 credit (free skip) | revised text, before and after scores |
| /v1/ai-detect | POST | free | human score and per-signal breakdown |
| /v1/mimic | POST | 1 brand-mimic credit | a profile from a public site, with consent |
| /v1/profiles/{id}/monitor | GET / POST | free (paid plans) | watched URLs, monthly scores |
| /v1/profiles/{id}/memory | GET / POST | free | topics, claims, stories; sync; add pieces; settings |
| /v1/profiles/{id}/rewrite-page | POST | 1 credit per 1,500 words | the page rewritten in the voice, scored before and after |
| /v1/billing | GET | free | plan, credits, upgrade_url |
Building a profile
You send `POST /v1/profiles` with a name and a `samples` array: 1 to 20 strings, each between 50 and 5,000 words, 50,000 words total across the lot. The response comes back fast with `profile_id`, `job_id`, and `status: "processing"`. That's it. The heavy lifting happens async.
Poll `GET /v1/profiles/{id}` and while it's running you get a `progress_percent`. When it finishes, the payload changes shape: you get a `narrative` (a plain-English read of the voice), a `confidence_score`, and an `attributes` object holding all 45 measured attributes, contraction rate, sentence length variance, clause ordering, the lot. The full schema for that object is documented at /reference/voice-profile-json-schema, and the scoring logic behind each attribute sits at /reference/voice-measurement-framework.
There's a second path if you don't have samples handy but do have a consented public site: the brand-mimic route, `POST /v1/mimic`. It costs 1 brand-mimic credit and requires consent; it's for building a profile from a site you have permission to read, not for scraping a competitor because you liked their tone. Three calls an hour on this one, deliberately tight.
Here's an illustrative request, trimmed for space:
POST /v1/profiles
{
"name": "Q3 blog voice",
"samples": [
"Our onboarding guide starts with why the tool exists...",
"Last week's newsletter opened with a story about a client call..."
]
}
And the completed profile, once `status` flips:
{
"data": {
"profile_id": "0b1d4a6e-4f5c-4d1a-9e3b-6f2a7c8d9e01",
"status": "complete",
"narrative": "Direct, warm, conclusion-first, moderate contraction use.",
"confidence_score": 0.91,
"attributes": {
"contraction_frequency": 18.4,
"avg_sentence_length": 16.9,
"formality_score": 4.6
}
},
"meta": { "request_id": "9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f" }
}
That's a slice of the 45 attributes, obviously, not the full object. But you get the idea: numbers, not vibes.
Scoring a draft: the voice-match endpoint
So this is the one people underuse, mostly because they assume it costs something. It doesn't. `POST /v1/voice-match` is free on every plan, no exceptions, and it's the endpoint that turns "does this sound like us" from a Slack argument into a number.
You send `profile_id` and `text`, the text needs to be at least 20 words and no more than 50,000 characters. Back comes `voice_match_score` (0 to 1), a short `voice_match_notes` summary, and a `deltas` array. Each entry in that array has a `feature`, a human `label`, a `similarity` score, a `weight`, a `salient` flag and a `detail` string; `word_count` sits beside the array at the top level. That detail string is the useful bit, it reads like "contraction rate (draft 6.0 vs profile 20.0 per 1,000 words)", which tells you exactly what to fix rather than just that something's off.
Reading the score: 0.85 to 0.95 is on-voice, 0.5 to 0.8 is partial (usually salvageable with a light edit), under 0.45 is off-voice and probably wants a rewrite rather than a polish. The deltas table beneath a score breaks down which features dragged it down and by how much, weighted by how much that feature matters to the profile overall. A low-weight mismatch on, say, semicolon frequency won't tank a score the way a mismatch on sentence length variance will. Full weighting methodology is at /reference/voice-measurement-framework if you want the maths.
Sample request:
POST /v1/voice-match
{
"profile_id": "0b1d4a6e-4f5c-4d1a-9e3b-6f2a7c8d9e01",
"text": "We're thrilled to announce our exciting new feature..."
}
Sample response:
{
"data": {
"voice_match_score": 0.42,
"voice_match_notes": "Formality and exclamation use diverge sharply from profile.",
"deltas": [
{
"feature": "exclamation_rate",
"label": "Exclamation rate",
"similarity": 0.1,
"weight": 1.5,
"salient": true,
"detail": "exclamation rate (draft 47.6 vs profile 0.4 per 1,000 words)"
}
],
"word_count": 42
}
}
That one delta alone tells you the fix: drop the exclamation marks.
Generating in the voice
`POST /v1/generations` takes `profile_id`, a `brief` (up to 8,000 characters), `content_type` (one of 16: article, linkedin, newsletter, email, short_story, social_post, twitter_thread, instagram_caption, blog_intro, press_release, product_description, ad_copy, website_landing, website_home, website_product, website_about), and a set of optional controls.
`word_count_target` runs 50 to 4,000, and the output lands within plus or minus 15% of that, with `within_tolerance` reported back so you can check programmatically rather than eyeballing it. `register` is formal, professional, conversational, or casual. `english_variant` is uk or us (British or American; older regional codes stored on old profiles are read as British). `extra_direction` gives you up to 2,000 characters of free-text steering on top of the profile. `variants` generates 1 to 3 versions in one call if you want options. `stream` set to true gives you SSE for long pieces rather than waiting on the full response. `continuity` set to true pulls on the profile's memory, useful if this piece needs to sit alongside ones written last month.
The response gives you `id`, `content`, `word_count`, `voice_match_score`, and `voice_match_notes`, scored automatically against the same profile you generated with. So you never have to make a second call just to check the work.
POST /v1/generations
{
"profile_id": "0b1d4a6e-4f5c-4d1a-9e3b-6f2a7c8d9e01",
"brief": "Announce the new dashboard export feature, keep it low-key.",
"content_type": "newsletter",
"register": "conversational",
"english_variant": "uk",
"word_count_target": 400
}
{
"data": {
"id": "7c2e9f10-3b4d-4c5e-8f6a-1b2c3d4e5f60",
"content": "So there's a new export button in the dashboard...",
"word_count": 412,
"voice_match_score": 0.89,
"voice_match_notes": "Strong match on contraction rate and sentence rhythm."
}
}
412 against a 400 target, well inside tolerance.
Error codes and limits
The error set is short and does what it says: `401 unauthorized` for a bad or missing key, `402 out_of_credits` when a metered call runs dry, `403 plan_not_eligible` or `403 extraction_cap_exceeded` for plan-gated or capped actions, `404 not_found`, `409 concurrent_request` for that idempotency collision mentioned earlier, `429 rate_limited` for the per-minute or per-hour caps, and `502 upstream_error` when something further down the chain falls over.
Credit costs and plan-by-plan limits (extraction slots, generation credits, what's metered versus free) are laid out in full at /reference/api-plan-limits. Worth a read before you build anything that calls `/v1/profiles` or `/v1/mimic` in a loop; those two have the tightest caps for good reason.
Questions
Is voice-match scoring really free on every plan?
Yes. `POST /v1/voice-match` carries no credit cost on Free or any paid plan, capped at 200 calls an hour per account. Same goes for `/v1/compare-voice`, `/v1/outlines`, `/v1/ai-detect`, and saving an edit via `/v1/generations/{id}/edit`. The metered operations are generation, polish, humanize, brand-mimic, and page rewrites.
How many attributes does a completed profile actually contain?
Forty-five. They cover things like contraction rate, sentence length variance, clause ordering, formality, and pronoun distribution, returned in the `attributes` object once `GET /v1/profiles/{id}` reports `status: complete`. The full field-by-field schema, with types and ranges, is documented at [/reference/voice-profile-json-schema](/reference/voice-profile-json-schema).
What counts as a good voice_match_score?
0.85 to 0.95 is on-voice. 0.5 to 0.8 is partial, usually fixable with a light edit rather than a rewrite. Below 0.45 counts as off-voice. The `deltas` array attached to every score tells you which specific features pulled the number down and by how much weight each one carries.
Can I generate several versions of the same brief in one call?
Yes, via the `variants` field on `POST /v1/generations`, accepting 1 to 3. Each variant is generated and scored independently, so you get separate `voice_match_score` values to compare rather than one score covering the lot. Credit cost is 1 credit per variant, so three variants costs three credits, not one.
Does the brand-mimic route scrape any site I point it at?
No. `POST /v1/mimic` requires consent and is built for building a profile from a site you have permission to use, not arbitrary scraping. It's capped at 3 calls an hour per account and costs 1 brand-mimic credit per call, deliberately tighter than the other endpoints given what it's doing.
Methodology
Endpoints, fields and codes are read from docs/api/v1.md and the api-v1 function as of 2026-09-20; example values are illustrative.