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

  1. 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.
  2. 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.
  3. The patient's numbers: the IP number and the UHID, both optional. demographics.ip_number, or demographics.ip_number_not_issued: true where the hospital issues none; and demographics.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_…

Linking a doctor

  1. 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.
  2. You send it: POST /v1/doctors/links with {"code": "ABCD-EFGH-JKMN"}.
  3. The answer is the link, with the doctor's name, registration number and council. Use its id as doctor_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

RequestWhat it does
POST /v1/summariesCreate a summary for a linked doctor: {"doctor_link_id", "contract"}. Send an Idempotency-Key header and a retry returns the same summary.
GET /v1/summariesList 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}/contractReplace the form while the summary is open.
POST /v1/summaries/{id}/generateDraft it. Answers 202; see Drafting.
PUT /v1/summaries/{id}/approvedSave 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}/pdfThe 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.

FieldShape
contract_version"1"
demographicsname, 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_uptext
investigationstext, and rows of test, value, unit, range, flag
diagnosistext, icd
proceduresrows of procedure, surgeon, anaesthesia, findings
discharge_medicationsrows of drug, dose, route, frequency, duration
dispositionnormal, 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
deathOnly 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
allergiesstatus (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.

Webhooks

Intelliwise registers your webhook address (https) and gives you a signing secret once. Events:

EventWhen
summary.draft_readyA draft was stored on one of your summaries, from the API, the embedded page or the doctor's own app.
summary.draft_failedA draft job you started ended without a draft. data.error says why.
summary.signedThe 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'));

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.

  1. Intelliwise registers the origins allowed to frame the page, for example https://his.example.in. Other sites cannot frame it.
  2. Your server calls POST /v1/embed-sessions with {"doctor_link_id"}, and optionally "summary_id" to reopen one. The answer has a url and expires_at (60 minutes).
  3. 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.

StatusCodes
400contract_invalid (with errors), idempotency_key_invalid, status_invalid
401api_key_invalid, api_key_expired, api_key_revoked
402vendor_no_credits: you have no summaries left
403vendor_inactive; account_pending or account_rejected for the doctor
404summary_not_found, doctor_link_not_found, link_code_not_found_or_used
409summary_signed, vendor_link_revoked, not_signed (PDF before signing)
422contract_incomplete (with missing), idempotency_key_reused
429rate_limited, with Retry-After
502, 504In a failed draft job: llm_upstream, llm_truncated, llm_timeout and similar. Nothing was used.