API archaeology: reconstructing an 18-module hospital system from its own network traffic. Companion note to A Bilingual Knowledge Layer for a Complex HIS.
The gap the paperwork left
The contractual spec is a 102-page PDF written for procurement — scope, modules, liability. The operator material is a set of vendor demo videos, recorded in Turkish, describing screens that have since changed. What does not exist anywhere: an integration guide. Want to know what actually happens when a clerk saves an encounter, or which service call sits behind the Print button? No document answers.
But the browser knows. Every click is a transaction on the wire, and transactions cannot lie — they either worked or they didn't. So we did what any engineer deprived of documentation eventually does: we read the traffic.
What a HAR actually is
A HAR (HTTP Archive) file is what browser dev tools produce when you export the Network panel: a JSON document containing every request and response — URL, headers, session cookies, POST bodies, status codes, timings. Play one workflow with the recorder on, and you hold the system's real API behavior for that workflow, step by step, in the exact order a human triggers it.
The HAR is not a description of what the system is supposed to do. It is a recording of what the system did. When those two disagree, trust the recording.
Capture discipline
Three rules kept the corpus usable and safe:
One workflow per capture. Register a patient: capture. Start an encounter: capture. Order a lab test: capture. Each named and dated, never mixed — mixing workflows turns reconstruction into guesswork.
Staging only, synthetic patients. Every capture ran against the staging environment on test records we created ourselves. No production traffic was ever recorded, so no real patient's data exists anywhere in this corpus. The live demo banner compresses this discipline into one phrase: PHI de-identification enforced.
Anchor roles separately. The same screen behaves differently for a registration clerk, a billing officer, and a department head — so each role's path was captured on its own clean session. That separation later became the role-scoped content layer in the assistant.
First pass: one giant JSON → a clean inventory
Raw, a single capture can run to hundreds of requests. The first pass was mechanical: group by endpoint and SOAP action, count, then sort by timestamp to recover call order. The REST surface organized itself quickly — 99 endpoints across inventory, hospital, and security services. The real beast was SOAP.
| Source | It claimed | What the wire showed |
|---|---|---|
| Paperwork | "Standard integration services" | Two SOAP giants of very un-standard size: PatientAdmissionService.asmx — 457 operations, a ~1.17 MB WSDL — and PatientFollowUpService.asmx — 707 operations, ~1.77 MB. |
| Demo videos | Screen walkthroughs | Every screen click fans out into a burst of service calls in a strict, invisible order — with server-side context the video never shows. |
| Folklore | "You can't do X" | Mostly true — and the failing request revealed exactly why, which is the part worth writing down. |
The rules nobody wrote down
Opening the WSDLs gave vocabulary: 457 + 707 = 1,164 operation names, none of them self-explanatory. The grammar came from the HARs, one failed replay at a time: replay the captured sequence, hit an error, adjust, replay again. After enough rounds a set of unwritten rules surfaced — not one of them appears in any document we could obtain:
1. Register the screen before you save. Before any save arrives, the screen itself must be registered server-side. Skip that first call and the save fails with an error that never mentions registration.
2. Saves are stateful per screen. The server tracks an edit session keyed to the screen; saves are not independent calls into a void. Replay them out of order and the second save quietly corrupts what the first meant to do.
3. New rows are ObjectID = -1. Never 0. In most integration land, zero means "unset". Here, zero means "you didn't read the captures". -1 is the sentinel for a brand-new child row.
4. LoadPatientInfo is the canonical-OID bridge. The UI's display identifiers and the service layer's internal object IDs are different currencies. This call performs the exchange. Call chains that skip it either hit nothing or — worse — succeed against the wrong object.
5. Sub-objects must be fully nested. Partial objects don't throw errors. They fail politely: empty responses, silent no-ops. The polite failure is the expensive kind — nothing alerts, data just isn't there.
A general observation on error culture: faults come back generic, and the informative message often lives one level deeper in the response than the surface fault node. Comparing HARs side by side — a call that failed against its corrected twin — became the fastest way to learn the contract. Error pairs are the closest thing this system has to error documentation.
The shape of a save (sanitized)
POST /services/<SOAP service>.asmx
SOAPAction: "Save…"
Session: cookie handed out once at login
Context: screenID from the earlier Register call
visit
├─ patientOID ← from LoadPatientInfo — not the UI's display ID
└─ rows[]
└─ ObjectID: -1 ← new row; 0 is not the sentinel here
That is the shape, not a verbatim capture — namespaces, field names, and payloads are generalized. The point is the dependency chain: login → register screen → load OID → save with the nesting rules honored. Break any link and the chain reports failure somewhere that doesn't name the cause.
From captures to a working map
With the rules in hand, each recorded workflow became a directed graph: a node per service operation, an edge where the capture showed sequence, annotations for required fields lifted from the POST bodies. Conflicts between documents and traffic were settled by replay, not debate: the wire wins.
Then the graph stopped being a picture and became a script.
Proof: the outpatient journey, end to end
The real validation of the mapping was to drive the full 7-stage outpatient journey — the same path a human walks, from first registration through completed follow-up — purely through the service layer: staging environment, synthetic records, no browser in the loop. It took numerous iterations (each unwritten rule above was extracted from exactly one of those failures), and then it passed and held: the scripted sequence reproduces what the captured browser sessions did, stage for stage.
From there the same loop — capture → replay → note — was applied across the remaining modules. The surviving notes became the module documents in the assistant's training corpus: the same synthesis the main post lists as training/*.md.
What it feeds
This note is the provenance behind one row of the knowledge-base table in the main post: KNOWLEDGE_BASE.md — the 18-module map, the 99 REST endpoints, the SOAP signatures, the staging constants, the protocol rules. Combined with the module docs and Q&A banks, all of it funnels into one retrieval stack: hybrid BM25 + multilingual embeddings, cross-encoder rerank, enforced citations, and an abstain threshold.
Two boundaries were non-negotiable. The assistant never exposes integration internals — endpoints, signatures, identifiers are translated into plain operator steps. And it never cites a HAR; it cites the distilled, reviewed notes the HARs produced.
What we deliberately did not do
HARs are sensitive by construction — they carry session credentials, and captured carelessly they carry data no patient consented to put in an archive folder. So: captures on staging with synthetic data only; captures never leave the analysis machine; nothing published verbatim; nothing replayed against production; nothing from inside a capture quoted anywhere. The HARs were scaffolding. The reviewed notes are the product.
Closing thought
An undocumented system is not an unknowable one. The traffic is a spec that cannot lie, the browser records it for free, and patience converts it into a contract. Write down what the wire says before the tribal knowledge retires — that's the whole method.
💬 Discussion