> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: pending-compliance and erasure gates

> Resolve the erasure-ownership 409/422 gates — CONTACT_ERASURE_PENDING on sends and ERASURE_COOLING_OFF_ACTIVE on a duplicate filing — by waiting out the cooling-off window or cancelling the active request, then retrying.

# Troubleshooting: pending-compliance and erasure gates

A GDPR Article-17 erasure request **owns the contact** for the life of the
request: from the moment you file it until the scheduled hard-delete runs
(or you cancel), a small class of pre-write checks refuse operations against
that contact. The refusal is deliberate — your own erasure request is
holding the contact, not a platform verdict you need to appeal.

Two error codes share that shape:

* `CONTACT_ERASURE_PENDING` — an outbound **send** to the contact is
  refused while an erasure request for them is pending or executing.
* `ERASURE_COOLING_OFF_ACTIVE` — a **new erasure filing** is refused
  because a pending or executing request already exists for the contact.

Both resolve one of two ways: **wait** for the scheduled hard-delete, or
**cancel** the active request while it is still cancellable, then retry.

***

## Cause table

| Endpoint that fired it                    | Code                                       | HTTP | What the gate holds                                                                                                                                 |
| ----------------------------------------- | ------------------------------------------ | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /messages` (direct API send)        | `CONTACT_ERASURE_PENDING`                  | 422  | Sends to the contact are blocked for the life of the erasure request; the response carries `details.contact_id`                                     |
| Campaign / bulk send                      | `CONTACT_ERASURE_PENDING` (campaign-level) | —    | The contacted recipient is marked `skipped` with `reason: "erasure_pending"` instead of failing the batch                                           |
| `POST /contacts/:id/gdpr/erasure-request` | `ERASURE_COOLING_OFF_ACTIVE`               | 409  | Exactly one erasure request per contact may be active at a time; the existing request is pending inside its cooling-off window or already executing |

The **cooling-off window** is per-tenant configurable and defaults to
**7 days**. While a request is pending inside it:

* erasure-request filings, message sends, and other contact writes to that
  contact are refused,
* the request stays **cancellable** — once the window lapses, the scheduler
  hard-deletes the contact and writes a per-resource audit chain, and the
  request is no longer cancellable.

Read a contact's active request and its window with
`GET /contacts/:id/gdpr/erasure-requests` — each row carries
`status` (`pending`, `executing`, `cancelled`, `completed`) and
`cooling_off_ends_at`.

***

## `CONTACT_ERASURE_PENDING` (422)

The send pipeline checks the recipient before dispatch. A contact with a
pending or executing erasure request fails that check, and the send never
reaches the provider — for a campaign the recipient row is marked
`skipped` with `reason: "erasure_pending"`; for a direct API send the
error arrives in the standard envelope:

```json theme={null}
{
  "error": {
    "code": "CONTACT_ERASURE_PENDING",
    "message": "Contact has a pending GDPR erasure request. Sends are blocked during the 7-day cooling-off window. Cancel the request from the contact's profile if this send is required.",
    "status": 422,
    "details": { "contact_id": "contact_…", "channel": "sms" }
  },
  "meta": {
    "request_id": "req_…",
    "timestamp": "2026-09-04T12:00:00.000Z"
  }
}
```

**Fix.**

1. **Cancel the erasure** if the send is required — while the request is
   still `pending`, call
   `POST /contacts/:id/gdpr/erasure-request/:requestId/cancel` (an optional
   `reason` is recorded on the audit trail). Sends to the contact unblock
   immediately.
2. **Or wait for the window.** When `cooling_off_ends_at` lapses, the
   scheduler hard-deletes the contact and writes the per-resource audit
   chain. After that, a send to the same address behaves as a send to an
   unregistered recipient.
3. **Do not retry-loop the 422.** The refusal is deterministic for the
   life of the request — it clears only on a cancellation or on the
   hard-delete.

**Retry-safe:** yes, once resolved — the gate rejects before any send is
dispatched, so nothing partial exists on the platform side to clean up.
Retry the identical send after a cancel, or after the contact has been
deleted.

***

## `ERASURE_COOLING_OFF_ACTIVE` (409)

`POST /contacts/:id/gdpr/erasure-request` enforces *one active erasure
request per contact*. A second filing lands on that uniqueness rule before
anything is written, and the endpoint returns the 409 — the existing
request is either still cancellable (`pending`) or already `executing`.

**Fix.**

1. List the contact's requests to find the active one:
   `GET /contacts/:id/gdpr/erasure-requests` — the active row is the one
   with `status` `pending` or `executing`.
2. If the active request is `pending`, cancel it with
   `POST /contacts/:id/gdpr/erasure-request/:requestId/cancel`, then re-file
   your new request — a corrected filing always goes through the cancel.
3. If the active request is `executing` (or has completed), cancel is no
   longer available; the scheduled hard-delete is imminent or done — treat
   the conflict as already resolved and file the new request only if data
   for the contact still exists.

**Retry-safe:** an immediate retry returns the same 409 until the active
request resolves; after a cancel or after the hard-delete, the same POST
is accepted. No row was written on the 409, so there is nothing to roll
back.

***

## `DSAR_NOT_CANCELLABLE` is a different, terminal gate

`DSAR_NOT_CANCELLABLE` (409) is the terminal state of the *cancel* path —
it means the request cannot be withdrawn because it does not exist or has
already completed, failed, expired, or been cancelled. It is not an
erasure-ownership gate like the two codes above; read the request status
first with `GET /compliance/dsar/:id` and treat the 409 as the answer, not
a retryable error. Its full recovery matrix sits on
[Troubleshooting: DSAR export, decrypt, and cancel failures](/troubleshooting/dsar-export-failures).

***

## Recovery summary

| Code                               | Resolution                                                       | Then retry?                                                                                 |
| ---------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `CONTACT_ERASURE_PENDING` (422)    | Cancel the pending erasure, or wait for `cooling_off_ends_at`    | Yes — the send was never dispatched                                                         |
| `ERASURE_COOLING_OFF_ACTIVE` (409) | Cancel the active (pending) request, or wait for the hard-delete | Yes — no row was written                                                                    |
| `DSAR_NOT_CANCELLABLE` (409)       | Terminal — poll the request status; do not re-cancel             | No — see [DSAR export, decrypt, and cancel failures](/troubleshooting/dsar-export-failures) |

<Note>
  Every control on this page is a **tenant-owned** path: Orbit runs the
  erasure lifecycle and the pre-write gates; whether you cancel a request
  or wait for the cooling-off window — and how you fulfil the data
  subject's request — is your call. Nothing here is a claim of GDPR, CCPA,
  or other compliance.
</Note>

***

## Related references

* [Data Subject Access Requests](/compliance/dsar) — the DSAR lifecycle the
  gates above belong to.
* [Troubleshooting: DSAR export, decrypt, and cancel failures](/troubleshooting/dsar-export-failures) —
  the export/decrypt pipeline and the terminal `DSAR_NOT_CANCELLABLE`
  gate.
* [Error codes](/reference/error-codes) — the registry entries for
  `CONTACT_ERASURE_PENDING`, `ERASURE_COOLING_OFF_ACTIVE`, and
  `DSAR_NOT_CANCELLABLE`.
