Aandall API
Fact-check and localize documents from your own application — a CMS, a publishing pipeline, or a script.
Getting started
- Create an API key on your Account page, under API keys. Copy it — it's shown only once.
- Send it with every request as
Authorization: Bearer aandall_live_… - Usage draws on your normal token balance. No separate billing.
Keep keys server-side. A key spends your tokens, so never put one in a web page, a mobile app, or public source control. If a key leaks, revoke it on your Account page.
How work is submitted
Fact-checking and localization take from a few seconds to a couple of
minutes, so the API is asynchronous. You create a job, get an
id back immediately, then poll the job until it reports
succeeded. Polling every 3–5 seconds is plenty. A job survives
network problems on your side — the work continues on our servers, and the
result waits for you.
Base URL
https://aandall.org/v1
Improve a document
Grammar, fact-checking against live sources, references, and your learned writing style.
POST /v1/documents/improve
{
"content": { "format": "html", "body": "<p>The sun rises in the west.</p>" },
"comments": [
{ "id": "c1", "note": "make this punchier" }
]
}
Responds 202 with a job:
{ "object": "job", "id": "AbC123…", "type": "improve",
"status": "queued", "created": "2026-08-02T14:05:00Z",
"result": null, "usage": null, "error": null }
Anchoring comments to specific passages
A comment is an instruction about a particular piece of text — the API equivalent of a margin note. Anchoring takes two parts that must match:
- In the HTML, wrap the passage the note is about in
<span class="cmt" data-comment-id="your-id">…</span> - In
comments, send an entry whoseidis that same your-id, with your instruction innote.
The ids are yours to choose — any short string works, as long as the two sides agree.
{
"content": {
"format": "html",
"body": "<p>Our platform <span class=\"cmt\" data-comment-id=\"c1\">leverages
synergistic paradigms</span> to deliver value. <span class=\"cmt\"
data-comment-id=\"c2\">Founded in 2019.</span></p>"
},
"comments": [
{ "id": "c1", "note": "plain English please, this is jargon" },
{ "id": "c2", "note": "check this date, I think it was 2018" }
]
}
Each note is applied to its marked passage, and the
<span> wrappers are stripped from the result — the text
you get back is clean.
Document-wide instructions. A comment whose id matches no
span is treated as guidance for the whole document. Useful for things like
{"id": "g1", "note": "keep it under 400 words"} or
{"id": "g2", "note": "British spelling throughout"}.
No comments at all? Omit the field. The document then gets a full editorial pass: grammar, fact-checking, references, and your learned writing style.
If you're pulling HTML from a CMS, add the comment spans after fetching the content and before sending it — don't store them in the CMS. They're a transport detail, not part of the author's document.
Localize a document
Send your document as segments — typically one per paragraph or heading. Segment ids are yours; we echo them back so you can put translations where they belong.
POST /v1/documents/localize
{
"language": "Spanish",
"region": "Mexico",
"segments": [
{ "id": "p1", "content": { "format": "html", "body": "<h1>Welcome</h1>" } },
{ "id": "p2", "content": { "format": "html", "body": "<p>Hello there.</p>" },
"translation": { "body": "<p>Hola.</p>" },
"note": "too formal — make it warmer" }
]
}
Translator notes and human edits
Localize has no spans — each segment is already its own unit, so a
note attaches directly to the segment it sits in. Three
combinations, and each behaves differently:
| What you send | What happens |
|---|---|
content only |
Translated fresh. |
content + translation |
Returned unchanged. A human approved this wording; we never silently overwrite it. |
content + translation + note |
Revised — honouring both the human's wording and their instruction. |
This is what makes iterative review work: send the whole document every time, and only the segments a translator has commented on get reworked. Everything they've settled stays put, and untouched segments are retranslated with the latest context.
Poll a job
GET /v1/jobs/{id}
status is one of queued, running,
succeeded, failed. A finished improve job:
{ "object": "job", "id": "AbC123…", "type": "improve", "status": "succeeded",
"result": {
"object": "improve_result",
"content": { "format": "html", "body": "<p>The sun rises in the east…</p>" },
"analysis": { "format": "markdown", "body": "## Fact-checking…" }
},
"usage": { "object": "usage", "tokens_in": 4210,
"tokens_out": 3050, "tokens_charged": 7260 },
"error": null }
A finished localize job returns result.segments, each with your
original id and the translated content.body.
Check your balance
GET /v1/account
{ "object": "account", "email": "you@example.com",
"tokens": { "object": "token_balance", "balance": 412000, "used_total": 88000 },
"limits": { "object": "limits", "requests_per_minute": 60,
"concurrent_jobs": 10, "max_words_per_document": 10000 } }
Errors
(Available only to select developers, reach out to the.sri.ss@gmail.com if you need access)
Every error shares one shape. Branch on type, never on the
message — messages may be reworded, types will not.
{ "object": "error", "type": "insufficient_tokens",
"message": "Not enough tokens for this job.",
"details": { "tokens_required": 180000, "tokens_available": 40000 } }
| type | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing, malformed, or revoked API key. |
invalid_request | 400 | Something's wrong with the request body. |
insufficient_tokens | 402 | Top up at aandall.org/account. |
not_found | 404 | No such job, or it isn't yours. |
too_large | 413 | Document exceeds the word limit. |
rate_limited | 429 | Slow down; see details.retry_after_seconds. |
server_error | 500 | Our fault. Retry; you aren't charged for failed jobs. |
A complete example
curl -X POST https://aandall.org/v1/documents/improve \
-H "Authorization: Bearer $AANDALL_KEY" \
-H "Content-Type: application/json" \
-d '{"content":{"format":"html","body":"<p>Draft text.</p>"}}'
# → {"object":"job","id":"AbC123","status":"queued", …}
curl https://aandall.org/v1/jobs/AbC123 \
-H "Authorization: Bearer $AANDALL_KEY"
On Windows: PowerShell's curl is
an alias for a different tool with different syntax. Use
curl.exe explicitly, or Invoke-RestMethod.
Our compatibility promise
We will add fields to these responses over time. We will not remove them,
rename them, or change what they mean. Write your integration to ignore
fields it doesn't recognise and it will keep working. If we ever need a
genuinely incompatible change, it will appear at /v2 and
/v1 will keep running.
Questions
Building a CMS plugin or an integration? Email the.sri.ss@gmail.com — we'd like to hear what you're making.