> ## 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: DSAR export, decrypt, and cancel failures

> Diagnose and fix the DSAR export pipeline failures — bucket unconfigured, artifact list/download, decrypt failures, oversized bundles, and cancel rejections — using the error code each stage returns.

# Troubleshooting: DSAR export, decrypt, and cancel failures

An access-class DSAR (`request_type` `know` or `portability`) runs through
three operator-visible stages after the export worker finishes:

<Steps>
  <Step title="Cancel the request">
    `POST /compliance/dsar/{id}/cancel` withdraws a request while it is still
    `received` or `processing`.
  </Step>

  <Step title="Resolve the export location">
    `GET /compliance/dsar/{id}/decrypted` first resolves the bucket the export
    worker wrote the encrypted artifacts to.
  </Step>

  <Step title="List, download, and decrypt the artifacts">
    The same endpoint lists the encrypted files under the export's object
    prefix, downloads each one, decrypts it, and assembles the plaintext JSON
    bundle (64 MB aggregate ceiling).
  </Step>
</Steps>

Each stage fails closed with a specific `error.code`, and — like every
Orbit error — the response body still arrives in the standard envelope:

```json theme={null}
{
  "error": {
    "code": "DSAR_DECRYPT_DOWNLOAD_FAILED",
    "message": "Could not download export artifact 'tables/messages.jsonl'. Try again.",
    "status": 502,
    "details": { "stage": "gcs_download", "artifact": "tables/messages.jsonl" }
  },
  "meta": {
    "request_id": "req_…",
    "timestamp": "2026-09-04T12:00:00.000Z"
  }
}
```

Read `error.code` first, then `details.stage` — together they name exactly
which step below failed. For the happy-path lifecycle and the API shape, see
[Data Subject Access Requests](/compliance/dsar).

***

## Decision tree

Identify the code on the response, then jump to its section:

| Error code                     | HTTP | Stage that failed                                          |
| ------------------------------ | ---- | ---------------------------------------------------------- |
| `DSAR_BUCKET_UNCONFIGURED`     | 500  | Export storage resolution — no export bucket is configured |
| `DSAR_DECRYPT_LIST_FAILED`     | 502  | Listing the export's encrypted files in storage            |
| `DSAR_DECRYPT_NO_ARTIFACTS`    | 404  | The listing succeeded but contained no encrypted files     |
| `DSAR_DECRYPT_DOWNLOAD_FAILED` | 502  | Downloading one encrypted file                             |
| `DSAR_DECRYPT_FAILED`          | 500  | Decrypting one downloaded file                             |
| `DSAR_DECRYPT_TOO_LARGE`       | 413  | The decrypted bundle crossed the 64 MB aggregate ceiling   |
| `DSAR_CANCEL_FAILED`           | 500  | Persisting the cancellation of a request                   |

***

## `DSAR_BUCKET_UNCONFIGURED` (500)

The decrypted download could not run because **no export storage location is
configured** on this deployment — the bucket lookup returned nothing.

**Fix.** Have your platform operator set one of these environment variables
on the API deployment, in priority order:

1. `DEVOTEL_DSAR_BUCKET` — the dedicated DSAR export bucket (preferred).
2. `DEVOTEL_GCS_PRIVATE_BUCKET` — the general private-bucket fallback.
3. `DEVOTEL_GCS_AUDIT_BUCKET` — the last-resort fallback.

Set exactly the bucket the export worker writes to — the mismatch is as
broken as the missing variable. If you are unsure which bucket the worker
uses, look at the `export_url` on `GET /compliance/dsar/{id}` — its bucket
component is the value to configure.

**Retry-safe:** yes, once the variable is set — repeat the
`GET /compliance/dsar/{id}/decrypted` call. This is a deployment
configuration gap, not a request problem, so no new DSAR request is needed.

***

## `DSAR_DECRYPT_LIST_FAILED` (502)

The storage list call that enumerates the export's encrypted files **failed
before any artifact was read** — the storage backend was unreachable or the
deployment's service account lost its read binding on the bucket.

**Fix.**

1. Confirm the storage service is reachable from your deployment (a
   transient network or storage-control-plane blip is the common cause).
2. Confirm the deployment's service account still has storage-object read
   access on the export bucket — a revoked IAM binding or a bucket moved to
   a different project breaks listing before anything else.
3. If the bucket lives in a different cloud or account than the one the
   listing credential can see, re-point `DEVOTEL_DSAR_BUCKET` at the bucket
   the worker actually wrote to.

**Retry-safe:** yes — re-issue the same GET. A retry that still fails after
the network and binding checks needs operator attention on the deployment.

***

## `DSAR_DECRYPT_NO_ARTIFACTS` (404)

The listing succeeded but returned **no encrypted files** for this DSAR —
the export is gone from storage.

**Fix.**

1. Check `GET /compliance/dsar/{id}` first. If `export_expires_at` is in the
   past, the export was swept under its retention window — file a **new**
   access request to regenerate it.
2. If the request's `status` is not `completed`, no export was ever
   produced — check the worker outcome on the request row.
3. If the request is completed and unexpired, verify you are reading the
   same bucket the worker wrote (the `export_url`'s bucket vs.
   `DEVOTEL_DSAR_BUCKET`).

**Retry-safe:** repeating the GET returns the same 404 while nothing has
changed. The correct re-do is a new access DSAR, not a re-download.

***

## `DSAR_DECRYPT_DOWNLOAD_FAILED` (502)

One specific encrypted file **failed to download** from storage while the
bundle was being assembled — a per-object transport problem, not a
configuration problem.

**Fix.**

1. Confirm the object path still exists — the `details.artifact` field on
   the error names the file's logical name (for example
   `tables/contacts.jsonl`), and `GET /compliance/dsar/{id}` returns the
   object prefix the download ran under.
2. Confirm the service account can read objects, not just list them — a
   binding that grants listing but not downloads fails exactly here.
3. Retry the GET. Because failures surface per file, a transient transport
   fault on one artifact often succeeds on retry.

**Retry-safe:** yes — re-issue the GET. If the same artifact keeps failing
across retries, escalate with the artifact name (see the escalation section
below).

***

## `DSAR_DECRYPT_FAILED` (500)

A downloaded file **could not be decrypted** — either the platform
encryption key rotated after the export was generated (so the current key no
longer matches the envelope), or the stored envelope is corrupt.

**Fix.**

1. If your deployment rotated its data-encryption key recently, the export
   predates the rotation and can no longer be opened with the current key —
   generate a **fresh access export** for the subject and download it under
   the current key.
2. If no rotation happened and the failure names one artifact only while
   the rest decrypt, that envelope is corrupt — regenerate the export by
   filing a new access request, and escalate the corrupt artifact name to
   support (below).
3. Do **not** keep retrying the same export: a malformed or mismatched
   envelope fails deterministically on every attempt.

Escalate recurring decrypt failures on **fresh** exports — that pattern
indicates a key-management fault on the platform side, not anything you can
fix from your tenant.

***

## `DSAR_DECRYPT_TOO_LARGE` (413)

The combined decrypted export **crossed the 64 MB aggregate ceiling** the
API enforces when assembling a bundle in a single response. This protects
the API from out-of-memory failures on pathologically large subjects — it
is a deliberate limit, not a fault.

**Fix.** Fetch the export's signed `export_url` from
`GET /compliance/dsar/{id}` and open the encrypted export landing page —
its per-file links let you retrieve and decrypt each artifact individually
instead of pulling the whole bundle into one response.

**Retry-safe:** retrying the bundled endpoint returns the same 413. The
per-file path is the supported workaround; there is no request-side way to
raise the ceiling.

***

## `DSAR_CANCEL_FAILED` (500)

The cancel endpoint **failed to persist the withdrawal** — the database
update threw before it could mark the request cancelled.

**Fix.** Re-issue the
`POST /compliance/dsar/{id}/cancel` call — the operation is idempotent per
request id, and a transient fault that failed the first attempt usually
succeeds on retry.

One sibling response on the same endpoint is expected behaviour, not a
failure: if the request has already reached a terminal state
(`completed`, `failed`, `expired`, or `cancelled`) or never existed, the
endpoint returns **409 `DSAR_NOT_CANCELLABLE`** instead. Read the status
first with `GET /compliance/dsar/{id}`; only a still-`received` or
still-`processing` request can be cancelled within the close window.

**Retry-safe:** yes — cancellation is retry-safe by design. If repeated
attempts keep failing with 500 on a request that reads `received` or
`processing`, escalate with the request id (below).

***

## Retry safety summary

| Code                           | Safe to retry?          | Correct re-do                                    |
| ------------------------------ | ----------------------- | ------------------------------------------------ |
| `DSAR_BUCKET_UNCONFIGURED`     | Yes, after config lands | Same GET                                         |
| `DSAR_DECRYPT_LIST_FAILED`     | Yes                     | Same GET                                         |
| `DSAR_DECRYPT_NO_ARTIFACTS`    | No effect               | New access DSAR                                  |
| `DSAR_DECRYPT_DOWNLOAD_FAILED` | Yes                     | Same GET                                         |
| `DSAR_DECRYPT_FAILED`          | No — deterministic      | New access DSAR (key-rotation / corruption)      |
| `DSAR_DECRYPT_TOO_LARGE`       | No                      | Per-file signed URLs via the export landing page |
| `DSAR_CANCEL_FAILED`           | Yes                     | Same POST                                        |

***

## When to escalate

Escalate to support when the fix above does not clear the failure, or when a
decrypt failure hits a **fresh** export (a platform-side key fault).
Include:

* The DSAR request id (`dsar_…`) from `GET /compliance/dsar/{id}`.
* The `error.code` and `error.details.stage` from the response — for a
  download or decrypt failure, the `details.artifact` name; for a list
  failure, the `details.bucket` name.
* The `meta.request_id` from the failing response.

<Note>
  Every control on this page is a **tenant-owned** path: Orbit exposes the
  export, decrypt, and cancel operations and the failure signals above; how
  you handle a data subject's request — and which request type you fulfil —
  stays 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
  this page troubleshoots.
* [Error codes](/reference/error-codes) — the full registry entry for each
  code above.
* [Compliance posture overview](/compliance/posture-overview) — where the
  DSAR surface sits in your overall posture.
