> ## 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: proof-of-deletion certificate errors

> Diagnose every response of GET /compliance/dsar/erasure-requests/{id}/certificate: 404 NOT_FOUND, 403 INSUFFICIENT_PERMISSIONS, 409 ERASURE_NOT_EXECUTED per request status, and a 200 whose fields read as unexpected; verify the certificate_id digest against the executed erasure; and reach subject-bound data while the request is still pending.

# Troubleshooting: proof-of-deletion certificate errors on `GET /compliance/dsar/erasure-requests/{id}/certificate`

The certificate endpoint issues the signed **proof-of-deletion
certificate** for a completed Article-17 erasure: the receipt a data
subject or supervisory authority accepts as evidence that the right
to erasure was fulfilled. This page covers every response the
endpoint can return, how to read a 200 that looks wrong, how to
verify the certificate you hold, and what to do while the erasure is
still in flight.

The normative reference is
[Data Subject Access Requests](/compliance/dsar); the step-by-step
operational sequence lives in the
[Proof-of-Deletion Certificate Runbook](/guides/compliance-dsar-erasure-certificate-runbook).

## What the certificate is, and who asks for it

Article 17 obliges you to erase personal data when the right
applies; Article 12 obliges you to demonstrate to the data subject
that you did. The certificate is that demonstration in portable
form: a dated, signed, per-subsystem statement of what was purged,
when, under which request, and against which tamper-evident audit
anchor. A data subject asks for it to close their request; a
supervisory authority or auditor asks for it as GDPR Art. 17
evidence in an investigation or proceeding.

Orbit signs the artifact's integrity. Whether the certificate
discharges your legal obligation in a given proceeding is a
determination for your counsel, consistent with Orbit's
tenant-owned compliance posture.

## The endpoint, end to end

`GET /compliance/dsar/erasure-requests/{id}/certificate` requires an
owner or admin credential. On a 200 it derives the certificate on
demand from the immutable executed request row and its audit-chain
anchor, self-signs it with a detached HMAC-SHA256 tag, and returns
`{ "certificate": ..., "signature": ... }`. Nothing is persisted:
re-issuing at any time reproduces the same `certificate_id` for the
same execution.

The failing states, in the order the endpoint checks them:

* **401 `UNAUTHORIZED`** — no valid credential on the request. Attach
  your API key and retry.
* **403 `INSUFFICIENT_PERMISSIONS`** — the credential is valid but
  its role is below owner/admin; the message reads
  `This action requires one of: owner, admin`. Switch to an owner-
  or admin-scoped credential. This is a permissions posture, not a
  defect.
* **404 `NOT_FOUND`** — no erasure request with this id exists in
  your tenant. Certificates are tenant-scoped: a request that
  belongs to another organization is indistinguishable from one
  that never existed. Re-copy the id from
  `GET /compliance/dsar/erasure-requests`.
* **409 `ERASURE_NOT_EXECUTED`** — the request exists but is not
  `executed`. The message names the live status (`pending`,
  `executing`, `cancelled`, or `failed`). Only a completed erasure
  can be certified; work the grid below.

The lifecycle is `pending` → `executing` → `executed`, with terminal
branches `cancelled` and `failed`. The executor ticks every five
minutes; once the cooling-off window lapses, a healthy erasure moves
from `pending` to `executed` in minutes, not hours.

## Error grid

| Response | What it means | What to do |
| - | - | - |
| `200` with the certificate | The erasure is `executed` and the artifact is issued. | Verify before presenting (below). |
| `404 NOT_FOUND` | The id matches no erasure request in your tenant. | Re-copy the id from the erasure list; confirm you call the tenant and environment the request was filed against. |
| `401 UNAUTHORIZED` | Missing or invalid credential. | Attach a valid API key. |
| `403 INSUFFICIENT_PERMISSIONS` | The credential's role is below owner/admin; member keys are rejected before any compliance data is read. | Use an owner- or admin-scoped credential. |
| `409 ERASURE_NOT_EXECUTED`, message ends `currently pending` | The request was filed but has not started: it is inside the cooling-off window, or the parent DSAR awaits its identity-verification decision. | Wait for cooling-off to lapse (or record the verification decision on the parent request), then poll the list until the row reads `executed`. |
| `409 ERASURE_NOT_EXECUTED`, message ends `currently executing` | The executor has claimed the request and is running the delete transaction. | Poll `GET /compliance/dsar/erasure-requests?status=executing` until it flips; the executor ticks every five minutes. |
| `409 ERASURE_NOT_EXECUTED`, message ends `currently failed` | Execution faulted and retries are exhausted, or the delete committed but the audit anchor could not be written. A `failed` request can never be certified. | Read `failure_reason` on the list row and act inside your statutory clock: the 30-day GDPR window keeps running while the row waits. |
| `409 ERASURE_NOT_EXECUTED`, message ends `currently cancelled` | The request was withdrawn during cooling-off. Nothing was erased, so nothing can be certified. | No certificate exists for this request. If the subject renews the request, file a new DSAR. |

### A 200 with an unexpected `status`-shaped field

The certificate carries no lifecycle `status` field. A 200 means the
backing row was `executed` at read time, and the fields that read as
surprises are honest flags, not failures:

* `records_erased.by_resource` is `null` for a subsystem whose table
  does not exist on your tenant: nothing existed there to purge. A
  number, including 0, is a confirmed count.
* `records_erased.total` is `null` when every purge ran but no count
  could be confirmed. It does not mean nothing was deleted.
* `destination_propagation.total: 0` is a signed, honest "no external
  destinations were notified" (internal-only erasure), not a missing
  section.
* `destination_propagation.failed` or `skipped` entries mean a
  downstream destination rejected or was not configured for the
  fan-out. Re-run propagation, then issue again: the re-issue keeps
  the same `certificate_id` and folds the updated trail under a
  fresh `issued_at` and signature.
* `attempt_count` above 1 means an earlier attempt hit a transient
  fault and the executor retried. The committed result is still
  all-or-nothing.

If you genuinely expected a different request, match on
`erasure_request_id` and `data_subject_contact_id` before trusting
the file you opened: certificates are derived per request, and a
stale id in your script is the usual cause of a "wrong" 200.

## Verifying the certificate against the executed erasure

Two independent checks, in increasing order of strength:

**Audit-chain cross-reference (yours to run).** The certificate's
`audit_log_id` names a row in your organization's audit chain with
action `gdpr.erasure_executed`. Its `resource_counts` must equal
`records_erased.by_resource`, and its timestamp must equal
`executed_at`. Every issuance also appends a
`compliance.dsar_erasure_certificate_issued` row naming the
`certificate_id`, so your audit export shows who issued which
certificate and when. A certificate whose anchor is missing or
disagrees should be treated as altered or mis-cited. The
`certificate_id` itself is deterministic, derived from the request
id and `executed_at`, so re-issuing the same executed request always
yields the same id: the digest you filed stays comparable against
any later re-issue.

**Signature recompute (held by the signing-key holder).** The tag is
a self-signature by Devotel Orbit over the canonicalised body; the
recipe is published for transparency:

1. Canonicalise the `certificate` object with RFC 8785 (JCS) style
   rules: object keys sorted ascending, no insignificant
   whitespace.
2. Compute HMAC-SHA256 over the string
   `orbit.dsar.erasure-certificate.v1.<canonical-json>`.
3. Base64url-encode and compare with `signature.value` using a
   timing-safe equality check.

The domain-separation context
(`orbit.dsar.erasure-certificate.v1`) is mixed into every tag, and
this is the deterministic-build contract the certificate service
documents: the signed byte sequence depends only on the immutable
executed row, the audit anchor, and `issued_at`, never on read
timing or row and key order. A mismatched or malformed tag verifies
false; verification never fails open.

## Reading subject-bound data while the request is pending

The certificate exists only after completion. While an erasure is
`pending` or `executing`, the subject-bound data still exists, and
the path to read it is the Article-15 access export, not the
certificate:

* `GET /compliance/dsar/{id}/decrypted` returns the plaintext access
  bundle for a completed, unexpired access request, decrypted for
  the verified controller (owner or admin) and audit-logged.
* It applies to access-class requests only. An Article-17 erasure
  produces no access export and returns
  `409 DSAR_NOT_ACCESS_REQUEST`: the two request types close
  different obligations and their artifacts do not substitute for
  each other.
* If the pending request is a delete and you need to inspect what
  will be destroyed, read a completed access export for the same
  subject where one exists; otherwise wait for the erasure to
  finalize and present the certificate.

## Your control, your posture

Issuing the certificate is a tenant-triggered control: Orbit makes
the artifact available on every executed erasure, and you decide
when to issue it, whom to hand it to, and how long it lives in your
privacy register. The platform does not auto-issue, auto-publish,
or auto-expire certificates on your behalf, and no regulator
interaction is driven by Orbit. Whether a presented certificate
discharges your obligation is your counsel's determination, not a
platform default.

## Worked example

Issue the certificate and keep the artifact verbatim:

```bash theme={null}
curl -s -X GET "https://api.orbit.devotel.io/api/v1/compliance/dsar/erasure-requests/gdpr_4e2a91c7/certificate" \
  -H "X-API-Key: dv_live_sk_..." | tee certificate.json
```

A 200 returns the `certificate` + `signature` pair (the
[runbook](/guides/compliance-dsar-erasure-certificate-runbook) shows
the full body). If instead you receive:

```json theme={null}
{
  "error": {
    "code": "ERASURE_NOT_EXECUTED",
    "message": "A proof-of-deletion certificate can only be issued once the erasure has completed. This request is currently executing.",
    "status": 409
  },
  "meta": {
    "request_id": "req_01j9zk4w",
    "timestamp": "2026-10-16T09:41:00.000Z"
  }
}
```

the request is mid-execution: poll until it reads `executed`, then
re-run the same call.

```bash theme={null}
curl -s -X GET "https://api.orbit.devotel.io/api/v1/compliance/dsar/erasure-requests?status=executed&limit=50" \
  -H "X-API-Key: dv_live_sk_..."
```

## See also

* [Data Subject Access Requests (DSAR)](/compliance/dsar) — the
  normative reference for intake, verification, erasure, and the
  certificate.
* [Proof-of-Deletion Certificate Runbook](/guides/compliance-dsar-erasure-certificate-runbook) —
  the operational sequence, including regulator presentation and
  retention of the certificate itself.
* [Pending compliance and erasure gates](/troubleshooting/pending-compliance-and-erasure-gates) —
  the send-side gates an in-flight erasure raises elsewhere.
* [Error code reference](/reference/error-codes) — the full
  envelope and where `ERASURE_NOT_EXECUTED` sits in the catalog.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.