# `uip admin audit` — CLI Command Reference

Single source of truth for every `uip admin audit` subcommand, flag, and output shape. Run every command with `--output json`; commands return `{ "Result": "Success"|"Failure", "Code": "...", "Data": ... }`.

For task workflows (investigate → query → export), see [audit-workflow-guide.md](./audit-workflow-guide.md). This file documents only the command surface.

## Command tree and scope

```text
uip admin audit
├── org
│   ├── sources
│   ├── events
│   ├── export
│   └── exclusions          # org only — no tenant twin
│       ├── list
│       ├── get <rule>      # <rule> = rule name or policy id
│       ├── create
│       ├── update <rule>
│       └── delete <rule>
└── tenant
    ├── sources
    ├── events
    └── export
```

Run `uip admin audit org <verb>` or `uip admin audit tenant <verb>`. Never use `--scope`; `audit sources --scope organization` is invalid. Tenant commands additionally support `--tenant-id`.

`sources`, `events`, and `export` are verb-symmetric across both scopes. `exclusions` exists only under `org`; `uip admin audit tenant exclusions` is `unknown command`. <!-- uip-check-skip -->

| Verb | `Data` shape |
|---|---|
| `audit <scope> sources` | array of `AuditEventSourceDto` |
| `audit <scope> events` | object `{auditEvents, next, previous}` |
| `audit <scope> export` | object `{Path, Format, Bytes, Days, NonEmptyDays}`; `Files` for `json`, `Events` for `--file-format csv` |
| `audit org exclusions list` | array of `ExclusionRuleDto` |
| `audit org exclusions get/create/update` | one `ExclusionRuleDto` |
| `audit org exclusions delete` | `{}` |

## `uip admin audit <scope> sources`

Run:

```bash
uip admin audit org sources --output json
uip admin audit tenant sources --output json
```

List visible audit event sources. Pass inner `id` GUIDs from `eventTargets[]` and `eventTypes[]` to `events --source/--target/--type`. `Data` is always an array, including when empty.

| Flag | Required | Description |
|---|---|---|
| `--login-validity <minutes>` | no | Refresh the bearer when its remaining lifetime is below this threshold. |
| `--tenant-id <guid>` | no | Tenant scope only; overrides the tenant from login context. Silently rejected on `org`. |

**Output `Code`:** `AuditOrgSources` / `AuditTenantSources`.

**Output `Data`:** `AuditEventSourceDto` objects containing `id`, `name`, `eventTargets`, and `eventTypes`; nested targets and types contain `id` and `name`.

## `uip admin audit <scope> events`

Run queries with filters and cursor pagination:

```bash
uip admin audit tenant events --from-date <FROM_TIMESTAMP> --to-date <TO_TIMESTAMP> --limit 50 --output json
```

| Flag | Required | Description |
|---|---|---|
| `--from-date <iso>` | no | ISO 8601 start; inclusive. Recommended for non-trivial queries. |
| `--to-date <iso>` | no | ISO 8601 end; inclusive of the exact instant. Pass the next day’s start or `T23:59:59.999Z` to capture a full final day. Applies only to `events`; `export` bounds are whole-day inclusive, so the next-day trick over-exports. See [workflow-guide gotchas](./audit-workflow-guide.md#common-gotchas). |
| `--source <guid...>` | no | Repeatable event-source filter; discover IDs with `sources`. |
| `--target <guid...>` | no | Repeatable event-target filter. |
| `--type <guid...>` | no | Repeatable event-type filter. |
| `--user-id <guid...>` | no | Repeatable acting-user filter. |
| `--search <term>` | no | Server-side substring search across event content. |
| `--status <Success\|Failure\|0\|1>` | no | Case-insensitive labels or raw `AuditEventStatus` enum values. |
| `--limit <n>` | no | Use 1–10000. Larger values fail before HTTP with `Result: "ValidationError"` / `invalid_argument`; do not use a huge value for “everything”. Each API call is clamped to 200; values above 200 trigger client-side pagination. Omit it for one call with the server default, typically up to 200 events. |
| `--login-validity <minutes>` | no | Token-refresh hint. |
| `--tenant-id <guid>` | no | Tenant scope only; overrides the active tenant. |

**Output `Code`:** `AuditOrgEvents` / `AuditTenantEvents`.

**Output `Data`:** always an object, never a bare array:

```json
{
  "auditEvents": [
    {
      "id": "...",
      "createdOn": "...",
      "organizationId": "...",
      "organizationName": "...",
      "tenantId": "...",
      "tenantName": "...",
      "actorId": "...",
      "actorName": "...",
      "actorEmail": "...",
      "eventType": "...",
      "eventSource": "...",
      "eventTarget": "...",
      "eventDetails": "{...}",
      "eventSummary": "...",
      "status": 0,
      "clientInfo": {
        "ipAddress": "...",
        "ipCountry": "..."
      }
    }
  ],
  "next": null,
  "previous": "/{org}/{tenant}/tenantaudit_/api/query/events?to=...&before=...&beforeId=...&maxCount=...&qw=..."
}
```

`tenantId` and `tenantName` are null on org-scope events. `eventDetails` is a JSON-encoded string with type-specific fields. `clientInfo` is optional and absent on server-originated events; when present it contains `ipAddress` and `ipCountry`. `status` is `0=Success, 1=Failure`. Cursor naming is chronological: `next` means newer and is often null when anchored at now; `previous` means older and is normally followed to scroll backward.

## `uip admin audit <scope> export`

Run the long-term audit store export for `[--from-date, --to-date]` as inclusive whole UTC days into the directory specified by `--output-path`:

```bash
uip admin audit tenant export \
  --from-date <FROM_DATE> \
  --to-date <TO_DATE> \
  --output-path ./audit-exports \
  --output json

uip admin audit tenant export \
  --from-date <FROM_DATE> \
  --to-date <TO_DATE> \
  --file-format csv \
  --output-path ./audit-exports \
  --output json

day=$(date -u -d 'yesterday' +%F)   # macOS/BSD: date -u -v-1d +%F
uip admin audit tenant export \
  --from-date "$day" \
  --to-date "$day" \
  --output-path ./audit-exports \
  --output json
```

Pass a directory, never a filename or extension. Each run creates `audit_<from>_<to>_<generated-at>`: a folder of day-wise JSON files by default or one merged CSV. JSON output is `./audit-exports/audit_<from>_<to>_<generatedAt>/`; CSV output is `./audit-exports/audit_<from>_<to>_<generatedAt>.csv`. The generated-at value is to the second; repeated exports do not collide.

This is the organization/tenant audit event store (LTS-schema columns such as `Identifier`, `DateCreatedUtc`, `ActorId`, `Action`, `Source`, `Category`) for compliance dumps, login history, and cross-platform “who did what where.” It is not `uip or audit-logs list --export` (the uipath-platform skill), which exports one Orchestrator tenant’s operational actions with `Component,User,Action,Operation,Time` columns. An org/tenant audit-event or compliance export with a date window and `--output-path`, whether JSON files or spreadsheet/Excel CSV, belongs here.

| Flag | Required | Description |
|---|---|---|
| `--output-path <dir>` | **yes** | Base directory, created if missing. Pass a directory only, never a filename or extension. The CLI creates a uniquely named folder of day-wise JSON files (`json`) or a single `.csv` (`csv`) inside it using `audit_<from>_<to>_<generated-at>`. It resolves the path to an absolute path internally. |
| `--output-file <dir>` | no | Deprecated alias for `--output-path`; it remains a base directory, not a file. Prefer `--output-path`; this flag emits a deprecation warning. |
| `--from-date <iso>` | **yes** | Required by Commander before HTTP. Interpreted as a whole UTC day; time components are truncated to the calendar day. |
| `--to-date <iso>` | **yes** | Required by Commander before HTTP. Inclusive as a whole UTC day. `--from-date X --to-date X` exports only day `X`; do not pass the next day to capture the final day. |
| `--file-format <json\|csv>` | no | `json` default: one `<YYYY-MM-DD>.json` per UTC day in a folder. `csv`: all events in one RFC 4180 CSV under a shared header. Invalid values fail before HTTP with `Invalid --file-format '<v>'. Use 'json' or 'csv'.` |
| `--login-validity <minutes>` | no | Token-refresh hint. |
| `--tenant-id <guid>` | no | Tenant scope only; overrides the active tenant. |

**Output `Code`:** `AuditOrgExport` / `AuditTenantExport`.

**Output `Data`:** contains `Path`, `Format`, `Bytes`, `Days`, `NonEmptyDays`, and `GeneratedAt`. `Format` echoes `--file-format`. `Path` is the generated folder under `--output-path` for JSON or the generated `.csv` for CSV. JSON additionally contains `Files`; CSV additionally contains `Events` (rows excluding the header).

```json
{
  "Path": "C:\\absolute\\path\\to\\audit-exports\\audit_<from>_<to>_<generatedAt>",
  "Format": "json",
  "Files": 27,
  "Bytes": 1841,
  "Days": 31,
  "NonEmptyDays": 27,
  "GeneratedAt": "<GENERATED_AT>"
}
```

For CSV, `Path` ends in `.csv`, `Format` is `csv`, and `Events` reports total rows; `Bytes`, `Days`, `NonEmptyDays`, and `GeneratedAt` remain present.

### Export behavior and diagnostics

- Run one HTTP call per UTC day in `[from, to]`; both formats aggregate those responses, mirroring `audit-dowload-from-longterm-store.sh`.
- For JSON, create a uniquely named `audit_<from>_<to>_<generated-at>` folder with `<YYYY-MM-DD>.json` files. Write server `.txt` payloads with `.json` because they contain JSON arrays with LTS-schema keys (`Identifier`, `DateCreatedUtc`, `Action`, …). Flatten nested-ZIP entries to `<inner>_<outer>.json`; give same-name collisions an iso-day suffix. Validate entry names as safe basenames, confirm they resolve inside the folder before writing, and prevent path traversal / Zip-Slip.
- For CSV, parse the same per-day JSON arrays into one RFC 4180 CSV with CRLF endings and a first header row. Use this field order: `OrganizationId, TenantId, ActorId, ActorEmail, ActorDetails, EventDetails, Status, Identifier, DateCreatedUtc, User, Action, Source, Category, ClientInformation`; append extra server fields by union across events. Stringify nested objects such as `ClientInformation`. Keep `Status` numeric (`0`=Success, `1`=Failure). Prefix string cells beginning with `= + - @` or TAB/CR with a single quote to neutralize spreadsheet formula injection.
- On any single-day HTTP failure, or a CSV day with invalid JSON, write nothing. For JSON, do not create the output folder. Identify the failed day; discard earlier chunks to preserve atomic export.
- `Days` is the requested UTC-day count; `NonEmptyDays` is the count containing data. `NonEmptyDays: 0` means an idle window, not failure. JSON `Files` counts written day files; CSV `Events: 0` produces a header-only file.
- The long-term store lags live `events`, typically by up to ~24–48 h. Recent days can be empty even when `events` has data. For completeness, end the window ≥2 days in the past or rerun later.

## `uip admin audit org exclusions`

CRUD over the organization's audit exclusion rules — which events the audit trail stops recording. Workflow, safety contract, and error-code recovery: [audit-exclusions-guide.md](./audit-exclusions-guide.md). This section is the surface only.

Organization-scoped: the service takes the owning organization from the validated request scope and ignores the tenant header, so there is no `audit tenant exclusions`. A rule limited to some tenants names them in a Tenant selector.

Reads need audit-read permission. `create`, `update`, and `delete` need a user token whose identity is in the organization's Administrators group; a service-to-service token is refused on writes.

```bash
uip admin audit org exclusions list --output json
uip admin audit org exclusions get "<RULE>" --output json
uip admin audit org exclusions create --name "<RULE_NAME>" --type <EVENT_TYPE_ID> --status Success --output json
uip admin audit org exclusions update "<RULE>" --name "<RULE_NAME>" --type <EVENT_TYPE_ID> --status Success --output json
uip admin audit org exclusions delete "<RULE>" --output json
```

### The `<rule>` positional

`get`, `update`, and `delete` take one positional accepting **either** the rule's name or its `policyId`. A GUID is used as it stands and costs no lookup; a name is matched against the organization's rules, exactly first and then case-insensitively. Reading by name costs one call, not two — the lookup's listing already carries the rule, so `get` answers from it.

A name is unique only among **active** rules, so an inactive rule may share one. A name matching several rules fails with `Result: ValidationError` (exit `3`) listing both policy ids and their active state; pass the id of the intended rule. A name matching nothing fails with `Result: Failure` (exit `1`). Neither attempts the operation.

### Selector model

A rule carries at most one selector per dimension; two selectors on one dimension are rejected. Values inside a selector are OR-ed, selectors are AND-ed.

| Dimension | Flag | Values |
|---|---|---|
| `Tenant` | `--exclude-tenant <guid...>` | tenant GUIDs. Repeatable. A tenant outside this organization is accepted and matches nothing. |
| `EventSource` | `--source <guid...>` | source GUIDs from `audit org sources`. Repeatable. |
| `EventTarget` | `--target <guid...>` | target GUIDs from `audit org sources`. Repeatable. |
| `EventType` | `--type <guid...>` | type GUIDs from `audit org sources`. Repeatable. |
| `Status` | `--status <status>` | `Success` or `Failure`, case-insensitive; `0` and `1` also accepted, as on `events`. Single-valued. |

The tenant selector is `--exclude-tenant`, **not** `--tenant-id`: on the read verbs `--tenant-id` picks the tenant a call runs against, while here the tenants are what the rule matches. `--tenant-id` is not an option on any `exclusions` command.

`--status` takes one value because a selector holding both statuses matches every event, which is what omitting the flag already does.

`enforcement` is not a flag: `Exclude` is the only value the service accepts, and the CLI supplies it.

### Write flags (`create` and `update`)

| Flag | Required | Description |
|---|---|---|
| `--name <name>` | yes, unless `--file` | Unique among the organization's **active** rules; max 256 characters; may not be a bare GUID. |
| `--exclude-tenant <guid...>` | no | Tenant selector. Repeatable. |
| `--source <guid...>` | no | EventSource selector. Repeatable. |
| `--target <guid...>` | no | EventTarget selector. Repeatable. |
| `--type <guid...>` | no | EventType selector. Repeatable. |
| `--status <status>` | no | Status selector. Single-valued. |
| `--inactive` | no | Save without activating. Excludes nothing; may reuse an active rule's name or match set. |
| `--file <path>` | no | Whole body as JSON. Mutually exclusive with every flag above; max 64 KB. |
| `--login-validity <minutes>` | no | Token-refresh hint. |

At least one selector flag is required — the CLI refuses an unconstrained rule locally, before any HTTP call, because it would exclude every event in the organization. A name shaped like a GUID is refused for the same reason `<rule>` accepts both: such a rule could not be addressed by name, and would shadow whichever rule owns that id.

`update` is a PUT: the body is a full replacement and every omitted field is dropped. Run `exclusions get` first and resend every selector the rule should keep. **Activation is part of the replacement** — without `--inactive` the rule comes back active, so replacing a deliberately staged rule starts it excluding events. Pass `<rule>` before the selector flags, which are variadic and would otherwise absorb it as a value.

### `--file` body

Exactly four camelCase keys; a selector carries only `type` and `values`:

```json
{
  "name": "<RULE_NAME>",
  "enforcement": "Exclude",
  "isActive": true,
  "selectors": [
    { "type": "EventType", "values": ["<EVENT_TYPE_ID>"] },
    { "type": "Status", "values": ["Success"] }
  ]
}
```

`enforcement` may be omitted. `isActive` may be omitted and defaults to `true`, so a file-authored `update` re-activates a staged rule unless it sets `"isActive": false`. `name` is required, trimmed, and may not be a bare GUID. Any other top-level key — `policyId`, `activatedOn`, `createdOn`, `lastModifiedOn`, or a typo — fails with `Result: ValidationError` naming the field, before the request is sent: server-owned fields are refused rather than silently dropped. A `get` response cannot be reused as a `--file` body, because keys under `Data` come back PascalCased and carry those server-owned fields.

Selectors in a file get exactly the checks the flags get, so the two input styles cannot disagree about what a valid rule is: `type` must be one of `Tenant`, `EventSource`, `EventTarget`, `EventType`, `Status` (the dimension stays `Tenant` even though its flag is `--exclude-tenant`); `values` must be a non-empty string array; ids must be GUIDs; a `Status` value is canonicalized (`success` / `0` / `Success` all store as `Success`); two selectors on one dimension are refused; and an empty or absent `selectors` fails locally rather than costing a round trip to hear `EmptySelectorsNotAllowed`.

### Output codes and `Data`

| Verb | `Code` |
|---|---|
| `list` | `AuditOrgExclusionsList` |
| `get` | `AuditOrgExclusionsGet` |
| `create` | `AuditOrgExclusionsCreated` |
| `update` | `AuditOrgExclusionsUpdated` |
| `delete` | `AuditOrgExclusionsDeleted` (`Data` is `{}`) |

`ExclusionRuleDto` fields: `policyId`, `name`, `enforcement`, `isActive`, `activatedOn`, `selectors`, `createdOn`, `lastModifiedOn`. `activatedOn` is the instant exclusion starts and is `null` while the rule is inactive. `selectors` entries carry `type` and `values`.

```json
{
  "policyId": "<POLICY_ID>",
  "name": "<RULE_NAME>",
  "enforcement": "Exclude",
  "isActive": true,
  "activatedOn": "<ACTIVATED_ON>",
  "selectors": [
    { "type": "EventType", "values": ["<EVENT_TYPE_ID>"] },
    { "type": "Status", "values": ["Success"] }
  ],
  "createdOn": "<CREATED_ON>",
  "lastModifiedOn": "<LAST_MODIFIED_ON>"
}
```

`list` is ordered by name and is not paginated — the rule set is capped server-side. A rule whose stored document cannot be parsed is still listed, with an empty `selectors` array, so that it can be deleted.

### Behavior worth knowing

- Exclusion is never retroactive: recorded events stay recorded. Renaming preserves `activatedOn`; changing the match set re-stamps it.
- Deleting a rule resumes recording from that moment. Events suppressed while it was active are gone permanently.
- UiPath monitoring events and audit-configuration changes — including changes to the rules themselves — can never be excluded (`SelectorValueNotPermitted`).
- An active rule hides its matching events from `audit <scope> events` **and** `audit <scope> export`. Neither response marks the gap.

### Exclusions error envelope

Failures carry the service's own reason in `Message` (folded in from the problem body's `detail`), a machine-readable `code` in `Context.errorCode`, a per-code hint in `Instructions`, and the usual `ErrorCode` / `Retry` classification:

```json
{
  "Result": "Failure",
  "ErrorCode": "conflict",
  "Retry": "RetryWillNotFix",
  "Message": "An active rule already excludes every event this rule would.",
  "Instructions": "An active rule already excludes everything this one would — its id is in the message. Delete or narrow that rule, or save this one with --inactive to stage it alongside.",
  "Context": { "errorCode": "OverlappingRuleExists", "httpStatus": 409 }
}
```

Exit codes follow the CLI contract: `0` success, `1` a service `Failure`, `2` a `403`, `3` a `ValidationError` — everything the CLI refuses locally, plus a `400`.

| `errorCode` | Cause |
|---|---|
| `RuleNotFound` | Unknown id, or a rule owned by another organization (answers identically). |
| `DuplicateRuleName` | Another active rule holds that name. |
| `OverlappingRuleExists` | An active rule already excludes everything this one would. |
| `RuleLimitExceeded` | The organization is at its rule cap. |
| `EmptySelectorsNotAllowed` | Body constrains nothing. |
| `InvalidSelectorValue` | Value is not a GUID, or `--status` is outside `Success`/`Failure`. |
| `UnknownSelectorValue` | No audit metadata defines that id. |
| `SelectorValueNotPermitted` | Protected target — UiPath monitoring or audit-configuration events. |
| `SelectorLimitExceeded` | Too many selectors, or too many values in one selector. |
| `RuleModifiedConcurrently` | Another admin changed the rule mid-call. |
| `InvalidRequest` | Rejected before the rule logic; the message names the field. |

A failed `<rule>` lookup never reaches the service and so carries no `errorCode`: an ambiguous name is `ValidationError` / `invalid_argument` (exit `3`) listing the matching policy ids, and a name matching nothing is `Failure` / `not_found` (exit `1`).

Recovery per code: [audit-exclusions-guide.md → Error codes](./audit-exclusions-guide.md#error-codes--what-to-do).

Availability: `unknown command 'exclusions'` means the installed CLI predates the feature (upgrade `@uipath/cli`); `HTTP 404` on `list` means the audit service in this organization does not expose `/api/EventConfig/rules` yet. Neither is retryable.

## Cross-cutting flags from the CLI host

These program-level `uip` flags appear on every command:

| Flag | Description |
|---|---|
| `--output <table\|json\|yaml\|plain>` | Success/failure envelope format; defaults to `json`. It does not change exported files (`--file-format` controls those). |
| `--output-filter <jmespath>` | Applies a JMESPath query to the envelope; useful for `events`/`sources`, less useful for small `export` envelopes. |
| `--log-level <debug\|info\|warn\|error>` | Logger threshold; logs go to stderr. |
| `--log-file <path>` | Redirects logs from stderr to a file. |
| `--help, -h` | Show help. |

## Error envelope

```json
{
  "Result": "Failure",
  "Message": "Audit export failed for <DATE> (HTTP 504): Gateway Timeout",
  "Instructions": "Ensure you are logged in with 'uip login' and have access to the audit service."
}
```

| `Message` snippet | Likely cause | Fix |
|---|---|---|
| `Not logged in. Run 'uip login' first.` | No cached login state | Run `uip login`. |
| `Tenant ID required for tenant-scoped audit calls.` | Tenant scope lacks a tenant in login context | Add `--tenant-id <guid>` or re-`uip login` selecting a tenant. |
| `HTTP 401 / WWW-Authenticate: Bearer` | Token `aud` lacks `Audit` | Run `uip logout && uip login` to mint a fresh token; `Audit.Read` is in `DEFAULT_SCOPES` post-onboarding. |
| `HTTP 504` on a single export day | Long-term-store query timed out | Rerun the export, or narrow the window. |
| `Audit export failed for YYYY-MM-DD (HTTP 504)` | A single-day chunk failed during a multi-day export | The whole export is rolled back. Rerun, or narrow `--from-date/--to-date` to skip the bad day. |
