Creating the first report
Analysis can take several seconds. The API accepts the text, processes it outside the initial request and exposes a separate resource for retrieving its state.
Authentication
Direct integrations require an active key. Send it in the Authorization header and never include it in public code or client applications.
Authorization: Bearer art_live_…
Official SDKs
Recommended integration path. The official clients wrap the complete asynchronous cycle — creation, Retry-After-compliant waiting, idempotent retries and result retrieval — behind a single call, with no third-party dependencies.
pip install artext# Install the official client: pip install artext
# Run this code on a server; never expose the key in a browser.
import os
from artext import Artext
client = Artext(
api_key=os.environ["ARTEXT_API_KEY"],
base_url="http://uned.tail9cc3dd.ts.net",
)
# analyze creates the report, waits honoring Retry-After and returns the result.
# A failed analysis raises an exception carrying the error detail.
report = client.analyze(
"The application will be reviewed at the present time.",
language="en",
domain_slug="plain-language",
text_type_slug="legal-administrative-text-addressed-to-citizens",
)
print(report.result.measurement("word-count").value)
for suggestion in report.result.suggestions:
print(suggestion.metric_id, suggestion.summary)
for occurrence in suggestion.occurrences:
print(" ", occurrence.text, "->", occurrence.replacement)
npm install artext// Install the official client: npm install artext
// Run this code on a server; never expose the key in a browser.
import { Artext } from "artext";
const client = new Artext({
apiKey: process.env.ARTEXT_API_KEY,
baseUrl: "http://uned.tail9cc3dd.ts.net",
});
// analyze creates the report, waits honoring Retry-After and returns the result.
// A failed analysis raises an exception carrying the error detail.
const report = await client.analyze({
text: "The application will be reviewed at the present time.",
language: "en",
domainSlug: "plain-language",
textTypeSlug: "legal-administrative-text-addressed-to-citizens",
});
for (const suggestion of report.result.suggestions) {
console.log(suggestion.metric_id, suggestion.summary);
for (const occurrence of suggestion.occurrences) {
console.log(" ", occurrence.text, "->", occurrence.replacement);
}
}
The examples below show the complete cycle over plain HTTP, without an SDK. The openapi.json schema supports generating clients for other languages.
Asynchronous semantics
arText follows standard HTTP semantics for work that cannot finish within the initial request. You do not keep the POST open or choose an arbitrary polling interval.
202 AcceptedThe text was accepted, but analysis may still be queued or running. It does not mean the report is complete.status_url + LocationThey identify the report resource. Always retrieve this same URL; do not repeat the POST to discover whether processing finished.Retry-AfterMinimum seconds to wait before the next retrieval. An earlier request can receive 429.events_urlOptional Server-Sent Events stream of state transitions. If unused or interrupted, status_url remains the fallback.HTTP Semantics · 202HTTP Semantics · Retry-AfterHTML · Server-sent events
Integration sequence
Analysis is not executed within the initial request: it is queued and processed in the background. The integration consists of the following five steps.
POST /v1/reportsThe plain text, the language and the API key are submitted.
202 · report_id + status_urlThe API validates, queues and answers in milliseconds. There is no result yet.
queued → runningThe engine analyzes the text in the background, with no client involvement.
GET status_url · Retry-AfterThe client polls the status honoring Retry-After or subscribes to events_url (SSE).
completed · resultThe full report is fetched from result; a failed analysis returns failed with the error.
The creation request must not be kept open while waiting for the result. Creating the report and fetching it are independent operations; this separation tolerates long analyses, restarts and intermediate proxies.
# Keep the key out of source code and load it from the environment.
# Requires curl and jq.
# export ARTEXT_API_KEY='art_live_...'
set -euo pipefail
API_BASE='http://uned.tail9cc3dd.ts.net'
IDEMPOTENCY_KEY="report-$(date +%s)-$RANDOM"
headers_file=$(mktemp)
trap 'rm -f "$headers_file"' EXIT
# Create the report once. Idempotency-Key makes a POST retry safe after a network failure.
created=$(curl --fail-with-body --silent --show-error --dump-header "$headers_file" \
--request POST "$API_BASE/v1/reports" \
--header "Authorization: Bearer $ARTEXT_API_KEY" \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'Content-Type: application/json' \
--data-binary '{"language":"en","domain_slug":"plain-language","text_type_slug":"legal-administrative-text-addressed-to-citizens","text":"The application will be reviewed at the present time.","external_id":"document-123"}')
# status_url is relative; resolve it against API_BASE.
status_url="$API_BASE$(jq -r '.status_url' <<<"$created")"
retry_after=$(awk 'BEGIN { IGNORECASE=1 } /^Retry-After:/ { gsub("\r", "", $2); print $2 }' "$headers_file" | tail -1)
retry_after=${retry_after:-2}
# Poll the same report while honoring Retry-After and an overall deadline.
for attempt in $(seq 1 90); do
sleep "$retry_after"
report=$(curl --fail-with-body --silent --show-error --dump-header "$headers_file" \
--header "Authorization: Bearer $ARTEXT_API_KEY" \
"$status_url")
status=$(jq -r '.status' <<<"$report")
case "$status" in
completed) break ;;
failed) jq -r '.error.message // "Analysis failed"' <<<"$report" >&2; exit 1 ;;
queued|running)
retry_after=$(awk 'BEGIN { IGNORECASE=1 } /^Retry-After:/ { gsub("\r", "", $2); print $2 }' "$headers_file" | tail -1)
retry_after=${retry_after:-2}
;;
*) echo "Unknown status: $status" >&2; exit 1 ;;
esac
done
# Process the result only after completed.
test "$status" = completed || { echo 'Polling timed out' >&2; exit 1; }
jq '.result.measurements, .result.suggestions' <<<"$report"import os
import time
import uuid
from urllib.parse import urljoin
import requests
# Keep the key out of source code and load it from the environment.
API_BASE = 'http://uned.tail9cc3dd.ts.net'
API_KEY = os.environ["ARTEXT_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
payload = {'language': 'en', 'domain_slug': 'plain-language', 'text_type_slug': 'legal-administrative-text-addressed-to-citizens', 'text': 'The application will be reviewed at the present time.', 'external_id': 'document-123'}
# Create the report once. Idempotency-Key makes a POST retry safe after a network failure.
response = requests.post(
f"{API_BASE}/v1/reports",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json=payload,
timeout=30,
)
response.raise_for_status()
created = response.json()
# status_url is relative; resolve it against API_BASE.
status_url = urljoin(f"{API_BASE}/", created["status_url"])
retry_after = int(response.headers.get("Retry-After", "2"))
# Poll the same report while honoring Retry-After and an overall deadline.
deadline = time.monotonic() + 180
while True:
time.sleep(retry_after)
response = requests.get(status_url, headers=HEADERS, timeout=30)
retry_after = int(response.headers.get("Retry-After", "2"))
if response.status_code == 429:
continue
response.raise_for_status()
report = response.json()
status = report["status"]
if status == "completed":
break
if status == "failed":
raise RuntimeError((report.get("error") or {}).get("message") or "Analysis failed")
if status not in {"queued", "running"}:
raise RuntimeError(f"Unknown report status: {status}")
if time.monotonic() >= deadline:
raise TimeoutError("Report polling timed out")
# Process the result only after completed.
result = report["result"]
for measurement in result["measurements"]:
print(measurement["metric_id"], measurement["value"], measurement["unit"])
for index, suggestion in enumerate(result["suggestions"]):
print(index, suggestion["metric_id"], len(suggestion["occurrences"]))// Node.js 18+. Run this code on a server; never expose the key in a browser.
const API_BASE = "http://uned.tail9cc3dd.ts.net";
const API_KEY = process.env.ARTEXT_API_KEY;
if (!API_KEY) throw new Error("ARTEXT_API_KEY is required");
const headers = {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
};
const api = async (path, options = {}) => {
const response = await fetch(new URL(path, `${API_BASE}/`), {
...options,
headers: { ...headers, ...options.headers },
signal: AbortSignal.timeout(30_000)
});
if (!response.ok && response.status !== 429) {
throw new Error(`${response.status}: ${await response.text()}`);
}
return response;
};
const delay = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
// Create the report once. Idempotency-Key makes a POST retry safe after a network failure.
const createdResponse = await api("v1/reports", {
method: "POST",
headers: { "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({"language":"en","domain_slug":"plain-language","text_type_slug":"legal-administrative-text-addressed-to-citizens","text":"The application will be reviewed at the present time.","external_id":"document-123"})
});
const created = await createdResponse.json();
const deadline = Date.now() + 180_000;
let retryAfter = Number(createdResponse.headers.get("Retry-After") || 2);
let report;
// Poll the same report while honoring Retry-After and an overall deadline.
while (Date.now() < deadline) {
await delay(retryAfter * 1_000);
const statusResponse = await api(created.status_url);
retryAfter = Number(statusResponse.headers.get("Retry-After") || 2);
if (statusResponse.status === 429) continue;
report = await statusResponse.json();
if (report.status === "completed") break;
if (report.status === "failed") {
throw new Error(report.error?.message || "Analysis failed");
}
if (!["queued", "running"].includes(report.status)) {
throw new Error(`Unknown report status: ${report.status}`);
}
}
if (report?.status !== "completed") {
throw new Error("Report polling timed out");
}
// Process the result only after completed.
for (const item of report.result.measurements) {
console.log(item.metric_id, item.value, item.unit);
}
for (const [index, item] of report.result.suggestions.entries()) {
console.log(index, item.metric_id, item.occurrences.length);
}Set ARTEXT_API_KEY in the environment. The examples honor Retry-After, recommend Idempotency-Key and stop after 3 minutes.
Report lifecycle
POST returns 202 while analysis continues in the queue. Use the optional events_url SSE stream or poll status_url until a terminal state is reached; do not create the report again.
queuedAccepted and waiting. Pause before polling again.
runningThe analyzer is working. Continue polling.
completedSuccessful terminal state. result contains the report.
failedFailed terminal state. Record error and X-Request-ID.
SSE is optional. When polling, always honor Retry-After; an early request receives 429. Do not treat position_in_queue as a time estimate.
202 · Queued
{
"events_url": "/v1/reports/rep_7f12a4c8/events",
"external_id": "document-123",
"report_id": "rep_7f12a4c8",
"status": "queued",
"status_url": "/v1/reports/rep_7f12a4c8"
}