Skip to main content

Pin, lock, and run a governed data-residency migration

The org-wide data-residency control pins PII, audit logs, and recordings to a geographic boundary, reports the per-plane posture honestly, and freezes the pin behind a deliberate lock. This page walks the full sequence — pin → verify → lock → (governed unlock) → migrate → re-lock — with the four refusal codes you can hit along the way and the cURL to run at each step.
Data residency is a tenant-owned control: you pin the region, you enforce it, you lock it, and you document it. Orbit records and reports the posture and refuses unsafe mutations; the decision and its documentation are yours. Nothing on this page is legal advice — run it past qualified counsel before you certify a posture.

The org-wide pin surface, and what it does not cover

The control surface lives under /api/v1/compliance/data-residency: Every write needs an owner or admin API key, and every change lands in your audit log. This org-wide pin is one of two residency surfaces. It covers the org-wide data planes (PII, audit logs, compliance documents, and the messaging / CDP planes projected below). It does not move the voice region — call recordings, voicemail, and live media follow the workspace voice-region pin documented in Voice data residency. The Data residency overview maps which surface answers which channel’s question; set the voice-region pin and the org-wide pin deliberately, never assume one covers the other.

Step order: read → pin → verify → lock

The sequence is four deliberate steps. Each one is verifiable from the GET before you move on — do not assume a step landed just because the call returned 200.
1

Read the current config

Call GET /api/v1/compliance/data-residency. A fresh org returns 200 with data.config: null, the region catalog, a resolved bucket, an API-region status block, and the per-plane data-plane matrix. Check the catalog before you pin — a region’s data.regions[].availability must read available before the pin can be enforced; a preview region pin is advisory-only by design.
2

Pin the region (PUT)

Issue a PUT with { "region": "<code>", "enforced": true, "justification": "…" }. A first-time pin returns 201, an update returns 200, and the response body is the full GET view (plus the audit-logged write). Enforcement is what turns the recorded prefer- ence into the region-scoped storage boundary, so an advisory pin (enforced: false) registers intent but never becomes “resident.”
3

Verify with a GET before you move on

Re-read GET /api/v1/compliance/data-residency and inspect data.config.region, data.config.enforced, and the per-plane matrix in data.data_planes. Read it back before you lock or migrate — an accidental enforced: false body, or a preview-region pin you then try to enforce, returns RESIDENCY_REGION_UNAVAILABLE on the enforcing PUT.
4

Lock the pin (POST …/lock)

Issue POST /api/v1/compliance/data-residency/lock with an optional { "notes": "…" } annotation. The lock refuses with RESIDENCY_NOT_ENFORCED (409) unless an enforced pin exists — lock-as-a-guard only makes sense on a boundary that actually scopes storage. Once locked, same-region updates to justification or enforced still succeed; only a region change is refused.

The four refusal codes, walked in the sequence

The surface uses exactly four refusal codes. Each one fires on a specific verb, so check the verb before you diagnose. The dedicated Troubleshooting: RESIDENCY_* errors page expands the recovery for each; the walk below lines them up with the sequence you actually run.
A fixation on “region must be enforced before it matters” drives three of the four codes: RESIDENCY_NOT_ENFORCED guards the lock, RESIDENCY_REGION_UNAVAILABLE guards the enforcing PUT, and the storedAndProcessedInRegion projection on the plane reads treats an advisory pin as intent-only. An advisory pin is a useful way to register a region request before its boundary is graduated to GA — it just is not residency. None of the refusals is a transient error; a retry that does not change the state reads the same refusal forever.

When the per-plane reads need explicit confirmation

The org-wide matrix is one object-wide view — but two data planes also expose their own projection, because an integrating buyer reads residency where it integrates rather than on a generic settings page. Read these before you certify a posture to a buyer or a supervisory authority. Confirm the messaging read after onboarding a regulated SMS buyer and the CDP read before you certify an ingested-profile posture. Both project the same singleton config the compliance surface manages — they are reads, not per-plane knobs, and they answer with the same enforced pin the GET /compliance/data-residency returns. The one common mistake is treating the messaging/CDP storedAndProcessedInRegion projection as a write answer: it reports the current posture, and only PUT /compliance/data-residency changes it.

The governed migration sequence

Region migration is a deliberate governance event, not an edit. Treat it with a two-officer mindset: the officer who unlocks should be the one who re-locks at the end, and the notes and justification bodies are the written record of why.
  1. Announce and document. Record the reason for the migration, the target region, and the sign-off (customer contract, regulatory driver, or procurement change) before any API call. The unlock and re-pin will land in the audit log on both sides; the officer’s written justification is the only narrative between them.
  2. Unlock the pin. POST /compliance/data-residency/unlock with a { "notes": "…" } body naming the reason. The call records the change only — it performs no data movement and wires no carrier. That distinction is load-bearing for audit: the surface records policy, and the audit log reads as a governance ledger.
  3. Re-pin the target. PUT the new region with enforced: true and a justification that names the migration. Same-region enforcement (enforced: true → enforced: true) is also allowed on a locked pin if you ever need to refresh the justification without unlocking.
  4. Run the data-plane migration out-of-band. Moving bytes between regions is a service-operations step, not part of this surface — contact Devotel to schedule it. Until the bytes move, the planes that do not follow the pin report the gap honestly in the matrix (residentRegion stays on the actual store region and the plane’s note says so).
  5. Re-lock. POST /compliance/data-residency/lock with notes referencing the migration ticket. Re-read the GET and confirm data.config.locked: true and data.config.region now name the new boundary — the sequence ends in the state you want, not the state the unlock left behind.
Each step returns the full GET view so you can verify without a second round trip; the write verbs land in the audit log on both sides of the unlock/re-pin pair, so there is no “forgot to re-lock” silent state.

Worked cURL for each step

Set the base and header once; only the verb changes.
Read the current config — a fresh org answers 200 with config: null:
Pin the EU region with enforcement — 201 on first pin, 200 on update:
Verify what you actually wrote (check data.config.region, data.config.enforced, and the plane rows in data.data_planes):
Lock the enforced pin — refuses with 409 RESIDENCY_NOT_ENFORCED if the config was not enforced:
Attempting a region change against the locked pin is refused with 409 RESIDENCY_LOCKED — same-region updates to justification are the exception and succeed. For a governed migration, unlock with a reason, re-pin the new region, then re-lock:
Read the per-plane projections next to the surfaces they govern:
A 409 with a non-region-change (same-region enforcement, lock repeat) replay still returns 200 idempotently — the refusals above fire only on a state the surface deliberately protects, never on noise.

See also

  • Data residency overview — which surface answers which channel’s residency question, including the messaging subprocessor answer.
  • Voice data residency — the workspace voice-region pin that covers recordings, voicemail, and live media. Set beside the org-wide pin; never instead of it.
  • Messaging data residency — the concept behind GET /messaging/data-residency and the composed storedAndProcessedInRegion verdict.
  • Troubleshooting: RESIDENCY_* errors — the reference for each refusal code and what to bundle for support.
  • Data placement and residency — the WHERE-isolation vs. WHERE-geographic vs. region-of-compute concept this pin composes with.