Authentication
Every request must include one of:
- X-API-Key header (used by the generator page itself):
X-API-Key: DF@2026:DF@2026 - HTTP Basic Auth (for 3rd-party integrations / Postman / curl
-u):curl -u DF@2026:DF@2026 ...
The key is defined in app/config/api_key.php (default shown above). Change this before any production deployment.
To rotate the key
- Copy
app/config/api_key.local.php.exampletoapp/config/api_key.local.phpand set a long random value — or set theTRUPATH_API_KEYenvironment variable, which takes priority over both files. - Update the
API_KEYconstant inindex.php'scallServerApi()function to the same value, since that page sends it as a literal (it has no server-side templating). - Restart/redeploy. Requests using the old key get
401 Unauthorized.
POST /api/generate-report
Headers: Content-Type: application/json, X-API-Key: <key>
.php extension is never required on this or any other route in the app (/index, /report, /dashboard/login, etc.) — a root-level .htaccess rewrite (not a redirect) strips it internally, so POST bodies, query strings, and auth headers all pass through unchanged. The .php form (/api/generate-report.php) still works too, if you already have it hardcoded somewhere.Request body
{
"mode": "initial_pcr",
"case": { "...": "see shape below" }
}
| Field | Description |
|---|---|
mode |
"pcr_only" — definitive PCR-only report. Always rendered with MAST forced off regardless of any MAST order on the case (no "results pending" header). Organism/MAST validation is relaxed since MAST isn't being reported at all."initial_pcr" — preliminary PCR / MAST-pending report. Requires case.requisition.mastOrder to be something other than "NOT_ORDERED"; if a MAST-eligible bacterial organism is present (clinically significant, category BACTERIA, participates true, and has a standard MAST plate in Evolve Antibiotic Plate Types(3)), the header shows "PCR RESULTS ONLY / MAST RESULTS PENDING"."final_pcr_mast" — the final report. Requires case.requisition.mastOrder to be something other than "NOT_ORDERED", requires at least one MAST-eligible bacterial organism as described above, and requires at least one S/R result in case.mastResults for every such organism.
|
case |
The same shape this instance's own readCase() produces client-side: schemaVersion, requisition (mastOrder, billing/ICD-10 fields, etc.), patient, order, organisms (up to 19, each {organismId, loadCategory, mastLoad, source} - loadCategory is "HIGH"/"MODERATE"/"LOW" (a legacy raw numeric load value or the retired "MEDIUM" label is still accepted and auto-bucketed for backward compatibility); mastLoad is the exact numerical Final MAST load, required and must be greater than 0 for any bacterium being MAST-assessed on a final_pcr_mast report (fungal/non-bacterial organisms never need it); source is "PCR" or "MAST_ONLY", defaulting to "PCR"), detectedGenes (gene acronyms or ids), empiric ({drugId|genericName, displayName, strength, directions} - strength and directions are joined with " ยท " into the report's displayed instructions text; a caller may still send instructions directly instead), allergies (free-text strings - or ["No known drug allergies"], which is stripped before any allergy-coverage logic runs), clinical ({pregnancy, renalIssue, hepaticIssue}), mastResults ({organismId: {drugId: "S"|"R"|"-"}}).See reference_source/sample_imports/Sample_Complete_PCR_Case.json and Sample_Complete_PCR_MAST_Case.json for complete real examples, and app/src/CaseInput.php for the exact validation rules.Unrecognized values are tolerated, not rejected: an organisms[] row whose organismId doesn't match this instance's reference catalog is silently dropped from the case (not a hard failure); the same is true for an unrecognized detectedGenes entry, and for an empiric block whose drugId/genericName doesn't match a known antimicrobial (the case proceeds with no empiric drug reported). This is meant to make the API forgiving of an integration partner sending IDs slightly out of sync with the current reference catalog, rather than failing the whole report. Each detectedGenes entry may also be sent as a plain string or as {"geneId": "..."} / {"acronym": "..."}. |
Response — success (200)
{
"success": true,
"message": "Report generated successfully",
"mode": "final_pcr_mast",
"renderer": "chrome",
"reportId": "20260725085315_27443829",
"pdfUrl": "{{BASE_URL}}/uploads/pdf/20260725085315_27443829.pdf",
"htmlUrl": "{{BASE_URL}}/uploads/html/20260725085315_27443829.html",
"getApi": "{{BASE_URL}}/api/get-report?reportId=20260725085315_27443829"
}
renderer is which engine actually produced the PDF: "chrome" (the primary path - headless Chrome printing the same self-contained HTML the in-browser preview renders, so the PDF matches it exactly) or "dompdf-fallback" (used automatically, only if Chrome can't run on this host at all - a simpler plain HTML/CSS layout, not pixel-matched to the preview, so a request never hard-fails just because Chrome is unavailable).
Response — validation error (422)
{ "success": false, "message": "A final PCR + MAST report requires a MAST order." }
Response — PDF generation failure (500)
Only happens if both renderers fail:
{ "success": false, "message": "PDF generation failed on both renderers.\nChrome: ...\nDompdf fallback also failed." }
Response — unauthorized (401)
{ "success": false, "message": "Unauthorized" }
GET /api/get-report?reportId=<id>
Headers: X-API-Key: <key>
Returns the same case payload that was submitted plus the pdfUrl, or 404 if the id doesn't exist. Report ids are validated against ^\d{14}_[0-9a-f]{8}$ before touching the filesystem, so this endpoint can't be used for path traversal.
curl examples
# Final PCR + MAST report (wrap the sample file's contents in
# {"mode":"final_pcr_mast","case": ...} first)
curl -X POST {{BASE_URL}}/api/generate-report \
-H "Content-Type: application/json" \
-H "X-API-Key: DF@2026:DF@2026" \
--data @final_pcr_mast_payload.json
# Retrieve it again later
curl "{{BASE_URL}}/api/get-report?reportId=20260725085315_27443829" \
-H "X-API-Key: DF@2026:DF@2026"
Postman collection
TruPath_API.postman_collection.json includes all three mode values (PCR Report Only, Preliminary PCR / MAST Pending, Final PCR + MAST) plus Get Report and an unauthorized-request check. Import it into Postman, then set the collection variables:
| Variable | Default | Set to |
|---|---|---|
baseUrl | {{BASE_URL}} | Your actual host, no trailing slash - use http://127.0.0.1/TruPath/tpl9 for local testing instead |
apiKey | DF@2026:DF@2026 | Whatever app/config/api_key.php resolves to on that host |
Run either "Generate..." request first — its test script captures the returned reportId into the reportId collection variable automatically, so "Get Report" works immediately afterward with no manual copy/paste.
What the API does NOT do
- It does not change
app/src/ClinicalEngine.php's rules based on where the request came from — the generator page's client-side preview and this API run the same logic, just against two independently-maintained copies of the reference data (the page's embedded snapshot vs. the MySQL database). Keep them in sync by re-runningtools/import_data_library.phpwheneverreference_source/TruPath_Generator_Data_Library_v3_1_2.jsonchanges, and re-embedding that same JSON intoindex.phpif the client sends an updated data library package. - It does not manage reference data (pathogens/genes/antimicrobials/recommendations) — use the admin dashboard for that.