PreviewExamples and files de-identified before upload only: payments run in Stripe test mode, so no real money moves; patient documents are not accepted; new accounts are by invitation for now.Test payments only, no patient documents, accounts by invitation.
PEPPERParseDocs · Tathya Health
API guide · version 1

Post a PDF, get source-linked data

The API is the same service as the workspace: the same credit, the same limits, the same results. Your account comes with an API key.

Quick start

Set PARSEDOCS_KEY to your key. Base address for this server: https://parsedocs.tathya.ai.

# 1. Upload a PDF and start parsing it.
#    data_class=deidentified states the file has no patient information.
curl -s -X POST "https://parsedocs.tathya.ai/v1/parse?tier=standard&data_class=deidentified" \
  -H "Authorization: Bearer $PARSEDOCS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@fax.pdf"

# 2. Check progress. Repeat until "status" is "completed".
curl -s "https://parsedocs.tathya.ai/v1/jobs/JOB_ID" \
  -H "Authorization: Bearer $PARSEDOCS_KEY"

# 3. Read the structured result, or download the findings as CSV.
curl -s "https://parsedocs.tathya.ai/v1/jobs/JOB_ID/result" \
  -H "Authorization: Bearer $PARSEDOCS_KEY"
curl -s "https://parsedocs.tathya.ai/v1/jobs/JOB_ID/result.csv" \
  -H "Authorization: Bearer $PARSEDOCS_KEY" -o findings.csv

# No file handy? Run a built-in fictional example instead of step 1.
curl -s -X POST "https://parsedocs.tathya.ai/v1/jobs" \
  -H "Authorization: Bearer $PARSEDOCS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"example_id": "referral-nephrology", "tier": "standard"}'

How a job moves

StatusWhat it meansCharged?
queuedAccepted. The price is set aside from your credit.Not yet
processingBeing read. progress shows the stage and pages done.Not yet
completedThe result is ready.Once, for the pages read
failedCould not finish. failure.message says why. Retrying is free.No
pending_fundsSaved, not started: credit does not cover it. It starts by itself once it does (for purchased credit: once payment, agreement and review are complete).No
canceledYou canceled it.No

Endpoints

RequestPurpose
POST /v1/parseUpload a PDF and start a job in one call. Query: tier, data_class, optional webhook_url.
POST /v1/documentsUpload a PDF without starting a job. Returns its page count.
POST /v1/estimatePrice of a document or page count at a tier, and whether your credit covers it.
POST /v1/jobsStart a job for an uploaded document_id or a built-in example_id.
GET /v1/jobs/{id}Status, progress, price, charge and a timeline.
GET /v1/jobsYour jobs, newest first.
POST /v1/jobs/{id}/retryRetry a failed job. Free.
POST /v1/jobs/{id}/cancelCancel a waiting or running job. Never charged.
GET /v1/jobs/{id}/resultThe structured result. Add ?version=1 for the original reading.
GET /v1/jobs/{id}/result.csvFindings as a spreadsheet.
POST /v1/jobs/{id}/reviewAccept, correct, hold or reject findings. Saved as a new version.
GET /v1/jobs/{id}/pages/{n}.pngAn image of a source page, for showing evidence.
GET /v1/account · /v1/usagePlan, what the account may do, credit and the credit history.
GET /v1/pricing · /v1/examplesPrices and the built-in fictional examples. No key needed.

Say what the file is: data_class

deidentified

You state the file contains no patient information: it is synthetic, or it was de-identified before upload. Accepted on every account. Your statement is recorded with the document.

patient

Real patient information. Refused with 403 baa_required until the account has a signed business associate agreement, approval and billing in place. The check happens before the file is read, and no credit balance changes it.

An upload with no data_class is refused unread. The service does not de-identify files for you.

What a result contains

{
  "schema": "parsedocs-result-v1",
  "version": 1,
  "overview": { "headline": "1 patient · lab report · 14 findings · 2 to review" },
  "patients": [{ "key": "A", "name": "…", "date_of_birth": "1961-04-09", "identity": { "confirmed": true } }],
  "documents": [{ "id": "d1", "label": "Lab report", "first_page": 1, "last_page": 2 }],
  "items": [{
    "id": "it_…", "patient_key": "A", "kind": "lab", "entity": "Potassium",
    "value": { "original": "5.9", "number": 5.9, "unit": "mmol/L" },
    "date": { "value": "2026-10-19", "basis": "collected" },
    "polarity": "present",                      // or absent: the page denies it ("no known drug allergies", "was not started")
    "medication_action": "none",                // for medications: started, stopped, changed, continued, refill_requested
    "status": "supported",                      // or needs_review, unsupported
    "flags": [],                                // for example unit_missing, patient_unbound, conflicting_information
    "holds": [],                                // ids of open holds that concern this finding
    "suggested_patient_key": null,              // on a page that prints no patient: who it probably belongs to (not assigned)
    "evidence": [{ "page": 1, "line": "p1.l14", "text": "Potassium 5.9 mmol/L H", "box": [0.11, 0.42, 0.63, 0.44] }],
    "review": { "state": "unreviewed" }
  }],
  "summary": [{ "category": "key_results", "text": "…", "evidence": [ … ] }],
  "holds":   [{ "id": "h_…", "reason": "conflicting_information", "label": "Statements that conflict", "page": 2, "items": ["it_…", "it_…"] }],
  "pages":   [{ "number": 1, "readable": true, "not_captured": [ … ] }],
  "usage":   { "tier": "standard", "pages": 2, "billed_pages": 2, "charge": "$0.04" }
}
  • Evidence. Each finding and summary line names the page and printed line it rests on. box is the line's position on the page image (fractions of width and height) when it could be located.
  • Uncertainty is kept. needs_review findings, unsupported findings (the cited line does not print the value), open holds and not_captured source lines are all returned, never dropped.
  • A contradiction is carried on the findings, not only on the document. When two statements conflict (a medication started on one page and "not started" in an addendum), both findings stay as printed, both become needs_review with the flag conflicting_information, and each names the hold in holds. Nothing is decided for you. In the CSV, read status, holds and open_holds_in_document before treating a row as cleared.
  • Present or absent, and what happened to a medication, are two separate fields. polarity says whether the page affirms the thing or denies it. medication_action says what the page reports was done. A medication the page reports as stopped is an event that happened, so it is present with medication_action: "stopped": "Lisinopril 20 mg, discontinued 10/02/2026" comes back present, stopped, with that date. A denial stays absent: "furosemide was not started" and "denies taking ibuprofen" come back absent. A line that says a medication was not stopped is left exactly as it was read and marked needs_review with the flag negated_action; a "stopped" that the cited line does not print is marked with action_unconfirmed. Neither is turned into a stop.
  • A medication name on a scanned page that the second reader spells differently is flagged, not corrected. The finding keeps the name as first read, becomes needs_review with the flag name_unconfirmed, and its notes give the other spelling. A person decides.
  • A page with no patient printed on it is not assigned. Its findings come back with no patient_key, the flag patient_unbound and, when the surrounding pages point to one person, a suggested_patient_key for a person to confirm. This is deliberate: a continuation page is attached by a person, not by a guess.
  • Versions. Version 1 is the reading. Each staff review saves a new version; corrected findings keep the first reading under original.
  • No urgency grading. Results report what is printed. They do not rank or triage.

Reviewing a result

A review is a list of actions. It is saved as a new version; the reading it started from is kept. Send the version you were looking at as base_version: if someone saved a newer one in the meantime you get 409 version_conflict and nothing changes.

POST /v1/jobs/{id}/review
{
  "base_version": 1,
  "actions": [
    { "target": "item", "id": "it_…", "action": "accept" },
    { "target": "item", "id": "it_…", "action": "correct", "fields": { "value_original": "5.4", "event_date": "2026-10-19" }, "note": "read from the page" },
    { "target": "item", "action": "add", "fields": { "entity": "Creatinine", "kind": "lab", "value_original": "1.4", "unit": "mg/dL", "page": 2 } },
    { "target": "hold", "id": "h_…", "action": "resolve", "note": "confirmed with the referring office" },
    { "target": "document", "action": "mark_reviewed" }
  ]
}
TargetActionsNotes
itemaccept · correct · hold · reject · reset · addcorrect can change entity, value_original, unit, event_date (YYYY-MM-DD), patient_key, kind, polarity and med_action; the first reading stays under original. add needs at least entity and is marked as added by staff. reset returns a finding to its first reading.
holdresolve · reopenA hold is never removed, only resolved, with who and when.
documentmark_reviewed · hold · reopenThe state of the whole result.

A malformed action is answered 400 invalid_action or 400 unknown_target with what to fix, and no version is saved. Values are text or numbers of at most 300 characters.

Errors

Every error has the same shape. code is stable; message says what to do next.

{ "error": { "code": "upgrade_required", "message": "Your document is saved and nothing was charged…",
             "request_id": "req_8Zk2…", "details": { "job": { "id": "job_…", "status": "pending_funds" }, "estimate": "$1.20" } } }
HTTPCodeMeaning
400data_class_required · data_class_conflict · invalid_pdf · encrypted_pdf · invalid_tier · invalid_action · unknown_targetFix the request. Nothing was stored or charged.
401unauthenticatedMissing, invalid, expired or revoked key.
402upgrade_required · insufficient_creditThe job is saved as pending_funds and starts by itself once credit is available (for purchased credit: once payment, agreement and review are complete).
403baa_required · missing_scopePatient data is not enabled, or the key lacks a permission.
404not_foundNot in this account.
409idempotency_key_reused · job_not_ready · version_conflictThe request conflicts with current state; the message says how.
413file_too_large · too_many_pages · too_many_pages_for_tier · page_too_largeSplit the document, or use Premium for longer ones. A page larger than about 20 inches on a side is refused.
429rate_limitedWait Retry-After seconds.
503engine_unavailableProcessing is not available right now. Nothing was charged.

Retries and webhooks

Safe retries

Send an Idempotency-Key header with any unique value. Repeating the request with the same key returns the same job. A document already waiting or running at a tier is returned instead of duplicated. One completed job is one charge.

Result webhooks

Pass webhook_url (https) when you start a job. We send job.completed or job.failed with the job's status and links, never document content, and retry on failure. Verify Parsedocs-Signature: t=…,v1=…: v1 is the hex HMAC-SHA256 of "<t>.<raw body>" with your webhook secret.