Aandall.

Aandall API

Fact-check and localize documents from your own application — a CMS, a publishing pipeline, or a script.

Getting started

  1. Create an API key on your Account page, under API keys. Copy it — it's shown only once.
  2. Send it with every request as Authorization: Bearer aandall_live_…
  3. 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:

  1. In the HTML, wrap the passage the note is about in <span class="cmt" data-comment-id="your-id">…</span>
  2. In comments, send an entry whose id is that same your-id, with your instruction in note.

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 sendWhat 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 } }
typeHTTPMeaning
unauthorized401Missing, malformed, or revoked API key.
invalid_request400Something's wrong with the request body.
insufficient_tokens402Top up at aandall.org/account.
not_found404No such job, or it isn't yours.
too_large413Document exceeds the word limit.
rate_limited429Slow down; see details.retry_after_seconds.
server_error500Our 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.