---
title: Enterprise Compliance
path: advanced/enterprise-compliance
status: published
---

Legal hold, retention policies, DLP, eDiscovery, and audit. The features that matter when your data is subject to regulation — financial services, healthcare, government — or when you need to preserve content for litigation.

For endpoint-level details, see [Enterprise Reference](/docs/scaidrive/reference/enterprise).

## Legal hold

Freezes content for litigation or investigation. Held content cannot be deleted — by anyone, including admins, even if deletion is attempted through the API, through retention policy, or via a bulk cleanup. Deletion attempts fail with `LEGAL_HOLD_BLOCKED_DELETION`.

A hold scopes to any combination of:

- **Users** — all content they created or own.
- **Groups** — aggregate of all members.
- **Shares** — an entire share.
- **Folders** — specific subtrees.
- **Include/exclude patterns** — glob patterns within the scope.

### Creating a hold

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/enterprise/legal-holds \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Matter 2026-003",
    "description": "Dispute with ACME Corp",
    "matter_id": "LEGAL-2026-003",
    "custodian_ids": ["usr_01J4M", "usr_01J4N"],
    "legal_counsel": "outside-counsel@example.com"
  }'
```

Then add scopes:

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/enterprise/legal-holds/hld_01J7B/items \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scope_type": "user",
    "scope_id": "usr_01J4M",
    "include_pattern": "**/*.docx"
  }'
```

Holds can be indefinite or have an `expiration_date`. Releasing a hold (`DELETE`) allows retention and normal deletion to proceed.

### What's preserved

- Current file versions.
- All prior versions (even those that would have been pruned by retention).
- Metadata — permissions, path, timestamps.
- Change log entries referencing held resources.

What's not preserved: audit events (kept per tenant's audit retention, separately), chunks referenced only by unrelated content.

## Retention policies

Retention automates deletion of content older than a configured age. Common use cases:

- Regulatory deletion ("delete financial records 7 years after creation").
- Housekeeping ("clear project scratch folders after 90 days of inactivity").
- Privacy compliance ("delete user data 30 days after account closure").

### Policy structure

```json
{
  "name": "7-year financial retention",
  "retention_days": 2555,
  "trigger": "creation",
  "action": "delete",
  "scope_type": "share",
  "scope_ids": ["shr_finance"]
}
```

**trigger:** `creation` or `modification` — which timestamp starts the clock.
**action:** `delete` (permanent), `archive` (move to archive store), `quarantine` (lock read-only pending review).

### Interaction with legal hold

Legal hold overrides retention. A policy saying "delete after 30 days" does nothing to content under legal hold — retention silently skips held items. When the hold releases, any content already past its retention age becomes immediately eligible for the policy's action.

### Inspection

Every file and folder has an effective retention computed from all applicable policies. Currently surfaced via:

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/files/fil_01J3K/retention
```

Returns `{eligible_at, policy_id, action}` or `null` if no policy applies.

## DLP (Data Loss Prevention)

Rules that match file content or metadata and take action before content leaks.

### Rule types

| Rule type | Matches |
|-----------|---------|
| `regex` | Pattern against file content (text or extracted text from documents) |
| `keyword` | Any of a list of keywords |
| `file_type` | MIME type or extension |

### Actions

| Action | Effect |
|--------|--------|
| `block` | Upload fails with `DLP_RULE_BLOCKED` |
| `quarantine` | File is uploaded but moved to quarantine; not accessible until reviewed |
| `notify` | File is uploaded normally; admin notified |
| `log` | Upload allowed; violation recorded for later review |

### Example

A rule that blocks US Social Security numbers:

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/enterprise/dlp/rules \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "US SSN",
    "rule_type": "regex",
    "pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b",
    "action": "block",
    "severity": "high"
  }'
```

### Where rules run

DLP evaluates at:

- **Upload time** — new files and new versions.
- **Link creation** — creating an external link triggers re-evaluation; blocked rules prevent link creation.
- **Download via external link** — DLP can block downloads, not just uploads.

Rules do not retroactively evaluate existing content. For backfill, run an eDiscovery search with the same pattern.

### Violations

```bash
curl -G $SCAIDRIVE_URL/api/v1/enterprise/dlp/violations \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-urlencode "status=detected" \
  --data-urlencode "severity=high"
```

Admins review and remediate. Remediation options: delete file, remove violating content, dismiss (false positive), elevate to legal hold.

## eDiscovery

For formal evidence-gathering. Cases, searches, and exports.

### Case

A case groups related searches and exports. It's the unit of access control — only users added to the case can run searches or view results.

### Search

A search scopes across custodians, date ranges, and optional query patterns:

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/enterprise/ediscovery/searches \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "case_id": "cas_01J7C",
    "name": "All 2026 Q1 email attachments about ACME",
    "custodians": ["usr_01J4M", "usr_01J4N"],
    "date_from": "2026-01-01T00:00:00Z",
    "date_to": "2026-03-31T23:59:59Z",
    "query": "ACME OR \"Acme Corp\""
  }'
```

Searches run asynchronously. Poll for completion:

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/enterprise/ediscovery/searches/sch_01J7D
```

Result counts, hit list, and links to download hits.

### Export

Package search results as a ZIP with a manifest for review in dedicated eDiscovery tools:

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/enterprise/ediscovery/exports \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"case_id": "cas_01J7C", "search_id": "sch_01J7D", "format": "edrm-xml"}'
```

Formats supported: `edrm-xml` (EDRM XML), `concordance` (Concordance load files), `standard` (ZIP + JSON manifest).

## Audit log

Every security-relevant action generates an audit event. Events are retained per tenant's configured audit retention (default 7 years).

### Categories

The `category` field is one of:

- `authentication` — login success/failure, token refresh
- `authorization` — ACL changes, inheritance changes, ownership transfer
- `file_access` — read, download, preview
- `file_modification` — file and folder create/update/delete/move/copy/rename/restore
- `sharing` — share CRUD, member add/remove, invitation create/revoke, external link create/update/revoke/delete, ownership transfer
- `admin` — user role change, identity mapping resolve/clear, quota set/delete/exempt, SIEM integration create/delete
- `security` — DLP violation, access denied, suspicious activity
- `compliance` — legal hold, retention policy, DLP rule lifecycle, eDiscovery, label CRUD

### Event type families

Each event has an `event_type` like `file.create`, `permission.ace_add`, `compliance.legal_hold_release`. The grouping:

- `auth.*` — `login`, `logout`, `token_refresh`, `mfa_challenge`
- `file.*` — `create`, `update`, `delete`, `move`, `rename`, `copy`, `restore`, `download`, `read`, `preview`
- `folder.*` — `create`, `rename`, `delete`, `move`, `restore`
- `sharing.*` — `share_create`, `share_update`, `share_delete`, `member_add`, `member_update`, `member_remove`, `invitation_create`, `invitation_revoke`, `external_link_create`, `external_link_update`, `external_link_revoke`, `external_link_delete`
- `permission.*` — `ace_add`, `ace_remove`, `inheritance_change`, `ownership_transfer`
- `admin.*` — `user_role_change`, `identity_mapping_resolve`, `identity_mapping_clear`, `quota_set`, `quota_delete`, `quota_exempt`, `siem_integration_create`, `siem_integration_delete`
- `compliance.*` — `legal_hold_create`, `legal_hold_update`, `legal_hold_release`, `legal_hold_item_add`, `legal_hold_item_remove`, `retention_policy_create`, `retention_policy_delete`, `retention_policy_apply`, `dlp_rule_create`, `dlp_rule_update`, `dlp_rule_delete`, `ediscovery_case_create`, `ediscovery_case_close`, `ediscovery_custodian_add`, `ediscovery_custodian_remove`, `ediscovery_search_create`, `ediscovery_export_create`, `label_create`, `label_delete`, `label_apply`, `label_remove`
- `security.*` — `dlp_violation`, `access_denied`, `suspicious_activity`

### Severity escalation

Most events emit at `INFO`. Three actions are auto-escalated to `WARNING` because they're high-impact and worth flagging in your SIEM rules:

- `compliance.legal_hold_release` — content protection coming off
- `compliance.retention_policy_delete` — retention rule disappearing
- `compliance.ediscovery_export_create` — data leaving the platform

Failed logins (`auth.login` with `outcome=failure`) also emit at `WARNING`. Denied authorization attempts emit at `ERROR`.

### Delegation provenance (service_account)

Every audit event has a `service_account` field. When a token reached ScaiDrive through a token-exchange flow — e.g., a ScaiSpeak save_to upload acting on a user's behalf — the RFC 8693 `act.client_id` claim is recorded here. For direct user logins, `service_account` is null.

This lets you answer "who performed this action" and "what service delegated it" in a single audit query. SIEM exports include the field, so downstream consumers see the delegation chain across all event types.

```bash
curl -G $SCAIDRIVE_URL/api/v1/enterprise/audit/events \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-urlencode "event_type=file.create" \
  --data-urlencode "since=2026-04-01T00:00:00Z"
```

```python
events = httpx.get(
    f"{url}/api/v1/enterprise/audit/events",
    headers={"Authorization": f"Bearer {token}"},
    params={"event_type": "file.create", "since": "2026-04-01T00:00:00Z"},
).json()

for e in events:
    actor = e["user_id"]
    via = e.get("service_account")
    print(f"{e['event_time']} {actor}" + (f" via {via}" if via else ""))
```

```typescript
const params = new URLSearchParams({
  event_type: "file.create",
  since: "2026-04-01T00:00:00Z",
});
const resp = await fetch(
  `${url}/api/v1/enterprise/audit/events?${params}`,
  { headers: { Authorization: `Bearer ${token}` } },
);
const events = await resp.json();
for (const e of events) {
  const via = e.service_account ? ` via ${e.service_account}` : "";
  console.log(`${e.event_time} ${e.user_id}${via}`);
}
```

### Querying

The `/audit/events` endpoint accepts filtering on category, severity, user_id, share_id, resource_type, event_type, since/until, and outcome. See the [Enterprise reference](/docs/scaidrive/reference/enterprise#audit-log) for the full parameter list.

### SIEM integration

For continuous streaming to a SIEM (Splunk, Elastic, syslog, generic webhook), configure a SIEM integration via `POST /api/v1/enterprise/siem/integrations`. Events stream in near-real-time as they're written; the `service_account` field is included in the exported payload so downstream rules can fire on delegation patterns (e.g., "alert when ScaiSpeak generates >100 files in 1 hour on behalf of the same user").

## Best practices

- **Scope aggressively.** Don't put "all content" on legal hold; use custodians and patterns. Small scopes are cheaper to maintain and audit.
- **Retention before DLP.** If you shouldn't keep it, delete it. Retention is cheaper to enforce than DLP rules that catch sensitive content.
- **Review DLP violations weekly.** `detected` status is a queue — let it grow and you'll miss the signal.
- **Test DLP rules before enabling `block`.** Start with `log`, check the violation rate, then tighten.
- **Keep audit retention aligned with legal requirements.** Most regulated industries require 7+ years.

## What's next

- [Enterprise Reference](/docs/scaidrive/reference/enterprise)
- [External Links](/docs/scaidrive/api-guides/external-links) — DLP at the sharing boundary.
- [Errors](/docs/scaidrive/core-concepts/errors)