Developers: the HIS vendor API
For hospital information systems that want to send discharge details to Kural discharge summaries and bring back the signed summary. Access is by agreement: keys are issued to a named vendor, never by sign-up.
Download the Postman collection Request access
Three rules the API is built around
- The doctor signs. There is no request that signs a summary. The treating doctor signs in Kural discharge summaries with their own Google account, and you are told when they have.
- The doctor says who may connect. You reach a doctor only with a one-time code the doctor makes in their own account. Either of you can end the connection, and then you no longer reach that doctor's summaries.
- The patient's numbers: the IP number and the UHID, both optional.
demographics.ip_number, ordemographics.ip_number_not_issued: truewhere the hospital issues none; anddemographics.uhid(since 27 Sep 2026), printed on the PDF beside the IP number. Both are letters, digits, space,/ . -, up to 30. There is no field for a phone number, and an unknown field is refused. Keep your own mapping from the summary id you get back to your record.
Keys
Base address: https://review.kural.health/v1. Send the key on every request:
Authorization: Bearer dcsk_test_3f9a0c1b2d4e5f60_…
- A key starts
dcsk_test_ordcsk_live_, and works only on a service in that mode. - A key is shown once, when it is made. It is stored only as a hash, so a lost key is replaced, not recovered.
POST /v1/keys/rotatereturns a new key; the old one keeps working for 24 hours.- Up to 120 requests a minute per key. Above that the answer is
429withRetry-After. The limit is counted per server instance, so treat it as approximate. GET /v1/accountshows your name, the summaries you have left and when they expire, and the key in use.
Linking a doctor
- The doctor, already verified in Kural discharge summaries, opens their account and makes a connection code. It looks like
ABCD-EFGH-JKMN, works once, and lasts 30 minutes. - You send it:
POST /v1/doctors/linkswith{"code": "ABCD-EFGH-JKMN"}. - The answer is the link, with the doctor's name, registration number and council. Use its
idasdoctor_link_id.
GET /v1/doctors/links lists your links. DELETE /v1/doctors/links/{id} ends one. A link the doctor ended shows "status": "revoked".
Summaries
| Request | What it does |
|---|---|
POST /v1/summaries | Create a summary for a linked doctor: {"doctor_link_id", "contract"}. Send an Idempotency-Key header and a retry returns the same summary. |
GET /v1/summaries | List yours, newest first. Filters: doctor_link_id, status=open|signed, before (an id, for the next page), limit up to 200. |
GET /v1/summaries/{id} | The summary, its current draft, the saved text, and the latest draft job. |
PUT /v1/summaries/{id}/contract | Replace the form while the summary is open. |
POST /v1/summaries/{id}/generate | Draft it. Answers 202; see Drafting. |
PUT /v1/summaries/{id}/approved | Save text the doctor edited in your system, with the draft_id of the draft it was edited from. The doctor still signs it in Kural discharge summaries, and only while that draft is the latest. |
GET /v1/summaries/{id}/pdf | The signed PDF, once the doctor has signed. |
The form (contract version 1)
Only these fields are accepted. Dates are YYYY-MM-DD; text fields are length-limited.
| Field | Shape |
|---|---|
contract_version | "1" |
demographics | name, age_years, sex (male, female, other), admission_date, discharge_date, department, blood_group, ip_number (up to 30 letters, digits, space, / . -; needed to draft and sign) or ip_number_not_issued (true) |
complaint, history, examination, course, condition_at_discharge, follow_up | text |
investigations | text, and rows of test, value, unit, range, flag |
diagnosis | text, icd |
procedures | rows of procedure, surgeon, anaesthesia, findings |
discharge_medications | rows of drug, dose, route, frequency, duration |
disposition | normal, lama, ama, died, medico_legal, newborn, psychiatric or mtp. medico_legal, newborn and mtp are coming soon: until each is released, drafting answers 409 discharge_type_not_drafted with release: "coming_soon", at once, with no job and no charge. psychiatric is never drafted or signed |
death | Only with died: date, time (HH:MM, 24-hour), cause_immediate, cause_antecedent, cause_underlying, other_conditions. Date, time and the immediate cause are needed to draft and to sign; they print on the death summary as entered. A death carries no discharge_date, condition_at_discharge, follow_up or discharge_medications |
allergies | status (nkda, recorded, not_recorded), and text when recorded |
A summary can be saved half-filled. To draft, it needs the patient's name, the admission date, the diagnosis, and a route for every medicine.
Drafting
POST /v1/summaries/{id}/generate answers 202 with a draft_job. Then either poll GET /v1/summaries/{id} until draft_job.status is done or failed, or wait for a webhook.
- Refusals that cost nothing come back at once, not as a job: a missing field is
422 contract_incompletewith the list; a signed summary is409. - A second request while a job is running returns that job. It does not draft twice.
- Each draft uses one of your summaries. A draft that fails uses nothing: the job ends
failedwith its reason, and anything taken is returned. - Summaries you have not used expire on the date shown in
GET /v1/account.
Webhooks
Intelliwise registers your webhook address (https) and gives you a signing secret once. Events:
| Event | When |
|---|---|
summary.draft_ready | A draft was stored on one of your summaries, from the API, the embedded page or the doctor's own app. |
summary.draft_failed | A draft job you started ended without a draft. data.error says why. |
summary.signed | The doctor signed. The PDF is ready. data.summary_code is the code printed on every page. An amendment also carries data.amends_summary_id: a signed summary is never changed, the doctor amends it with a new summary that replaces it. |
The body carries ids and states only, never clinical text. Fetch the summary with your key.
{ "id": "evt_…", "type": "summary.signed", "created_at": "2026-09-16T08:30:00.000Z",
"data": { "summary_id": "42", "doctor_link_id": "7", "status": "signed" } }
Check every delivery before you trust it. The DCS-Signature header is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of t + "." + raw body with your secret:
const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const ok = fresh && crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'));
- Answer with any
2xxwithin 10 seconds. Anything else is retried after 1, 5 and 30 minutes, then 2, 6, 12 and 24 hours, then marked failed. - The same event can arrive twice. Use
DCS-Event-Idto ignore repeats.
The embedded page
For a hospital system without its own discharge page. The doctor fills the form, drafts, edits and saves inside your system; you pay for the drafts.
- Intelliwise registers the origins allowed to frame the page, for example
https://his.example.in. Other sites cannot frame it. - Your server calls
POST /v1/embed-sessionswith{"doctor_link_id"}, and optionally"summary_id"to reopen one. The answer has aurlandexpires_at(60 minutes). - Put the URL in an iframe as it is. The token is in the part after
#, which browsers never send to a server.
<iframe src="https://review.kural.health/embed/v_…#token=…" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox" style="width:100%;height:100vh;border:0"></iframe>
The page cannot sign. Its "Sign in DCS" button opens Kural discharge summaries in a new window, where the doctor signs in and signs; that is why the frame needs allow-popups. The page shows the signature when it is done, and you receive summary.signed.
Errors
Every error is JSON with an error code, sometimes with facts beside it. A summary or link you cannot reach is always 404, so ids reveal nothing.
| Status | Codes |
|---|---|
| 400 | contract_invalid (with errors), idempotency_key_invalid, status_invalid |
| 401 | api_key_invalid, api_key_expired, api_key_revoked |
| 402 | vendor_no_credits: you have no summaries left |
| 403 | vendor_inactive; account_pending or account_rejected for the doctor |
| 404 | summary_not_found, doctor_link_not_found, link_code_not_found_or_used |
| 409 | summary_signed, vendor_link_revoked, not_signed (PDF before signing) |
| 422 | contract_incomplete (with missing), idempotency_key_reused |
| 429 | rate_limited, with Retry-After |
| 502, 504 | In a failed draft job: llm_upstream, llm_truncated, llm_timeout and similar. Nothing was used. |