Troubleshooting: RESIDENCY_* data-residency errors
The data-residency pin lives on the compliance surface:PUT /api/v1/compliance/data-residency pins (or re-pins) your
organization’s region,
POST /api/v1/compliance/data-residency/lock freezes it,
POST /api/v1/compliance/data-residency/unlock frees it again for a
governed region change, and
GET /api/v1/compliance/data-residency reads the posture. Every write
needs an owner or admin API key.
Four refusal codes gate that surface:
The full concept (advisory vs. enforced pins, boundaries, the lock
mechanism) is Data placement and residency;
this page is the recovery runbook for the refusals.
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; the decision and its documentation are yours.
Symptom decoder — where each code fires
- PUT
/data-residency— the only write that can returnRESIDENCY_LOCKED(you tried to change the region on a locked pin) orRESIDENCY_REGION_UNAVAILABLE(you tried to enforce a region the platform has not provisioned). - POST
/data-residency/lock— returnsRESIDENCY_NOT_ENFORCEDwhen the config is missing entirely or pinned without enforcement. - POST
/data-residency/unlock— returnsRESIDENCY_NOT_FOUNDwhen the config is missing (the one 404 the surface uses); aPUTwith a changed region succeeds once the pin is unlocked. - GET
/data-residency— read-only; returns 200 withdata.config: nullwhen nothing is pinned, plus the region catalog, resolved bucket, API-region status, and per-plane residency matrix. Use it to read state between every step below — do not assume a step advanced.
RESIDENCY_NOT_FOUND (404) — nothing pinned yet
This fires only on POST /data-residency/unlock. It means your
organization has no residency config at all — there is nothing to
unlock. The fix is to pin a config with PUT first.
Read the state to confirm:
"config": null — the GET
never 404s on the absence. A typical reset trigger is a pasted recipe
that runs the unlock before the pin sequence:
PUT from your sequence is
the only cause.
RESIDENCY_NOT_ENFORCED (409) — pinned but not enforced
POST /data-residency/lock refuses with this code when there is no
enforced pin to lock. Enforcement is what turns a recorded region
preference into an actual region-scoped storage boundary — pinning
alone does nothing at rest. If your config exists with
enforced: false (an advisory pin), it reads as not-enforced; if there
is no config at all, that reads the same way.
The response names the remediation:
GET) and check data.config.enforced. If it is
false — an advisory pin — re-issue the PUT with enforced: true
against the same region, provided that region’s
data.regions[].availability is available:
VALIDATION_ERROR on the PUT means the body shape failed (region
outside the catalog, or a wrong field type) — that is the deterministic
validation family, not one of the four codes here. And a lock call on an
already-locked pin returns 200 idempotently: re-read locked on the GET
instead of assuming a successful response moved the state.
RESIDENCY_REGION_UNAVAILABLE (409) — boundary not provisioned
An enforcing PUT (enforced: true in the request, or carried forward
from the existing config) refuses with 409 when the requested region’s
storage boundary is not provisioned on the platform — meaning not
available in the GET region catalog.
The catalog is deliberately honest about procurement posture: the EU
boundary ships as available from the platform itself; every other
region reads preview until its in-region storage is stood up, at
which point it graduates to available. preview does not mean
disabled — it means the platform refuses to claim an at-rest guarantee
it cannot yet keep.
Advisory pinning still works: a PUT with enforced: false (or simply
omitting enforced, when nothing carries it forward) against a preview
region succeeds and records your intent so you can register where
your data needs to land. That is by design — enforcement is the one
transition this code gates.
List the regions before you enforce:
data.regions[] entry carries region, label, jurisdictions,
frameworks, and availability — enforce only an entry that reads
available.
preview, open a Devotel support ticket
and reference this 409 — region graduation is a platform-provisioning
decision recorded per request, not something you toggle on your org.
RESIDENCY_LOCKED (409) — frozen by design
Once POST /data-residency/lock has run, region drift is forbidden:
any PUT that names a different region than the locked pin refuses
with 409 RESIDENCY_LOCKED. Same-region updates to justification or
enforced still succeed on a locked pin — the guard fires only when the
region would actually move.
The freeze is deliberate. Bit-by-bit data already written under a pinned
region must not be silently re-homed by a later PUT (GDPR Art. 44). The
only move while locked is unlock — a guarded owner/admin change that
itself lands in your audit log.
Read the lock state:
data.config.locked is true and data.config.region names the frozen
boundary. To perform a governed region migration, unlock first, then
re-pin:
locked on the GET so the sequence ends
in the state you want.
If your organization cannot reach an owner/admin to run the unlock
(key rotation, offboarded owner, locked role assignment), escalate to
Devotel support with the lock bundle below plus proof of ownership —
the platform unlocks nothing outside the API and will flag a
support-side unlock to a re-pin as suspicious in the org audit view.
Worked cURL per code
Every command below uses the same header; only the verb changes.What not to do
- Do not retry a 409 in a loop — all four codes are deterministic state refusals. A retry that does not change the state reads the same refusal forever.
- Do not conflate codes —
RESIDENCY_NOT_ENFORCEDon lock andRESIDENCY_REGION_UNAVAILABLEon an enforcing PUT are siblings of the same gate;RESIDENCY_NOT_FOUND(404, unlock-only) andRESIDENCY_LOCKED(409, PUT-only) are unrelated. Check which verb the refusal came from before assuming the cause. - Do not treat an advisory pin as residency — a pinned-but-not-
enforced (
enforced: false) region records intent only; the region-scoped bucket assignment and thestoredAndProcessedInRegionverdict on the messaging read both require enforcement. - Do not write a support ticket before reading the GET — the GET names the Config, Region catalog, and lock state precisely; a ticket opened before that read is harder than one pasted with it.
What to send support
Open a ticket (or post into your shared Devotel channel) with the bundle below. It is enough to reconstruct the lock state server-side without exposing your data-plane content:- The refusal code and HTTP status (
RESIDENCY_*+ 409 or 404). meta.request_idfrom the error envelope.- Your organization slug (e.g.
org_XXXX) — never the raw tenant id unless support asks. - The target region you attempted (and the region currently locked, if the 409 was on a PUT).
- A copy of
GET /api/v1/compliance/data-residencyso theconfig, region catalog, and lock state travel with the ticket. - For a
RESIDENCY_REGION_UNAVAILABLEconsult: your regulatory driver (GDPR/LGPD sector note) and which channel’s plane you need in — region graduation needs per-plane provisioning, not just the flag.
See also
- Data placement and residency — the WHERE (tenant isolation) vs. WHERE-geographically (the pin) vs. region-of-compute concept.
- Messaging data residency — the
messaging projection of the pin and the composed
storedAndProcessedInRegionverdict. - Data residency overview — which surface answers which channel’s residency question.
- Error Code Reference — the remaining codes the API envelopes carry.