> For the complete documentation index, see [llms.txt](https://documentation.opencrvs.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://documentation.opencrvs.org/v2.1/technical/guides/configuration/administrative-hierarchy/updates-and-versioning.md).

# Updates & versioning

**TL;DR**

1. Every location and administrative area keeps a `versions` history — an ordered list of `{ versionId, name, externalId, status, effectiveFrom }` entries.
2. Identity (`id`, parent/administrative area, location type) is fixed at creation and never changes. Only name, code and status are versioned.
3. Each record and certificate resolves the version that was in effect at the record's own date (its anchor), not the current name.
4. Updates only append a new version — nothing is edited or removed in place. A future-dated version can be withdrawn before it takes effect.
5. Re-parenting a location or administrative area is not supported. Moving one to a different parent means inactivating the old one and creating a new one.
6. Writing locations or administrative areas after go-live requires the `location.edit` scope.

### Why versioning exists

Names, codes and active/inactive status of offices, facilities and administrative areas change over time — a health facility closes, a district office is renamed, a village is merged into another. Historical records must keep showing the name and status that applied when they were captured, while current forms and searches must reflect what's true today. A single mutable `name`/`status` field cannot do both at once, so each location and administrative area instead keeps a small history of versions.

### The `versions` array

Each element of `versions` has:

* `versionId` — a UUID identifying the version itself.
* `effectiveFrom` — a plain date (`YYYY-MM-DD`) from which this version applies.
* `name`
* `externalId` — optional, used for point-in-time code uniqueness (see below).
* `status` — `active` or `inactive`.

Versions are sorted ascending by `effectiveFrom`. Unless an explicit `effectiveFrom` is given at creation, a location's first version defaults to `0001-01-01` — a beginning-of-time sentinel — so that every location always resolves to *some* version, however far back a record's date reaches.

Everything else about a location or administrative area — its `id`, its parent (`administrativeAreaId` / `parentId`), and a location's `locationType` — is set once at creation and cannot change. Renames and status changes only ever append a new version; nothing is edited or deleted in place.

### Resolving a version — the anchor date

Because a location's name and status vary over time, any UI or API that renders one needs an **anchor date**: the date the name/status should be resolved at. Which anchor applies depends on the surface:

* Record views, review screens and a certificate's declaration fields use the **record anchor** — the record's own date of event, or `createdAt` when the configured date-of-event field is empty (e.g. a partial notification), the same fallback convention used elsewhere for date-of-event resolution.
* Form selectors resolve against **today** unless the field opts in with [`anchorToDateOfEvent`](/v2.1/technical/guides/configuration/administrative-hierarchy/how-to-limit-location-and-administrative-area-options-in-event-declaration.md#limiting-options-by-version-status-and-date).

"Today" isn't the same clock everywhere: the events service resolves it as the UTC calendar date, while a client anchoring to today uses the browser's local calendar date. The two can differ by a day for a user near midnight UTC.

Resolution takes the version with the greatest `effectiveFrom` that is still on or before the anchor, falling back to the earliest version when the anchor precedes all of them. In practice this means:

* A record captured before a rename shows the old name in the record view and on the certificate, even after the location has since been renamed.
* A record captured before a location is deactivated still shows it as it was, even though fields configured with [`activeOnly`](/v2.1/technical/guides/configuration/administrative-hierarchy/how-to-limit-location-and-administrative-area-options-in-event-declaration.md#limiting-options-by-version-status-and-date) no longer offer it in new declarations.
* A location whose first version's `effectiveFrom` has not yet arrived is likewise hidden from fields configured with `activeOnly` — a location scheduled for the future stays hidden until then. Fields that don't set the flag keep listing every location regardless of version status.

The wire format for a location or administrative area still carries flat `name`/`status`/`externalId` fields (resolved as of today) alongside `versions` — some consumers read these directly; the reference country config's analytics sync, for instance, writes the flat fields straight into its own database rather than resolving per-record history. The registrar-facing client is stricter: its cached location map strips the flat fields entirely, so no client code can read a location's current name without going through `resolveVersion`/`resolvePath` at the anchor. That cache is kept for up to a day (a 24-hour stale time), so a location created or renamed elsewhere can take up to a day to reach a given device.

{% hint style="warning" %}
**Changed in v2.1:** the `validUntil` field has been removed from the `Location` and `AdministrativeArea` wire models. Read the top-level `status` for whether an entity is active today, or derive when a version stopped applying from the `effectiveFrom` of the next element in `versions`.
{% endhint %}

### Withdrawing a pending change

A version that has not taken effect yet (its `effectiveFrom` is strictly in the future) can be withdrawn — this removes it from the history outright, as if it had never been scheduled. Once a version's `effectiveFrom` is today or earlier it counts as already in effect and can no longer be withdrawn; a further version must be appended instead to change course. A version can only be withdrawn if it is not the only one a location or administrative area has — a row must always keep at least one version, so withdrawing the last remaining one is rejected instead.

### Transfers: no re-parenting

A location's administrative area, and an administrative area's parent, are part of its immutable identity — they cannot be changed by appending a version. Moving a health facility or office to a different administrative area (or moving an administrative area under a different parent) is therefore done as two separate, independent operations:

1. Inactivate the existing location/area (append an `inactive` version).
2. Create a new location/area under the correct parent.

Both operations are idempotent and safe to retry individually; they are not combined into a single atomic call.

Nothing in this recipe carries over automatically. Say District Office A closes and District Office B opens as its replacement, same jurisdiction:

* **No cascade.** Anything that pointed at District Office A — locations assigned to it, or sub-areas beneath it, if it's an administrative area — keeps pointing at District Office A. None of it is moved or relinked to District Office B.
* **No data migration.** Records already registered at District Office A keep referencing District Office A's UUID forever. They do not move to District Office B, even though B is meant to replace A.
* **`externalId` reuse is order-sensitive.** If both offices share the same reference code, inactivate District Office A first, then create District Office B. Create B before A is inactive (or with an earlier `effectiveFrom` than A's inactivation) and the request is rejected — the code is still active at A.

### Point-in-time code uniqueness

Where an `externalId` (an external reference code) is set, it must be unique among **active** holders at any given point in time — not across all of history. A new location can reuse a code that a different, since-inactivated location used to hold, but it cannot take over a code that's still active (or scheduled to become active) elsewhere from the same date onward. Locations and administrative areas enforce this independently, each only against its own kind — a location and an administrative area may hold the same `externalId` at the same time without conflict.

### Where history comes from

Existing rows didn't always have a `versions` history. The upgrade to this model backfilled every location and administrative area with a `0001-01-01` active version built from its old flat name and code, plus — for a row that had a legacy `valid_until` date set — a further `inactive` version dated at that cutover. The initial hierarchy seed (see [How to populate administrative hierarchy](/v2.1/technical/guides/configuration/administrative-hierarchy/how-to-populate-administrative-hierarchy.md)) can also supply a pre-built `versions` array directly; re-running the seed against the same row **replaces** its stored history wholesale rather than merging into it, so a caller that means to keep a history it built has to resend it every time.

### Audit trail

Every create, update and withdraw is written to the audit log. An idempotent replay — the request was already applied before — is deliberately not re-audited, since nothing new happened.

### Configuring form selectors

`LOCATION`, `ADMINISTRATIVE_AREA` and `ADDRESS` fields have their own `activeOnly` / `anchorToDateOfEvent` configuration options for controlling which versions a form selector offers — see [How to limit location and administrative area options in event declaration](/v2.1/technical/guides/configuration/administrative-hierarchy/how-to-limit-location-and-administrative-area-options-in-event-declaration.md#limiting-options-by-version-status-and-date).

### Access control

Locations and administrative areas can only be written through the write API described in [How to add new locations & administrative areas](/v2.1/technical/guides/configuration/administrative-hierarchy/how-to-add-locations-and-administrative-areas.md) and [How to update & deactivate locations & administrative areas](/v2.1/technical/guides/configuration/administrative-hierarchy/how-to-update-and-deactivate-locations-and-administrative-areas.md). Both require the `location.edit` scope, which must be assigned to a role in your country configuration before any user can manage locations this way.


---

# 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://documentation.opencrvs.org/v2.1/technical/guides/configuration/administrative-hierarchy/updates-and-versioning.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.
