API Reference

The ArticleGrade API.

Start deep content-quality audits, stream live progress, gate your pipeline on score, and get notified the moment a page passes or fails — straight from your code.

Base URL https://app.articlegrade.com Auth Bearer ag_live_… Spec openapi.json

Introduction#

The ArticleGrade API runs the same dual-LLM audit that powers the dashboard, from your own code. Point it at a live URL and it returns a 0–100 quality score with a structured breakdown: E-E-A-T and factual depth (graded by Claude), SEO and structure (screened by Gemini), plus the specific phrases that hurt the page and how to fix them.

It is a REST API over HTTPS. Every request and response body is JSON (the one exception is the streaming endpoint, which returns Server-Sent Events). All paths below are relative to the base URL.

Base URL. https://app.articlegrade.com — prepend it to every path. The current version prefix is /v1.

Audits are asynchronous: you start one and get back an id immediately, then either poll for the result or subscribe to the stream. A deep audit typically completes in well under a minute.

Authentication#

Authenticate every request with an API key sent as a bearer token:

Authorization header
Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx

As an alternative, you may pass the key in the x-api-key header instead — useful for clients that reserve Authorization for something else:

Alternative header
x-api-key: ag_live_xxxxxxxxxxxxxxxxxxxx

Keys are created and revoked on the Developers page of your dashboard. Live keys are prefixed ag_live_.

API access is a Growth / Enterprise feature. Programmatic keys are available on Growth and Enterprise plans. On lower plans the Developers page is read-only and key creation is disabled — an authenticated call from a plan without API access returns 403.
Keep keys server-side. A key carries your full account permissions and bills against your quota. Never embed one in a browser, mobile app, or public repository. Rotate immediately from the Developers page if a key leaks.

Quotas & metering#

Audits are metered against your plan's monthly quota. A fast screen (the Gemini SEO/structure pass) is cheap; a deep audit (the Claude dual-LLM pass) is what counts against your included allotment.

  • Deep audits beyond your plan's included amount are billed as overage at your plan's per-audit rate — there is no surprise tier jump.
  • When a hard limit is reached (for example a spend cap you've configured, or an unpaid balance), the API returns 402 and stops accepting new audits until the limit clears.
  • Call GET /v1/account at any time to read your current included, used and overage counts.

Errors#

Errors use conventional HTTP status codes and a uniform JSON body:

Error body
{ "error": "a short, human-readable message" }
200Success. The audit record or list is in the body.
202Accepted. The audit (or bulk batch) was queued; the body contains its id(s).
400Bad input — missing or malformed url, an invalid provider, or a body that isn't valid JSON.
401Missing or invalid API key.
402Quota or hard limit hit. Add overage budget or raise your cap, then retry.
403Your plan doesn't include this feature (e.g. bulk, schedules, or API access at all).
404No audit with that id belongs to your account.
502An upstream LLM or fetch failed transiently. Safe to retry with backoff.

Start an audit#

POST/v1/audits

Queues an audit for a single URL and returns immediately. By default this runs the deep dual-LLM audit. Use provider:"gemini" for just the fast SEO/structure screen, or runBoth:true to force the full deep audit explicitly.

Request body

FieldTypeDescription
urlrequiredstringThe fully-qualified https:// URL of the live page to audit.
provideroptional"claude" | "gemini"Defaults to "claude" (the deep audit). "gemini" runs the fast SEO/structure screen only — not a deep audit.
runBothoptionalbooleanWhen true, runs the full deep dual-LLM audit (Claude + Gemini). Equivalent to the default deep path.
curl — start a deep audit
curl -X POST https://app.articlegrade.com/v1/audits \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/blog/best-running-shoes" }'

Response 202

202 Accepted
{
  "id": "aud_8Fq2kR1xR7",
  "status": "queued",
  "url": "https://example.com/blog/best-running-shoes"
}

Take the returned id and poll the audit (or stream it) until status is done or error.

Get an audit#

GET/v1/audits/{id}

Fetches the full audit record. Poll this until status is done (or error). While running, score and most of record are null.

Response fields

FieldTypeDescription
idstringThe audit id, e.g. aud_….
statusstringOne of queued, running, done, error.
scorenumber | nullOverall quality score 0–100. null until the audit finishes.
providerstringThe provider used for this run (claude or gemini).
titlestringThe audited page's title.
live_urlstringThe URL that was fetched and audited.
recordobjectThe full result. See the sub-fields below.
record.dimsarrayScored dimensions: [{ name, val }].
record.errorsarraySpecific issues: [{ phrase, sev, fix, source }].
record.scoresobjectPer-model scores: { claude, gemini }.
record.textstringThe extracted body text that was graded.
record.headingsarrayThe page's heading outline.
passboolean | nullWhether the score met threshold (if one applies), else null.
thresholdnumber | nullThe pass/fail threshold applied, if any.
curl — poll until done
curl https://app.articlegrade.com/v1/audits/aud_8Fq2kR1xR7 \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx"
200 OK — completed audit (abridged)
{
  "id": "aud_8Fq2kR1xR7",
  "status": "done",
  "score": 82,
  "provider": "claude",
  "title": "The Best Running Shoes of 2026",
  "live_url": "https://example.com/blog/best-running-shoes",
  "record": {
    "dims": [
      { "name": "Experience", "val": 78 },
      { "name": "Expertise",  "val": 85 },
      { "name": "Factual depth", "val": 80 }
    ],
    "errors": [
      {
        "phrase": "experts agree these are the best",
        "sev": "high",
        "fix": "Attribute to a named source or cite testing data.",
        "source": "claude"
      }
    ],
    "scores": { "claude": 82, "gemini": 76 },
    "text": "Choosing a running shoe comes down to…",
    "headings": ["How we tested", "Top picks"]
  },
  "pass": true,
  "threshold": 70
}

Stream progress#

GET/v1/audits/{id}/stream

Opens a Server-Sent Events (text/event-stream) connection that pushes live progress while the audit runs — an alternative to polling. Two event types are emitted:

  • status — fired as the audit moves through its phases (fetch, screen, deep pass). Data carries the current status.
  • complete — fired once when the audit reaches done or error; data carries the final score. The stream then closes.
curl — follow the stream
curl -N https://app.articlegrade.com/v1/audits/aud_8Fq2kR1xR7/stream \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx"
event stream
event: status
data: {"status":"running"}

event: complete
data: {"status":"done","score":82}

List audits#

GET/v1/audits

Returns your most recent audits, newest first.

Query parameters

ParamTypeDescription
limitoptionalintegerHow many to return. Defaults to 50.
curl — list recent audits
curl "https://app.articlegrade.com/v1/audits?limit=50" \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx"

Bulk submit#

POST/v1/audits/bulkGrowth+

Submits many URLs in one call. Each accepted URL becomes its own audit you then poll or stream individually. Up to 200 URLs per request.

Request body

FieldTypeDescription
urlsrequiredstring[]Array of https:// URLs to audit. Maximum 200.
curl — bulk submit
curl -X POST https://app.articlegrade.com/v1/audits/bulk \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "urls": ["https://example.com/a", "https://example.com/b"] }'

Response 202

202 Accepted
{
  "accepted": [
    { "id": "aud_aa11", "url": "https://example.com/a" }
  ],
  "rejected": [
    { "url": "not-a-url", "reason": "invalid url" }
  ]
}

Account & usage#

GET/v1/account

Returns your organization, current usage against quota, and the feature flags your plan enables — handy for showing remaining budget or deciding whether a feature is available before you call it.

curl — account & usage
curl https://app.articlegrade.com/v1/account \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx"
200 OK
{
  "org": "Acme Media",
  "usage": { "included": 200, "used": 147, "overage": 0 },
  "features": { "api": true, "bulk": true, "schedules": true, "webhooks": true }
}

Schedules#

POST/v1/schedulesGrowth+

Creates a recurring audit so a URL is re-graded automatically on a cadence — pair it with a webhook to get alerted whenever a tracked page's score drops.

Request body

FieldTypeDescription
urlrequiredstringThe https:// URL to re-audit on the cadence.
cadencerequired"daily" | "weekly" | "monthly"How often the audit runs.
curl — schedule a weekly audit
curl -X POST https://app.articlegrade.com/v1/schedules \
  -H "Authorization: Bearer ag_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/pillar", "cadence": "weekly" }'

Webhooks#

Growth+

Register a webhook endpoint on the Developers page and ArticleGrade will POST it a JSON payload the moment an audit completes — no polling required.

Payload

POST to your endpoint
{
  "event": "audit.completed",
  "data": {
    "id": "aud_8Fq2kR1xR7",
    "url": "https://example.com/blog/best-running-shoes",
    "score": 82,
    "status": "done"
  },
  "ts": 1782000000
}

Headers

HeaderDescription
X-AG-EventThe event name, e.g. audit.completed.
X-AG-Signaturesha256=<hex> — an HMAC-SHA256 of the raw request body, keyed with your endpoint's signing secret (shown once when you create the webhook).

Verifying the signature

Compute the HMAC over the raw, unparsed body and compare it to the header in constant time. In Node:

Node.js — verify webhook
const crypto = require('crypto');

function verify(rawBody, header, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const received = (header || '').replace('sha256=', '');
  // timingSafeEqual throws on length mismatch — guard first
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(received, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express — capture the RAW body for verification:
// app.post('/ag-webhook', express.raw({ type: 'application/json' }),
//   (req, res) => {
//     if (!verify(req.body, req.get('X-AG-Signature'), SECRET))
//       return res.sendStatus(401);
//     const evt = JSON.parse(req.body.toString());
//     res.sendStatus(200);
//   });
Use the raw body. Re-serializing the parsed JSON will change byte-for-byte and break the signature. Verify against the exact bytes you received.

Recipe: CI quality gate#

Fail a build when a page scores below your threshold. This script starts an audit, polls until it finishes, and exits non-zero if the score is under 70 — drop it into any CI step.

bash — gate the build on score (needs jq)
#!/usr/bin/env bash
set -euo pipefail

URL="$1"
THRESHOLD=70
AUTH="Authorization: Bearer $AG_API_KEY"
BASE="https://app.articlegrade.com"

# 1. start the audit, capture the id
ID=$(curl -sS -X POST "$BASE/v1/audits" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"url\":\"$URL\"}" | jq -r '.id')

# 2. poll until the audit is done
while :; do
  RES=$(curl -sS "$BASE/v1/audits/$ID" -H "$AUTH")
  STATUS=$(echo "$RES" | jq -r '.status')
  [ "$STATUS" = "done" ] && break
  [ "$STATUS" = "error" ] && { echo "audit failed"; exit 1; }
  sleep 3
done

# 3. gate on the score
SCORE=$(echo "$RES" | jq '.score')
echo "ArticleGrade score: $SCORE (threshold $THRESHOLD)"
if [ "$SCORE" -lt "$THRESHOLD" ]; then
  echo "::error::content quality below threshold — blocking publish"
  exit 1
fi
One-liner check. Already have a finished audit id? Gate inline: test "$(curl -sS "$BASE/v1/audits/$ID" -H "$AUTH" | jq '.score')" -ge 70

That's the whole surface. The machine-readable contract lives in openapi.json — import it into Postman, Insomnia, or your codegen of choice.