API documentation

Use ProofedAI from your own applications. Submit documents for AI proofreading and get structured corrections back. You can also ask for a human review by a Proofed editor, and get a webhook when each job finishes.

Download OpenAPI spec

Authentication

All API requests require a Bearer token in the Authorization header. Generate API keys from your Settings page after you log in.

Authorization: Bearer your-api-token

Base URL

https://proofed.ai/api/v1

Remote file URLs

Every endpoint that accepts a multipart file (“Submit a document”, “Submit reviewer feedback” and “Submit a fact-check”) also accepts a matching _url field, and we fetch the file from that URL for you. Pass either the file or the URL for each slot, never both.

Field pairs

Endpoint File field URL field
POST /documentsfilefile_url
POST /reviewer-feedbackeditor_fileeditor_file_url
reviewer_filereviewer_file_url
original_file (optional, recommended)original_file_url
POST /fact-checksdocumentdocument_url

Rules

Example

curl -X POST https://proofed.ai/api/v1/documents \
  -H "Authorization: Bearer your-api-token" \
  -F "file_url=https://bucket.s3.amazonaws.com/uploads/report.pdf?X-Amz-Signature=..." \
  -F "language=en-US"

Errors

Policy and download failures come back as 422 validation errors on the URL field. Common messages:

Document lifecycle

Every document moves through a series of statuses. Once you submit it, our AI reads the document, finds the issues and returns structured corrections.

pending
Document accepted and queued. Processing has not yet started.
processing
The AI is proofreading the document.
completed
Proofreading finished successfully. Corrections are available on the document.
failed
Processing failed. We give back any tokens it cost.
cancelled
Processing was canceled before it finished.

How long processing takes depends on the document’s length. Send a webhook_url to hear when it finishes, or poll the “Get a document” endpoint.

Pricing

Proofreading costs 1 token per 1,000 words, rounded up, with a minimum of 1 token: a 3,420-word document costs 4 tokens and a 999-word one costs 1. A professional edit (professional_editor=true) adds +2 tokens per 1,000 words, rounded the same way, so the 3,420-word document costs 12 tokens with it. A fact-check costs 2 tokens per 1,000 words, rounded the same way, so the 3,420-word document costs 8 tokens to fact-check. That is the price alongside a proofread and on its own through “Submit a fact-check”. Hyperpowered costs 5 tokens per 1,000 words instead of 1. You pay it when you send hyperpowered=true from an account that has it. Every Hyperpowered document costs at least 5 tokens, and the 3,420-word document costs 20, or 36 with a professional edit and a fact-check. A human review by a Proofed editor costs $60 per 1,000 words, pro rata with no rounding up, so a 3,420-word document costs $205.20. You pay for it by card on the document’s page. Service accounts are not charged.

We count the words before we charge anything. The document records its cost in tokens_charged and its word count in word_count. If a document fails, we return its tokens.

Not enough tokens 402 Payment Required

Nothing is charged. The response says how many tokens the document needs and how many the account has.

{
  "message": "Insufficient tokens.",
  "tokens_required": 4,
  "tokens_available": 2
}

A file whose words cannot be counted

Nothing is charged. A file we cannot read returns 422. If the counting service is briefly unavailable, the response is 503 and you can retry the request.

{
  "message": "We couldn't read that file to count its words. Try again or contact us."
}

Submit a document

POST /api/v1/documents

Upload a file for proofreading. Requires multipart/form-data. Costs 1 token per 1,000 words (see “Pricing”).

Parameters

Field Type Required Description
file file One of The document file (max 96 MB). Supported: pdf, docx, pptx, html, htm, txt, rtf, odt, epub. For DOCX files with tracked changes, we accept the insertions and drop the deletions, so we read the “final” version. PPTX is ingest-only: findings are returned as JSON and no annotated output file is produced. Required when file_url is not provided.
file_url string One of A public http(s) URL we fetch instead of an upload. It may be an S3, GCS or Azure pre-signed link. The URL path must end in an allowed extension. See “Remote file URLs” above for the size, SSRF and redirect rules. Mutually exclusive with file and blocks.
blocks array One of An array of pre-split content blocks, sent instead of a file. Each block is an object {name, type, content, comments?}, where type is “html” or “text”. Findings refer to each block by its name. Max 1,000 blocks. In multipart submissions, pass it as a JSON-encoded string. Mutually exclusive with file and file_url.
style_guide_url string No Full URL of a Stylus style guide for the AI to enforce.
style_guide_id string No Either a full Stylus URL (used verbatim) or a Stylus UUID (resolved server-side). Accepted alongside style_guide_url for compatibility with the Azure proofreading API.
variables object No Free-form key/value object merged into meta (meta keys win on conflict). Same validation rules as meta.
model / prompts / chain_prompts any No Accepted for compatibility with the Azure proofreading API, and ignored. We choose the model ourselves, based on hyperpowered.
language string No A dialect code. Supported: en-GB, en-US, en-CA, en-IE, en-AU, en-NZ, en-ZA, en-IN, es-ES, es-MX, fr-FR, fr-CA, de-DE, de-AT, de-CH, it-IT, pt-BR, pt-PT. A code without a region, such as en, returns 422. The dialect overrides every other rule, including the style guide. Default: en-GB.
editing_phase string No Which AI prompt set runs. One of pre-edit (first AI pass on raw copy) or post-edit (AI sanity check on already-edited copy). Default: pre-edit.
order_id string No Your own order reference (max 255 chars). Echoed back on every read, used to group jobs in the per-order cost endpoint and search, and preferred over the order-prefixed file name convention when both are present.
job_id string No Your own job reference (max 255 chars). Echoed back on every read and in the cost API, and searchable via the search endpoint.
webhook_url string No URL to receive a POST when processing completes (success or failure). When review=true, this URL also receives a separate POST when the human review finishes. See “Webhooks”.
review boolean No Default false. When true, once the AI proofread completes, we also send the document to a Proofed editor for a human review. You download the reviewed file from a signed URL. A review costs $60 per 1,000 words and is paid by card on the document’s page, so accounts that pay for reviews get a 402 here. See “Human review by a Proofed editor”.
review_brief string No Plain-text instructions for the human editor (max 500 chars). Only used when review=true. Forwarded verbatim in the order brief.
hyperpowered boolean No Default false. Runs the proofread on the premium “hyperpowered” model, which costs 5 tokens per 1,000 words instead of 1 (see “Pricing”). Requires the can_use_hyperpowered permission; setting it without that permission returns 403. Echoed back as hyperpowered on the document. Also available on “Submit reviewer feedback” and “Submit a fact-check”. It costs nothing extra there.
professional_editor boolean No Default false. Professional edit: besides the proofread, also makes the changes a senior editor would make (tighter wording, clearer sentences, house style). Adds +2 tokens per 1,000 words to the proofread (see “Pricing”). Echoed back as professional_editor on the document.
meta object No An optional flat object of scalar values that we send back to you on the show and list endpoints, in webhook payloads and in the per-order cost API. Use it for tracking IDs like {"jobid": 123}. Up to 20 keys; keys must match [A-Za-z0-9_-] and be ≤ 64 chars; values must be a string, number or boolean, with strings ≤ 512 chars; total payload ≤ 4 KB. In multipart submissions, pass it as a JSON string. “metadata” and “variables” are accepted as aliases and folded into meta (explicit meta keys win).

Example

curl -X POST https://proofed.ai/api/v1/documents \
  -H "Authorization: Bearer your-api-token" \
  -F "file=@report.pdf" \
  -F "language=en-US" \
  -F "editing_phase=pre-edit" \
  -F "order_id=ORD-2026-0417" \
  -F "job_id=JOB-9812" \
  -F "webhook_url=https://your-app.com/webhook" \
  -F "review=true" \
  -F "review_brief=Light proofread; preserve the author's voice." \
  -F 'meta={"jobid":123,"campaign":"Q3-2026"}'

Response 201 Created

{
  "data": {
    "uuid": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "filename": "report.pdf",
    "file_type": "pdf",
    "file_size": 102400,
    "language": "en-US",
    "editing_phase": "pre-edit",
    "meta": {"jobid": 123, "campaign": "Q3-2026"},
    "order_id": "ORD-2026-0417",
    "job_id": "JOB-9812",
    "status": "pending",
    "source": "api",
    "correction_count": null,
    "word_count": 3420,
    "tokens_charged": 4,
    "webhook_url": "https://your-app.com/webhook",
    "submitted_at": null,
    "completed_at": null,
    "created_at": "2026-04-16T12:00:00+00:00"
  }
}

List documents

GET /api/v1/documents

Returns a paginated list of your documents, newest first, 20 per page.

Example

curl https://proofed.ai/api/v1/documents \
  -H "Authorization: Bearer your-api-token"

Get a document

GET /api/v1/documents/{uuid}

Returns document details. The full corrections array is included on this endpoint but omitted from the list endpoint.

Response 200 OK

{
  "data": {
    "uuid": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "filename": "report.pdf",
    "file_type": "pdf",
    "file_size": 102400,
    "language": "en-US",
    "status": "completed",
    "source": "api",
    "correction_count": 5,
    "word_count": 3420,
    "tokens_charged": 4,
    "ai_tells": { "count": 1, "per_1000_words": 0.3 },
    "corrections_data": [
      {
        "type": "spelling",
        "original": "achivements",
        "corrected": "achievements",
        "context": "Misspelling",
        "page": 3
      }
    ],
    "webhook_url": "https://your-app.com/webhook",
    "submitted_at": "2026-04-16T12:00:05+00:00",
    "completed_at": "2026-04-16T12:00:42+00:00",
    "created_at": "2026-04-16T12:00:00+00:00",
    "download_url": "https://proofed.ai/api/v1/documents/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/download?expires=...&signature=...",
    "proofed_review": {
      "status": "delivered",
      "deadline": "2026-04-18T12:00:00+00:00",
      "word_count": 1234,
      "delivered_at": "2026-04-17T16:00:00+00:00",
      "download_url": "https://proofed.ai/api/v1/documents/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/proofed-review/download?expires=...&signature=..."
    }
  }
}

The proofed_review block is present only when the document was submitted with review=true. See “Human review by a Proofed editor” for details.

Cancel a document

POST /api/v1/documents/{uuid}/cancel

Cancel a pending or processing document. Completed, failed or already-canceled documents cannot be canceled (the request returns 422).

Cancellation is cooperative: if the AI is mid-call, it finishes that step and stops at the next turn boundary.

If a human review is attached and the review is pending or submitted, canceling also cancels the review (and sends a proofed_review.canceled webhook). When the review is in_progress or delivered, the cancel returns 422, because we cannot cancel the Proofed order from our side. Contact support.

Response 200 OK

{
  "message": "Document canceled."
}

Diff (tracked changes)

POST /diff

Turn two versions of a document into one file that shows what changed. A DOCX comes back with real Word tracked changes; HTML and blocks get del/ins markup; a PDF gets annotations; PPTX gets native comments.

This endpoint is synchronous. No AI model runs, so there is nothing to poll for, and the response body is the file. Expect a second or two for a short document and up to a minute for a long PDF.

The edited copy is required; the original usually is not

Always send the edited copy. It can be a clean copy with no tracked changes of its own: adding the tracked changes is this endpoint’s job. For the original, you have three options, and only one of them needs an upload:

We search only your own documents. If none of the three gives us an original, you get a 422 rather than an empty diff. A file with no changes in it and a file we could not build are different answers, and you should not have to guess which one you got.

Parameters

Field Type Notes
editor_file file, required The edited copy. Max 96 MB. Formats: pdf, docx, pptx, txt, rtf, odt, html, epub.
original_file file, optional The original the edits were made to. Must be the same format as editor_file: we write the tracked-changes copy in the source format, so we cannot compare a DOCX with an HTML file.
order_id string, optional Use instead of original_file to have us find the pre-edit for that order.
document_id uuid, optional Names the pre-edit exactly, when an order has more than one.
author string, optional Shown as the author of every tracked change. The default is Proofed.

Response

200, with the file as the body. Content-Disposition names it “tracked-changes-<your file name>”. The X-Diff-Original-Source header says which original we used: “upload”, or “pre-edit:<uuid>” when we found it for you.

422 when the edited copy is missing, when no original can be found or when the two files are in different formats. 502 when we could not build the diff itself.

Examples

# Both files, saving the result
curl -X POST https://proofed.ai/api/v1/diff \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "original_file=@original.docx" \
  -F "editor_file=@edited.docx" \
  -o tracked-changes.docx

# Let us find the original from the order number
curl -X POST https://proofed.ai/api/v1/diff \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "editor_file=@edited.docx" \
  -F "order_id=51656" \
  -F "author=Editorial Team" \
  -o tracked-changes.docx

Document fields

Field Type Description
uuid string Stable document identifier. Use this to reference documents in other API calls.
filename string The original file name, as uploaded.
file_type string Lowercase extension, e.g., pdf or docx.
file_size integer Size in bytes.
language string Language code.
status string One of pending, processing, completed, failed, cancelled.
source string Where the document came from: api, web, email.
correction_count integer Number of corrections found. null while pending or processing.
word_count integer|null Words in the document, counted before it was charged and confirmed when the proofread completes.
tokens_charged integer|null 1 token per 1,000 words, rounded up, minimum 1, plus +2 tokens per 1,000 words for a professional edit. With Hyperpowered, the proofread costs 5 tokens per 1,000 words instead, minimum 5. 0 for service accounts; null for documents submitted before word pricing (they cost 1 token).
ai_tells object|null {count, per_1000_words}: machine-sounding phrases left in the finished document. null until the proofread completes, and on results from engines that do not count them. 3 or more per 1,000 words reads as machine-written.
corrections_data array Array of correction objects (only on the “Get a document” endpoint). See “Correction shape”.
webhook_url string|null Webhook delivery target, if one was provided.
download_url string A signed URL, valid for 24 hours, for the AI-proofread output file. Present only once the processed file exists (status completed). The signature is the authentication, so do not send a Bearer token to this URL. This is the AI-edited file; proofed_review.download_url is the separate file from the human review.
submitted_at ISO8601|null When the document was submitted to the AI provider.
completed_at ISO8601|null When processing completed (success or failure).
created_at ISO8601 When the document was uploaded.
proofed_review object Present only when the document was submitted with review=true. See “Human review by a Proofed editor” for the block’s shape.

Correction shape

Each entry in corrections_data follows this shape. The AI produces both replacement corrections (with a suggested fix) and comments (judgment calls flagged for a human editor to review).

Field Type Description
type string Category: spelling, grammar, punctuation, style, consistency, clarity, tone, word_choice, formatting or other.
original string The text as it appears in the document.
corrected string The suggested replacement. For comments, this may be the comment text.
context string A short note on why this is an issue.
page integer|null Page number (when known).

Webhooks

If you give a webhook_url when you submit a document, we send a POST request to that URL when processing ends (completed or failed).

Webhook payload

{
  "event": "document.completed",
  "document": {
    "uuid": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "filename": "report.pdf",
    "status": "completed",
    "correction_count": 5,
    "order_id": "ORD-2026-0417",
    "job_id": "JOB-9812",
    "download_url": "https://proofed.ai/api/v1/documents/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/download?expires=...&signature=..."
  },
  "timestamp": "2026-04-16T12:00:42+00:00"
}

The event field is document.{status}, e.g., document.completed or document.failed. We retry delivery up to 3 times, 30 seconds apart. We treat any non-2xx response as a failure and retry it.

document.completed payloads include a download_url: a signed URL, valid for 24 hours, for the AI-proofread output file. The signature is the authentication, so do not send a Bearer token to it. The field is missing if no processed file was produced.

To fetch the full corrections, call the “Get a document” endpoint with the uuid from the payload.

Download the proofread file

GET /api/v1/documents/{uuid}/download

Streams the AI-proofread output file. The file name is proofed-{original-name}.

Authentication is by signed URL only, so do not send an Authorization header. Use the download_url from the “Get a document” response or the document.completed webhook payload. The URL is valid for 24 hours from issue; each “Get a document” response gives a fresh URL with a fresh expiry.

Returns 403 if the signature is invalid or expired. Returns 404 if no processed file exists for the document or the file is missing on disk.

curl "$DOWNLOAD_URL" -o proofed-report.pdf

Polling and live status

If you cannot receive webhooks, poll the “Get a document” endpoint. We recommend a 15-second interval while status is pending or processing. How long a document takes depends on its length.

Pseudocode

while document.status in ("pending", "processing"):
    sleep(15)
    document = get_document(uuid)

if document.status == "completed":
    process_corrections(document.corrections_data)
else:
    handle_failure(document.status)

Human review by a Proofed editor

An optional extra step: after the AI proofread completes, you can send the document to a Proofed editor for a human review. The editor returns a final reviewed file, which you fetch from a signed URL.

A review costs $60 per 1,000 words, paid by card on the document’s page at proofed.ai. The API cannot take a card, so we refuse review=true from an account that pays for reviews with a 402, before anything is stored or charged. Send the document without it, then choose “Get a human review” on its page. Service accounts are not charged and can ask with review=true.

Ask for a human review

Add review=true to your “Submit a document” request. You can also send a review_brief (max 500 chars) with instructions for the editor.

curl -X POST https://proofed.ai/api/v1/documents \
  -H "Authorization: Bearer your-api-token" \
  -F "file=@report.pdf" \
  -F "webhook_url=https://your-app.com/webhook" \
  -F "review=true" \
  -F "review_brief=Light proofread; preserve the author's voice."

Review lifecycle

The document’s own status moves to completed as soon as the AI proofread is done, whatever the review is doing. Track the human review separately with the proofed_review.status field on the document.

pending
Order created. The AI proofread is still running; nothing has been sent to Proofed yet.
submitted
Sent to Proofed and waiting for an editor.
in_progress
Proofed has assigned an editor. deadline and word_count are populated.
delivered
The editor returned the reviewed file. delivered_at and download_url are populated.
failed
Sending failed, or Proofed reported an error. error_text on the webhook says why.
canceled
The order was canceled, by you or by Proofed.

The human review comes back within 24 hours.

The proofed_review block

The “Get a document” response includes a proofed_review object whenever review=true was set on submit. Most fields are populated as the review progresses; only status is always present.

"proofed_review": {
  "status": "delivered",
  "deadline": "2026-04-18T12:00:00+00:00",
  "word_count": 1234,
  "delivered_at": "2026-04-17T16:00:00+00:00",
  "download_url": "https://proofed.ai/api/v1/documents/{uuid}/proofed-review/download?expires=...&signature=..."
}
Field Type Description
status string One of pending, submitted, in_progress, delivered, failed, canceled.
deadline ISO8601 The editor’s deadline, as reported by Proofed. Populated once status is in_progress.
word_count integer The word count, as reported by Proofed. Populated once status is in_progress.
delivered_at ISO8601 When the editor returned the reviewed file. Populated once status is delivered.
download_url string A signed URL, valid for 24 hours, for the reviewed file. Present only when status is delivered. The signature is the authentication, so do not send a Bearer token to this URL.

Download the reviewed file

GET /api/v1/documents/{uuid}/proofed-review/download

Streams the file the human editor returned. The file name is {original-name}-reviewed.{ext}.

Authentication is by signed URL only, so do not send an Authorization header. Use the URL in proofed_review.download_url or in the proofed_review.delivered webhook payload. The URL is valid for 24 hours from issue. Each “Get a document” response gives a fresh URL with a fresh expiry.

Returns 403 if the signature is invalid or expired. Returns 404 if the document has no review attached, the review has not been delivered yet or the file is missing on disk.

Example

curl "$DOWNLOAD_URL" -o report-reviewed.pdf

Human review webhooks

When you send webhook_url with review=true, you get two separate webhooks: one when the AI proofread finishes (document.completed) and one when the human review ends. We retry each up to 3 times, 30 seconds apart.

proofed_review.delivered

{
  "event": "proofed_review.delivered",
  "document": {
    "uuid": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "filename": "report.pdf"
  },
  "proofed_review": {
    "status": "delivered",
    "delivered_at": "2026-04-17T16:00:00+00:00",
    "download_url": "https://proofed.ai/api/v1/documents/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/proofed-review/download?expires=...&signature=..."
  },
  "timestamp": "2026-04-17T16:00:01+00:00"
}

proofed_review.failed

{
  "event": "proofed_review.failed",
  "document": { "uuid": "...", "filename": "report.pdf" },
  "proofed_review": {
    "status": "failed",
    "error_text": "proofed.com submit failed: HTTP 502"
  },
  "timestamp": "2026-04-17T12:01:00+00:00"
}

proofed_review.canceled

{
  "event": "proofed_review.canceled",
  "document": { "uuid": "...", "filename": "report.pdf" },
  "proofed_review": { "status": "canceled" },
  "timestamp": "2026-04-17T12:01:00+00:00"
}

To fetch the document’s full state at any time, call “Get a document” with the uuid.

Editor quality reviews

ProofedAI also offers editor quality reviews: upload an original document and an editor’s version, and the AI assesses the editor’s work against a baseline proofread. You get a structured feedback report with a score and categorized findings.

Web app only. Not available through the public API yet.

Admins and users with editor review access can use this feature in the ProofedAI web app. Public API access is on the roadmap. Contact support if you have a use case.

What it produces

How to access

Admins submit reviews at /admin/reviews/submit in the ProofedAI web app. Users with editor review access submit at /reviews/submit. DOCX files with tracked changes are fully supported: we read the “final accepted” version of the editor’s work.

Reviewer feedback

Reviewer feedback compares a reviewer’s final copy with the editor’s final copy. It works out why the reviewer made each change by matching it to the style guide, then returns feedback grouped by rule that the reviewer can scan, adjust and send to the editor. It saves reviewers time when they give constructive feedback.

Requires reviewer feedback access on your account.

Admins grant it per user. Without it, all reviewer-feedback endpoints return 403.

Lifecycle

Feedback jobs move through pending → processing → completed / failed / cancelled, the same states as documents. How long a job takes depends on the document’s length and the number of differences.

Submit reviewer feedback

POST /api/v1/reviewer-feedback

Upload the editor’s final copy and the reviewer’s final copy as multipart/form-data. Both files should be final versions with no tracked changes.

Parameters

Field Type Required Description
editor_file file One of The editor’s final copy. Max 96 MB. Supported: pdf, docx, pptx, html, htm, txt, rtf, odt, epub. Required when editor_file_url is not provided.
editor_file_url string One of A public http(s) URL we fetch instead of editor_file. Mutually exclusive with editor_file. See “Remote file URLs” above.
reviewer_file file One of The reviewer’s final copy. Same limits as editor_file. Required when reviewer_file_url is not provided.
reviewer_file_url string One of A public http(s) URL we fetch instead of reviewer_file. Mutually exclusive with reviewer_file. Each slot is independent, so you can mix uploads and URLs.
original_file file No Optional, recommended. The original copy, before the editor worked on it. Same size and format limits as editor_file. With it, each finding’s origin says whether the editor missed the problem or introduced it, or whether the reviewer put back the original wording. The delta copy then covers every change from the original. If you leave it out and send order_id, we use the original from that order’s proofread when we have one. Mutually exclusive with original_file_url.
original_file_url string No Optional. A public http(s) URL we fetch instead of original_file. You can leave out both forms of this slot.
language string No Language code. Supported: en, en-GB, en-US, en-CA, en-IE, en-AU, en-NZ, en-ZA, en-IN, es, es-ES, es-MX, fr, fr-FR, fr-CA, de, de-DE, de-AT, de-CH, it, it-IT, pt, pt-BR, pt-PT. Default: en.
style_guide_url string No Optional Stylus style-guide URL. We use it to match reviewer changes to named rules. Without one, findings are grouped by theme.
webhook_url string No URL we POST to when the job finishes. See “Reviewer feedback webhooks”.
order_id string No Your reference for the order this feedback relates to. Max 255 chars. Echoed back in responses and webhooks.
job_id string No Your reference for the job this feedback relates to. Max 255 chars. Echoed back in responses and webhooks.

Example

curl -X POST https://proofed.ai/api/v1/reviewer-feedback \
  -H "Authorization: Bearer your-api-token" \
  -F "editor_file=@editor-final.docx" \
  -F "reviewer_file=@reviewer-final.docx" \
  -F "original_file=@original-pre-edit.docx" \
  -F "language=en" \
  -F "style_guide_url=https://stylus.proofedapps.com/api/style-guides/abc-123" \
  -F "webhook_url=https://your-app.com/webhook" \
  -F "order_id=ORD-2026-0417" \
  -F "job_id=JOB-9812"

Response 201 Created

{
  "data": {
    "uuid": "7c2a1d5e-3f4b-4c6d-8e9f-0a1b2c3d4e5f",
    "editor_filename": "editor-final.docx",
    "reviewer_filename": "reviewer-final.docx",
    "original_filename": "original-pre-edit.docx",
    "language": "en",
    "status": "pending",
    "style_guide_url": "https://stylus.proofedapps.com/api/style-guides/abc-123",
    "webhook_url": "https://your-app.com/webhook",
    "order_id": "ORD-2026-0417",
    "job_id": "JOB-9812",
    "narrative": null,
    "finding_count": null,
    "diff_count": null,
    "report_available": false,
    "submitted_at": null,
    "completed_at": null,
    "created_at": "2026-04-17T15:00:00+00:00"
  }
}

List reviewer feedback

GET /api/v1/reviewer-feedback

A paginated list of your reviewer-feedback jobs, newest first, 20 per page.

curl https://proofed.ai/api/v1/reviewer-feedback \
  -H "Authorization: Bearer your-api-token"

Get reviewer feedback

GET /api/v1/reviewer-feedback/{uuid}

Returns the full feedback draft, including the rule-grouped findings, the summary and the reviewer-only tone notes. While a job is in progress, findings and summary are null and narrative describes the current step.

Response 200 OK (completed)

{
  "data": {
    "uuid": "7c2a1d5e-3f4b-4c6d-8e9f-0a1b2c3d4e5f",
    "editor_filename": "editor-final.docx",
    "reviewer_filename": "reviewer-final.docx",
    "original_filename": "original-pre-edit.docx",
    "language": "en",
    "status": "completed",
    "style_guide_url": "https://stylus.proofedapps.com/api/style-guides/abc-123",
    "webhook_url": "https://your-app.com/webhook",
    "order_id": "ORD-2026-0417",
    "job_id": "JOB-9812",
    "narrative": null,
    "finding_count": 3,
    "diff_count": 14,
    "summary": "Three recurring themes across your edit...",
    "tone_notes": "Keep it constructive. The editor did most of the heavy lifting.",
    "findings": [
      {
        "rule_slug": "currency-abbreviations",
        "rule_name": "Currency in Billions",
        "rule_description": "Abbreviate as bn.",
        "summary": "Use bn for currency in the billions.",
        "origin": "missed",
        "examples": [
          { "paragraph": "14", "from": "$4 billion", "to": "$4bn" }
        ]
      }
    ],
    "report_available": true,
    "submitted_at": "2026-04-17T15:00:05+00:00",
    "completed_at": "2026-04-17T15:03:42+00:00",
    "created_at": "2026-04-17T15:00:00+00:00"
  }
}

Finding shape

Field Type Description
rule_slug string Machine-readable identifier for the rule or theme.
rule_name string Human-readable rule name (from the style guide if one was provided).
rule_description string The rule description from the style guide.
summary string One-sentence feedback the reviewer can send to the editor.
origin string Where the problem came from: missed (it was in the original and the editor left it), introduced (the editor made the error), reverted (the reviewer put back the original wording), mixed, or unknown. It is unknown when we have no original.
examples array Examples showing the paragraph number, the original text and the reviewer’s change.

Cancel reviewer feedback

POST /api/v1/reviewer-feedback/{uuid}/cancel

Cancel a pending or processing job. A job that has already finished returns 403.

{
  "message": "Reviewer feedback canceled."
}

Download the report

GET /api/v1/reviewer-feedback/{uuid}/report

Returns the generated DOCX feedback report (application/vnd.openxmlformats-officedocument.wordprocessingml.document). Available once status is completed; before that, it returns 404. The file name is the reviewer file’s name with -reviewer-feedback.docx on the end.

curl https://proofed.ai/api/v1/reviewer-feedback/{uuid}/report \
  -H "Authorization: Bearer your-api-token" \
  -o feedback.docx

Download the delta copy (tracked changes)

GET /api/v1/reviewer-feedback/{uuid}/delta

Every completed reviewer-feedback job also produces a “delta copy”: the document, annotated to show what actually changed between the versions. When we have the original (you sent original_file, or we found it from order_id), the delta covers every change from the original to the reviewer’s final copy. Otherwise, it shows the reviewer’s changes to the editor’s copy.

You can fetch it two ways. Call this path with your API token, exactly as for the report (the job must be yours). Or use the delta_url from the “Get reviewer feedback” response: it is signed and valid for 24 hours, so you need no API token. The response also includes delta_available. The delta is optional, so treat false or null (or a 404 from this path) as normal, not as an error.

What you get, per format

Source formatDelta representation
docxReal Word tracked changes (insertions and deletions) that you can review in Word or Google Docs
Google DriveSame as DOCX: Drive documents are handled as .docx
html, blocksInline del/ins markup
pdfThe original, annotated with strikethroughs and notes that carry the replacement text
pptxNative PowerPoint comments (PresentationML has no tracked-changes equivalent)
odt, epub, txtAnnotated in the source format
xlsxNot supported: we do not process spreadsheets at the moment
# with your API token
curl https://proofed.ai/api/v1/reviewer-feedback/{uuid}/delta \
  -H "Authorization: Bearer your-api-token" \
  -o tracked-changes.docx

# or with the signed delta_url from the job (no token required)
curl -L "$DELTA_URL" -o tracked-changes.docx

Reviewer feedback webhooks

If you send a webhook_url on submit, we POST to it when the job finishes. We retry up to 3 times, 30 seconds apart.

{
  "event": "reviewer-feedback.completed",
  "reviewer_feedback": {
    "uuid": "7c2a1d5e-3f4b-4c6d-8e9f-0a1b2c3d4e5f",
    "editor_filename": "editor-final.docx",
    "reviewer_filename": "reviewer-final.docx",
    "original_filename": "original-pre-edit.docx",
    "status": "completed",
    "order_id": "ORD-2026-0417",
    "job_id": "JOB-9812",
    "finding_count": 3,
    "diff_count": 14
  },
  "timestamp": "2026-04-17T15:03:42+00:00"
}

event is reviewer-feedback.{status}, e.g., reviewer-feedback.completed or reviewer-feedback.failed. To fetch the findings, call “Get reviewer feedback” with the uuid.

Fact-check

Check the factual claims in a document against your Stylus style guide’s ground truth and the live web. Each claim comes back with a verdict (verified, contradicted or unverifiable), the reasoning and source citations.

Submit a fact-check

POST /api/v1/fact-checks

Submit a document to fact-check. The job runs asynchronously, so poll “Get a fact-check” or register a webhook_url.

Costs 2 tokens per 1,000 words, rounded up, minimum 2 tokens. We count the words before we charge anything; the response records the charge in tokens_charged, and a failed fact-check is refunded. A short balance returns 402 with tokens_required and tokens_available and charges nothing (see “Pricing”).

Parameters

Field Type Required Description
document file One of The document to fact-check. Max 96 MB. Supported: pdf, docx, pptx, html, htm, txt, rtf, odt, epub. Required when document_url is not provided.
document_url string One of A public http(s) URL we fetch instead of document. Mutually exclusive with document. See “Remote file URLs”.
language string No Language code. Supported: en, en-GB, en-US, en-CA, en-IE, en-AU, en-NZ, en-ZA, en-IN, es, es-ES, es-MX, fr, fr-FR, fr-CA, de, de-DE, de-AT, de-CH, it, it-IT, pt, pt-BR, pt-PT. Default: en.
style_guide_url string No Optional Stylus style-guide URL. Ground-truth facts and uploaded reference documents take precedence over the web for any claim they cover.
webhook_url string No URL we POST to when the job finishes.
order_id string No Your reference for the order. Max 255 chars. Echoed back in responses and webhooks.
job_id string No Your reference for the job. Max 255 chars. Echoed back in responses and webhooks.

Example

curl -X POST https://proofed.ai/api/v1/fact-checks \
  -H "Authorization: Bearer your-api-token" \
  -F "document=@article.pdf" \
  -F "language=en" \
  -F "style_guide_url=https://stylus.proofedapps.com/api/style-guides/abc-123" \
  -F "webhook_url=https://your-app.com/webhook"

List fact-checks

GET /api/v1/fact-checks

A paginated list of your fact-check jobs, newest first, 20 per page.

Get a fact-check

GET /api/v1/fact-checks/{uuid}

Returns the full list of findings once status is completed, or a progress narrative while it is processing. Each finding has a verdict (verified, contradicted or unverifiable), the model’s reasoning and a sources array (Stylus ground-truth citations or web links).

Download the report

GET /api/v1/fact-checks/{uuid}/report

Streams the generated DOCX report. Returns 404 if the job has not completed yet. The file name is the source document’s name with -fact-check.docx on the end.

Error codes

Code Description
401 Unauthorized: invalid or missing API token.
402 Payment Required: not enough tokens. The body gives tokens_required and tokens_available; nothing is charged. See “Pricing”.
403 Forbidden: you do not have access to this resource.
404 Not Found: the resource does not exist.
422 Unprocessable Entity: validation errors, a file whose words could not be counted or an invalid state change (e.g., canceling a completed document).
429 Too Many Requests: rate limit exceeded.
503 Service Unavailable: we could not count the words just now. Nothing was charged; try again shortly.

Code examples

PHP

$client = new \GuzzleHttp\Client();

$response = $client->post('https://proofed.ai/api/v1/documents', [
    'headers' => [
        'Authorization' => 'Bearer ' . $apiToken,
    ],
    'multipart' => [
        [
            'name'     => 'file',
            'contents' => fopen('/path/to/report.pdf', 'r'),
            'filename' => 'report.pdf',
        ],
        [
            'name'     => 'language',
            'contents' => 'en-US',
        ],
    ],
]);

$document = json_decode($response->getBody(), true)['data'];

JavaScript (Node.js)

const form = new FormData();
form.append('file', fs.createReadStream('./report.pdf'));
form.append('language', 'en-US');

const response = await fetch('https://proofed.ai/api/v1/documents', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiToken}`,
  },
  body: form,
});

const { data } = await response.json();

Python

import requests

response = requests.post(
    'https://proofed.ai/api/v1/documents',
    headers={'Authorization': f'Bearer {api_token}'},
    files={'file': open('report.pdf', 'rb')},
    data={'language': 'en-US'},
)

document = response.json()['data']