The API
Two endpoints. One tells you what is wrong. The other hands the draft back fixed, with a flag your pipeline can gate on. Both answer on the same request; long drafts can be submitted as a job and polled instead.
On this page
Getting started
Quick start
Base URL https://useclaimcheck.com. Authenticate with a key from your account page. Every request is JSON, every response is JSON.
curl https://useclaimcheck.com/api/v1/correct \
-H "Authorization: Bearer ck_live_..." \
-H "Content-Type: application/json" \
-d '{"text": "The Berlin Wall fell in 1999."}'That is the whole integration. There is no SDK to install and no session to manage.
POST /v1/check
Every factual claim in the draft, each with a verdict, a 0-100 score, and the source behind it. Use this when a human will read the result.
{
"check_id": "chk_...",
"summary": { "claims_found": 3, "verified": 1, "contradicted": 2 },
"claims": [
{
"text": "The Berlin Wall fell in 1999.",
"verdict": "contradicted",
"likelihood": 5,
"reasoning": "Sources agree it fell on 9 November 1989.",
"source_url": "https://en.wikipedia.org/wiki/...",
"citations": [{ "url": "...", "stance": "refutes", "says": "..." }],
"misspellings": [],
"disputed": false,
"no_trace": false
}
],
"skipped": [{ "text": "Dota 2 is toxic.", "reason": "A value judgement." }]
}likelihood is always “how likely this is true”, 0 to 100. Below 40 is contradicted, 40 to 64 unresolved, 65 and above holds up. no_trace means the subject is well documented and this specific claim appears nowhere, which is the strongest signal of a fabricated fact. disputed means credible sources actively disagree, and both sides are in citations.
POST /v1/correct
The same check, returned as edits. Use this when a program will read the result.
{
"publish_ready": false,
"corrected_text": "The Berlin Wall fell in 1989. Microsoft acquired Figma in 2024.",
"corrections": [
{
"kind": "date",
"before": "The Berlin Wall fell in 1999.",
"after": "The Berlin Wall fell in 1989.",
"why": "Sources give 1989, not 1999.",
"source_url": "https://en.wikipedia.org/wiki/..."
}
],
"needs_review": [
{
"text": "Microsoft acquired Figma in 2024.",
"verdict": "contradicted",
"likelihood": 10,
"why": "Adobe attempted it; the deal was abandoned.",
"source_url": "https://www.nytimes.com/...",
"disputed": false
}
]
}Every correction is derived, never generated. A replacement is only made when a source stated the value: a figure it gave, a spelling it used, a date it named. Nothing is rewritten by a model, so no correction can be a fluent guess. kind is spelling, figure, or date.
Anything that cannot be fixed from the evidence comes back under needs_review with its sources, and the text is left exactly as written. When two reputable sources give different years, you get the flag, not a coin flip.
Long drafts: jobs
A long draft can run past two minutes, which is longer than the default timeout in most HTTP clients. That failure is worse than it looks: the work does not stop when your client gives up, so a timeout throws away a result you already paid for. Submit the draft as a job instead and the id comes back in milliseconds.
Submit
curl https://useclaimcheck.com/api/v1/jobs \
-H "Authorization: Bearer ck_live_..." \
-H "Content-Type: application/json" \
-d '{"text": "...", "mode": "correct"}'
# 202 Accepted
{
"job_id": "job_U14uiDXy4YHi",
"status": "queued",
"mode": "correct",
"poll_url": "/api/v1/jobs/job_U14uiDXy4YHi",
"estimated_seconds": 30
}mode is check (the default) or correct, and decides which of the two payloads above you get back.
Poll
Polling takes the same key: a job holds your text, so the id alone is not enough to read it.
curl https://useclaimcheck.com/api/v1/jobs/job_U14uiDXy4YHi \
-H "Authorization: Bearer ck_live_..."
{
"job_id": "job_U14uiDXy4YHi",
"status": "running",
"mode": "correct",
"created_at": "2026-08-08T14:02:11.402Z",
"finished_at": null,
"retry_after_seconds": 5
}status is queued, running, succeeded, or failed. While it is unfinished you get retry_after_seconds; once it succeeds that field disappears and result appears, holding exactly the payload the synchronous endpoint would have returned. A failure carries the same error.code values listed below, so you do not have to learn a second error vocabulary.
{
"job_id": "job_U14uiDXy4YHi",
"status": "succeeded",
"mode": "correct",
"check_id": "chk_...",
"created_at": "2026-08-08T14:02:11.402Z",
"finished_at": "2026-08-08T14:02:34.981Z",
"result": {
"publish_ready": false,
"corrected_text": "...",
"corrections": [],
"needs_review": []
}
}const { job_id, estimated_seconds } = await submit(draft);
await sleep(estimated_seconds * 1000);
for (;;) {
const job = await poll(job_id);
if (job.status === "succeeded") return job.result;
if (job.status === "failed") throw new Error(job.error.code);
await sleep(job.retry_after_seconds * 1000);
}Wait for estimated_seconds before your first poll. It is deliberately pessimistic, because two model calls run per draft no matter how short it is: even a one-sentence check takes about 30 seconds, and polling before then only produces requests nobody can answer yet.
Concurrency
Ten jobs may be in flight per key at once, which is a guard rather than a throttle. Submitting is cheap and the work is not, so without it a loop could queue thousands of runs faster than your allowance could notice. If a worker dies mid-run the job resolves as failed with job_abandoned rather than pending forever, and nothing is charged.
The synchronous endpoints are unchanged and are still the right choice for a short draft. Polling exists for the case that needs it, not as the way in.
Publishing workflow
The intended shape: check before you publish, gate on publish_ready, and put anything unresolved in front of a person.
const res = await fetch("https://useclaimcheck.com/api/v1/correct", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLAIM_CHECK_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text: draft }),
});
const { publish_ready, corrected_text, needs_review } = await res.json();
if (publish_ready) {
await cms.publish(corrected_text); // every fix already applied
} else {
await cms.saveDraft(corrected_text);
await notify(needs_review); // a human decides these
}Every request counts as a check, including one that re-runs text we have already seen. A repeat is served from cache so it comes back in under a second, but it is still a check against your allowance. Call this on publish, not on every keystroke.
Errors
Every error is { "error": { "code", "message" } }. Codes are stable; messages are for humans and may change.
Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The body is missing text or is not JSON. |
| 401 | invalid_api_key | The key is wrong or revoked. |
| 403 | api_not_on_plan | The key belongs to a plan without API access. |
| 404 | job_not_found | No job with that id, or it is not yours. |
| 413 | text_too_long | The draft is longer than your plan allows. |
| 422 | draft_declined | The model provider refused this text. Not a verdict. |
| 429 | rate_limited | Allowance spent. Retry-After says when. |
| 429 | too_many_jobs | Ten jobs already in flight. Let one finish. |
| 503 | search_unavailable | Search is down. Retry. |
Timeouts
A check takes 30 to 60 seconds depending on how many claims are in the draft. Set your client timeout to at least 90 seconds, or use jobs and stop depending on the connection staying open at all.
A failed check costs nothing. The allowance is only spent when a check completes, so a timeout, a declined draft, or a search outage never counts against you.
Limits and pricing
API access is on the Pro plan: $59 a month for 1,000 checks, then 6c a check beyond that. A check is one request, however many claims are in it and however long the draft is.
Both endpoints cost the same and count the same, because they run the same work. Re-checking identical text counts too: a cached repeat is faster, not free.
For comparison, the nearest self-serve alternative works out around 16c per verification. We are cheaper, and a check here covers a whole draft rather than a single claim.