> For the complete documentation index, see [llms.txt](https://docs.digit.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.digit.org/health/design/architecture/low-level-design/services/health-services/referral-management.md).

# Referral Management

## Overview <a href="#api-spec" id="api-spec"></a>

Referral Management records follow-up care during a health campaign. Field workers use it when a beneficiary needs care that cannot be provided immediately. It helps facilities and workers continue that care.

It manages three related records:

* **Referral** — records further care for a beneficiary. It identifies the referrer, recipient, reason, and any triggering side effect.
* **Side Effect** — records an adverse reaction after a campaign task, such as a vaccine dose.
* **HF Referral (Health Facility referral)** — records a referral created at or for a health facility. It includes symptoms and a referral code.

A referral links to its campaign project and beneficiary. It can also link to its originating side effect. An HF referral links to its project facility and locality.

It also powers the **offline-first mobile app**: it can bulk-pull everything a device needs for a locality in one call (the **downsync** endpoint), so workers can keep operating with no network.

In short: *"who got sent for follow-up, why, and what does the offline app need to carry with it?"*

## Business Flow

* **During the campaign (runtime)**, field workers register referrals and side effects from the mobile app — usually offline, queued, and pushed up when connectivity returns.
* **Health-facility staff/distributors** raise HF referrals tied to a project facility and locality.
* Every record links back to the **campaign context** — the project, the project beneficiary, and (for referrals) the originating side effect — so follow-up is traceable to the right person and campaign.
* The mobile app periodically **downsyncs** a locality's data (households, members, beneficiaries, tasks, referrals, side effects, HF referrals) so a worker has the full picture on-device.
* The records feed the **dashboards** (via the transformer/indexer → Elasticsearch) so programme managers can see referral and adverse-event volumes.

## Key APIs / Entry Points

All endpoints use the `/referralmanagement` base path. The table omits this base path. Referral, side effect, and HF referral each support create, update, delete, bulk operations, and search. The remaining endpoints support offline sync.

<table><thead><tr><th width="328.3984375">Endpoint</th><th>Purpose</th></tr></thead><tbody><tr><td><code>POST /v1/_create</code>, <code>/v1/bulk/_create</code></td><td>Record a referral (single or bulk).</td></tr><tr><td><code>POST /v1/_update</code> · <code>/v1/_delete</code> (+ <code>/bulk/…</code>)</td><td>Correct or soft-delete a referral.</td></tr><tr><td><code>POST /v1/_search</code></td><td>Find referrals (by beneficiary, project, ids, since-time …; supports <code>includeOnlyUpdatedByOthers</code>).</td></tr><tr><td><code>POST /side-effect/v1/_create … _search</code> (+ bulk)</td><td>Same shape for adverse-event records.</td></tr><tr><td><code>POST /hf-referral/v1/_create … _search</code> (+ bulk)</td><td>Same shape for health-facility referrals.</td></tr><tr><td><code>POST /beneficiary-downsync/v1/_get</code></td><td>Offline bulk pull — everything a locality needs in one response (or pre-generated file links).</td></tr><tr><td><code>POST /downsync/v1/_generate</code> · <code>/downsync/v1/jobs/_search</code></td><td>Kick off background generation of downsync files for a tenant/project, and poll the job.</td></tr></tbody></table>

**Kafka entry points (async).** Bulk requests land on `save-referral-bulk-topic` / `update-…` / `delete-…` (and the side-effect / hf-referral equivalents) and are processed by the service's own consumer. Persisted results go out on `save-referral-topic` / `update-referral-topic` / `delete-referral-topic` (+ side-effect and hf-referral topics) for the persister and transformer.

**Swagger contract:**

{% embed url="<https://editor.swagger.io/?url=https://raw.githubusercontent.com/egovernments/health-campaign-services/master/docs/health-api-specs/contracts/referral-management.yml>" %}

#### Kafka topics

<table><thead><tr><th>Topic</th><th width="108.0390625">Dir</th><th>Purpose</th></tr></thead><tbody><tr><td><code>save-referral-bulk-topic</code></td><td>in</td><td>Bulk referral create requests</td></tr><tr><td><code>update-referral-bulk-topic</code></td><td>in</td><td>Bulk referral update requests</td></tr><tr><td><code>delete-referral-bulk-topic</code></td><td>in</td><td>Bulk referral delete requests</td></tr><tr><td><code>save-side-effect-bulk-topic</code></td><td>in</td><td>Bulk side-effect create requests</td></tr><tr><td><code>update-side-effect-bulk-topic</code></td><td>in</td><td>Bulk side-effect update requests</td></tr><tr><td><code>delete-side-effect-bulk-topic</code></td><td>in</td><td>Bulk side-effect delete requests</td></tr><tr><td><code>save-hfreferral-bulk-topic</code></td><td>in</td><td>Bulk HF-referral create requests</td></tr><tr><td><code>update-hfreferral-bulk-topic</code></td><td>in</td><td>Bulk HF-referral update requests</td></tr><tr><td><code>delete-hfreferral-bulk-topic</code></td><td>in</td><td>Bulk HF-referral delete requests</td></tr><tr><td><code>save-referral-topic</code></td><td>out</td><td>Persist new referrals</td></tr><tr><td><code>update-referral-topic</code></td><td>out</td><td>Persist referral updates</td></tr><tr><td><code>delete-referral-topic</code></td><td>out</td><td>Persist referral soft-deletes</td></tr><tr><td><code>save-side-effect-topic</code></td><td>out</td><td>Persist new side-effects</td></tr><tr><td><code>update-side-effect-topic</code></td><td>out</td><td>Persist side-effect updates</td></tr><tr><td><code>delete-side-effect-topic</code></td><td>out</td><td>Persist side-effect soft-deletes</td></tr><tr><td><code>save-hfreferral-topic</code></td><td>out</td><td>Persist new HF-referrals</td></tr><tr><td><code>update-hfreferral-topic</code></td><td>out</td><td>Persist HF-referral updates</td></tr><tr><td><code>delete-hfreferral-topic</code></td><td>out</td><td>Persist HF-referral soft-deletes</td></tr></tbody></table>

## Dependencies

* **idgen** — generates referral/side-effect / hf-referral ids.
* **project** — validates the project, project-beneficiary, project-task and project-facility a record links to.
* **facility** — validates the health facility an HF referral is for.
* **household/individual** — read during downsync to assemble the full beneficiary bundle.
* **service-request** — optional services pulled into downsync when enabled.
* **MDMS** — resolves project type/beneficiary type used to drive downsync.
* **egov-enc-service** — decrypts beneficiary fields where needed during downsync.
* **health-services-common / -models** — shared clients, validators, POJOs (the `Referral`, `SideEffect`, `HFReferral` models live here).
* **Kafka** — async create/update/delete pipeline.
* **egov-persister** (deployed via the `configs/` repo) — actually writes the rows to Postgres off the `save-*` topics.
* **transformer/indexer → Elasticsearch** — builds the dashboard read-model from the same topics.
* **Redis** — caches incoming bulk referrals before the consumer processes them (and search caching in the shared repository layer).
* **S3 (object storage)** — stores pre-generated downsync files; the service hands back time-limited download links.

## Process Flow

Writes are **asynchronous**:

1. The API validates and enriches the request. It then acknowledges the request.
2. A Kafka consumer processes the request. It emits a `save-*`, `update-*`, or `delete-*` event.
3. **egov-persister** writes that event to Postgres. The **transformer/indexer** projects it to Elasticsearch for dashboards.

Search reads from Postgres. Downsync is a synchronous bulk read. It calls dependent services to build the device bundle.

#### Sequence Diagrams

{% tabs %}
{% tab title="Side Effect" %}

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/Kl8UjSxMyj4y5JgezxPp/image.png" alt=""><figcaption><p>Side Effect Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/4lngjEmRNpUHusWrCtNT/image.png" alt=""><figcaption><p>Side Effect Bulk Create<br></p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/GHqSvm38j3Ecy0vJ2pTl/image.png" alt=""><figcaption><p>Side Effect Update<br></p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/r5suJEXvjzsfrKFM0b3t/image.png" alt=""><figcaption><p>Side Effect Bulk Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/VT4ww78wFRLQ68RQ4bGs/image.png" alt=""><figcaption><p>Side Effect Delete</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/sqtxAsegcJKYUBmghYfn/image.png" alt=""><figcaption><p>Side Effect Bulk Delete</p></figcaption></figure>
{% endtab %}

{% tab title="Referral" %}

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/h0J0P93WX2B2Mk7oF7SI/image.png" alt=""><figcaption><p>Referral Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/6xeqy4lBh8MdhwZhOM0l/image.png" alt=""><figcaption><p>Referral Bulk Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/GQXCp2UUf3hudwdOEeny/image.png" alt=""><figcaption><p>Referral Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/qM46s6D3InTHrytfgwe9/image.png" alt=""><figcaption><p>Referral Bulk Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/DMap3nrnCXjnhHS0Bdqq/image.png" alt=""><figcaption><p>Referral Delete</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/j9H8ks0WqOEy6PPPNXlV/image.png" alt=""><figcaption><p>Referral Bulk Delete</p></figcaption></figure>
{% endtab %}

{% tab title="HFReferral" %}

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/Y6H1idrgmltBhVpcxgmf/image.png" alt=""><figcaption><p>HFReferral Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/KZV2XbuEH0OcsJlgBOXB/image.png" alt=""><figcaption><p>HFReferral Bulk Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/5P36s0OYqeltd4A1kjEb/image.png" alt=""><figcaption><p>HFReferral Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/vqpLrhsVTjpzpurwzlSr/image.png" alt=""><figcaption><p>HFReferral Bulk Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/o7JGIJN6jtxQUDA5WoVn/image.png" alt=""><figcaption><p>HFReferral Delete</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/iTgVWpfApHDyQfpLdbFz/image.png" alt=""><figcaption><p>HFReferral Bulk Delete</p></figcaption></figure>
{% endtab %}
{% endtabs %}

### Failure / Retry Handling

* **Async, no batch rollback.** A bulk request returns `202` before persistence. If one record fails consumer validation, the others remain unchanged. Use each record's `clientReferenceId` to correlate it with consumer logs. Correct the failed record, then resubmit it.
* **Idempotency** is via `clientReferenceId` — re-submitting the same one should not create a duplicate row (the unique-entity validators guard this).
* **Optimistic locking** via `rowVersion` protects against concurrent edits on update/delete.
* **Soft delete** (`isDeleted`) everywhere — nothing is hard-deleted.
* **Downsync is best-effort and bounded.** If `lastSyncedTime` is missing or older than the staleness threshold (default 8h), the request is served from **pre-generated files**; if none exist yet it returns `PREGEN_NOT_AVAILABLE` and a generation job must be triggered first.
* **Downsync generation is single-flight per tenant/project.** A second `/downsync/v1/_generate` while one is running returns `409 Conflict` with progress details; interrupted jobs are resumed on startup (`503 SERVICE_INITIALIZING` until that scan completes). Per-file failures are tracked, so a job can finish `COMPLETED`, `PARTIAL_FAILURE`, or `FAILED`.
* If the **persister config** for the referral topics is missing/stale in an environment, the API will accept writes but rows will silently not appear in Postgres — a classic "it worked in QA" trap.

### Known Risks / Limitations

* **Downsync correctness depends on fresh pre-generated files.** For stale/cold syncs, the device gets whatever was last generated; if no generation job has run, the locality returns no data (`PREGEN_NOT_AVAILABLE`) — operations must schedule/trigger generation.
* **`recipientType` / `reasons` / `symptoms` carry free-form or list data** validated only at app level — the DB won't stop unexpected values.
* **Side effects are linked to a task; the linkage query is a known shortcut** (flagged in code as needing a proper task-search enhancement) — keep an eye on it as task volumes grow.
* **Relaxed-edit / last-write semantics across devices.** With `includeOnlyUpdatedByOthers`, a device intentionally ignores its own latest edits during sync; QA should confirm multi-device edit scenarios behave as expected.
* **Downsync fans out to many services synchronously.** A slow household/individual/project search makes the on-device bundle path slow; the pre-gen path exists precisely to take that off the request hot path.
* **CLAUDE.md lists `health-services-common` as 1.1.5-SNAPSHOT, but this service's `pom.xml` is on `1.1.3-SNAPSHOT`** — confirm the intended version before release.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.digit.org/health/design/architecture/low-level-design/services/health-services/referral-management.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
