--- name: omni-admin description: Administer an Omni Analytics instance — manage connections, users, groups, user attributes, permissions, schedules, and schema refreshes via the Omni CLI. Use this skill whenever someone wants to manage users or groups, set up permissions on a dashboard or folder, configure user attributes, create or modify schedules, manage database connections, refresh a schema, set up access controls, provision users, or any variant of "add a user", "give access to", "set up permissions", "who has access", "configure connection", "refresh the schema", or "schedule a delivery". --- # Omni Admin Manage your Omni instance — connections, users, groups, user attributes, permissions, schedules, and schema refreshes. > **Tip**: Most admin endpoints require an **Organization API Key** (not a Personal Access Token). ## Prerequisites ```bash # Verify the Omni CLI is installed — if not, ask the user to install it # See: https://github.com/exploreomni/cli#readme command -v omni >/dev/null || echo "ERROR: Omni CLI is not installed." ``` ```bash # Show available profiles and select the appropriate one omni config show # If multiple profiles exist, ask the user which to use, then switch: omni config use # Confirm the active profile is authenticated and inspect your permissions: omni whoami whoami ``` > **Auth**: a profile authenticates with an **API key** or **OAuth**. If `whoami` (or any call) returns **401**, hand off — ask the user to run `! omni config login ` (OAuth 2.1 browser flow; it blocks ~2 min on the browser). Don't run `config login` yourself in a headless/CI session (no browser → timeout); on a local interactive machine you *may*. See the [**`omni-api-conventions`**](rules/omni-api-conventions.mdc) rule for profile setup (`omni config init --auth oauth`) and discovering command and request-body shapes with `--schema`. If no CLI profile exists but the environment provides credentials, pass them explicitly: ```bash omni --base-url "$OMNI_BASE_URL" --token "$OMNI_API_TOKEN" ``` ## Discovering Commands ```bash omni scim --help # User and group management omni schedules --help # Schedule operations omni connections --help # Connection management omni documents --help # Document permissions omni folders --help # Folder permissions omni scim users-create --schema # Print a command's args, flags, body schema + example, and response shape (no token) ``` > **Tip**: Use `-o json` to force structured output for programmatic parsing, or `-o human` for readable tables. The default is `auto` (human in a TTY, JSON when piped). ## Safe Admin Defaults - For create operations, first try the requested create. If the API returns a conflict because the resource already exists, look it up and verify it exactly matches the requested state before reporting success. - Prefer read-after-write checks that inspect the specific created or changed resource, not just a successful status response. - Use the role names returned by Omni permission APIs (`VIEWER`, `EXPLORER`, `EDITOR`, `MANAGER`) when updating content access. ## Connections ```bash # List connections omni connections list # Schema refresh schedules omni connections schedules-list # Connection environments (create, update , delete ) omni connections connection-environments-create --schema ``` ### Commit Signing Key Rotation (CLI ≥ 1.1.2) Rotating invalidates the previous key — confirm with the user before running, and re-register the new public key wherever the old one was trusted. ```bash # Rotate a connection's dbt commit signing key omni connections dbt-rotate-signing-key # Rotate a model's git commit signing key omni models git-rotate-signing-key ``` ## User Management (SCIM 2.0) > The `--body` blocks below are worked examples. For the authoritative field list (types, required, enums), run the command with `--schema` — e.g. `omni scim users-create --schema` — rather than relying on these shapes to be exhaustive. ```bash # List users omni scim users-list # Find by email omni scim users-list --filter 'userName eq "user@company.com"' # Create user omni scim users-create --body '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "newuser@company.com", "displayName": "New User", "active": true, "emails": [{ "primary": true, "value": "newuser@company.com" }] }' # Deactivate user omni scim users-update --body '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "Operations": [{ "op": "replace", "path": "active", "value": false }] }' # Delete user omni scim users-delete ``` ## Group Management (SCIM 2.0) ```bash # List groups omni scim groups-list # Create group omni scim groups-create --body '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Analytics Team", "members": [{ "value": "user-uuid-1" }] }' # Add members omni scim groups-update --body '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "Operations": [{ "op": "add", "path": "members", "value": [{ "value": "new-user-uuid" }] }] }' ``` ## User Attributes ```bash # List attributes (system + custom) omni user-attributes list # Create a custom attribute definition (CLI ≥ 1.4.0) omni user-attributes create --body '{ "name": "region", "label": "Region", "type": "String", "description": "User region for row-level security filtering", "default_value": "us-east" }' omni user-attributes update --body '{ "default_value": "us-west" }' omni user-attributes delete # Find the user by email before setting an attribute omni scim users-list --filter 'userName eq "user@company.com"' # Set attribute on user (via SCIM) omni scim users-update --body '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "Operations": [{ "op": "replace", "path": "urn:omni:params:1.0:UserAttribute:region", "value": "West Coast" }] }' ``` User attributes work with `access_filters` in topics for row-level security. SCIM can set values only for attribute definitions that already exist. Use `omni user-attributes list` to confirm the requested attribute definition exists before setting a value, but do not use it as proof that a specific user's value changed. If the definition is missing, create it with `omni user-attributes create` (CLI ≥ 1.4.0) and confirm the create before assigning values; do not keep retrying SCIM paths or claim the value was set from an empty `User attributes set: {}` response. Managing definitions needs the **Manage User Attributes** permission; the naming and type rules are in `--help` and fail loudly. Two behaviors do not: - **`delete ` reaches past the definition, and the response does not say so.** Every user value goes with it, embed SSO logins still passing the name fail outright (an embed lockout), model SQL referencing it breaks, and a connection selecting its environment by that name silently falls back to the default connection. Search the model for the name and confirm with the user first. - **`Number` values are stored as strings**, and a JSON number past 2^53 - 1 is silently rounded on the way in — a `default_value` of `9007199254740993` is stored, and returned, as `"9007199254740992"`. Send large numbers as strings. When the user explicitly asks to set or update a user attribute, converge the user record with a SCIM update even if the initial user lookup already shows the requested value. This keeps the operation idempotent while still honoring the requested admin action. After the update, read back the same user and verify the value under `urn:omni:params:1.0:UserAttribute`. ## Model Roles & Caller Access ### Determining what a caller can do — run before deciding where model/content changes live This is the canonical access check other skills defer to (e.g. `omni-content-builder` / `omni-model-builder` deciding whether a new field goes on a **branch** vs a **workbook model**). Run `omni whoami whoami --model-id ` and read `rolesByModel[].permissions`. Gate on the **presence of permissions**, not the role *name* — names can be renamed custom roles and surface as internal codes (e.g. `QUERY_TOPICS`): | Permission (in `whoami`) | Capability | |---|---| | `QUERY_TOPICS` | query curated topics (Restricted Querier and up) | | `QUERY_FULL_MODEL` | full-model + SQL access — and the **observable proxy for "can create/use a branch"** (Querier, Modeler, Admin) | | `UPDATE` (shared-model-scoped) | **merge/promote** changes to the shared model (Modeler, Admin) | | `USE_WORKBOOKS` | create content (workbooks/dashboards). A **Viewer lacks this and cannot author at all.** | Decision shortcuts: - **Where should a new model field live?** `QUERY_FULL_MODEL` present → a **shared-model branch** (merge it yourself if you *also* have `UPDATE`, otherwise open a PR / request a merge — a Querier can branch and modify but not promote); `QUERY_FULL_MODEL` absent but `USE_WORKBOOKS` present (Restricted Querier) → the document's **workbook model** (extension). A Viewer can't author content, so they never reach this choice. - **What a Restricted Querier can change *in* the workbook model — view-scoped only:** add dimensions/measures to an **existing view** (extension mode) and edit **decorations** on existing fields (label/format/description/hidden/synonyms). They **cannot** modify a **topic** in any way (expose a field, change joins/topic config, create a topic) or change **access grants** / other governance — those need `QUERY_FULL_MODEL` on a shared-model branch. So don't plan a topic-scope, join, or grant change for a restricted querier's workbook model; it 403s mid-build and can leave the document half-modeled. - **What a Restricted Querier can *query* — topics only.** `QUERY_TOPICS` without `QUERY_FULL_MODEL` means every query they author must be **topic-based** (`table` = the topic's base view + `join_paths_from_topic_name`). A **bare base-view query** or a **raw-SQL `userEditedSQL`** tile needs `QUERY_FULL_MODEL` (full-model + SQL access) — so it isn't an option for a restricted-querier author; build every tile on a topic. (Distinct from *visibility*: even a Querier-authored non-topic tile is hidden from restricted/Viewer *audiences* unless Access-Boosted.) - A shared-model-scoped `whoami` does **not** list branch ability as its own entry — branch create/use is a separate capability granted to Querier+ — so use `QUERY_FULL_MODEL` as the proxy. (Holds for base roles; a custom role could grant one without the other — confirm when it matters.) ### Assigning roles ```bash # Roles are keyed by the MEMBERSHIP id, NOT the user id (a user id → 404 "Membership … not found"). # Get it — yourself: whoami → user.membershipId · another user: scim users-list (its `id` IS the membershipId, see note below) omni users get-model-roles --model-id omni users assign-model-role --body '{ "modelId": "", "roleName": "" }' # Group variants omni users user-groups-get-model-roles --model-id omni users user-groups-assign-model-role --body '{ "modelId": "", "roleName": "" }' ``` > **Resolving *another* user's membershipId needs the org key.** `whoami → user.membershipId` is self-only. For someone else, `omni scim users-list --filter 'userName eq "them@company.com"'` returns the membership as its **`id`** (which is the membershipId — *not* the user's `user.id`). But the **SCIM API accepts only the org-level API key**: a user-scoped PAT/OAuth is rejected with *"User-scoped API keys are not allowed to access the SCIM API."* The `get`/`assign-model-role` calls above work with a user PAT — only this lookup needs the org key. - The body key is **`roleName`** (not `role`), and the path id is the **membership id** (a user id returns 404). - `roleName` is a **server-validated, instance-specific** string — base roles (e.g. `QUERY_TOPICS`, `QUERIER`, `CONNECTION_ADMIN`) **plus** any org custom roles. The valid set is **not** discoverable via `--schema` (the field is a bare string, no enum) or any list command; an unknown value returns `422 "Invalid role"`. Discover a valid code by reading `get-model-roles` on a membership that already holds the target role. - There is **no un-assign command** — to revert an override, re-assign the prior role. ## Document Permissions ```bash # Check effective permissions for a user (userId required) omni documents get-permissions --user-id # List document access principals omni documents access-list # Add direct access for a group omni documents add-permits --body '{ "userGroupIds": ["group-uuid"], "role": "VIEWER" }' # Add direct access for a user omni documents add-permits --body '{ "userIds": ["user-uuid"], "role": "EDITOR" }' ``` `role` is one of `NO_ACCESS`, `VIEWER`, `EDITOR`, `MANAGER`. ### Access Boost **Access Boost** lets Viewer / Restricted Querier roles view a dashboard built on **non-topic content** — a raw-SQL (`userEditedSQL`) tile or a bare base-view query — which those roles otherwise can't see. (Model **access grants** still apply unless the grant sets `access_boostable: true`.) **Dashboard-only:** Access Boost lifts the restriction on the **dashboard** view of those tiles. It does **not** extend to the underlying **workbook** — a restricted role still can't open the workbook's non-topic or SQL tabs (or see the query behind the tile) regardless of Access Boost. **⚠️ Confirm before boosting — it loosens access controls.** Access Boost deliberately exposes content that restricted roles can't otherwise see, and non-topic / raw-SQL tiles bypass topic-scoped governance (access filters, `always_where`) — so boosting can surface data those controls would normally withhold. **Do not apply Access Boost autonomously or as a reflexive fix for "they can't see it."** First: 1. **Understand what the document exposes** — what data the boosted tiles show, at what grain, and whether any of it is sensitive. 2. **Confirm intent with the requester** — that they really mean to grant *these specific* Viewer / Restricted Querier users or groups visibility into that content. State the implication back to them and get an explicit go-ahead before running the command. 3. **Prefer the narrowest scope** — boost specific users/groups (`add-permits`) over the org-wide `organizationAccessBoost`; reach for org-wide only when that's explicitly what's wanted. 4. **Note the governance interaction** — model access grants still apply unless a grant sets `access_boostable: true`; don't treat that as a safety net, confirm intent regardless. **Prerequisite (org capability, not in the CLI):** the org must have `allowsDocumentAccessBoost` enabled (and `allowsMemberToProvisionAccessBoost` for non-admins to grant it). This is an instance/admin setting — if it's off, the document-level flags below are silently cleared. It's a gate; it does **not** itself turn Access Boost on anywhere. Once you've confirmed intent, there are two activation levers, both **scoped to a single document**: ```bash # Boost specific users/groups on this document (add-permits / update-permits) omni documents add-permits --body '{ "userGroupIds": ["group-uuid"], "role": "VIEWER", "accessBoost": true }' # Boost the "everyone in the org" principal on this document omni documents update-permission-settings --body '{ "organizationAccessBoost": true, "organizationRole": "VIEWER" }' ``` `update-permission-settings` (PUT) also carries the document's other toggles — `canDownload`, `canDrill`, `canSchedule`, `canUpload`, `canUseDashboardAi`, `canUseTimezoneOverride`, `canViewWorkbook`, `requirePullRequestToPublish`. Note `organizationAccessBoost` boosts the org-default principal on **this** document only — it is not an org-wide switch. ## Folder Permissions ```bash # Get omni folders get-permissions # Set omni folders add-permissions --body '{ "permissions": [{ "type": "group", "id": "group-uuid", "access": "view" }] }' ``` ## Schedules ```bash # List schedules omni schedules list # Create schedule omni schedules create --body '{ "identifier": "dashboard-identifier", "name": "Weekly Dashboard - Monday 9am PT", "schedule": "0 9 ? * MON *", "timezone": "America/Los_Angeles", "destinationType": "email", "content": "dashboard", "format": "pdf", "subject": "Weekly dashboard", "recipients": ["team@company.com"] }' # Manage recipients for an existing schedule omni schedules recipients-get omni schedules add-recipients --body '{ "recipients": ["team@company.com"] }' ``` > **`schedules update` is a full replacement, not a patch**, and it returns > `"success": true` either way. Any optional property you leave out is reset to > its default — filter values cleared, **the alert condition removed**, > `maxRowLimit` and the presentation flags back to defaults. `--help` lists > every property that resets. > > **You cannot round-trip `schedules get` into it.** The read shape is not the > write shape: the GET nests presentation options under `metadata` and > recipients under `destinations[]`, while the update body wants `subject`, > `maxRowLimit`, `recipients` and `destinationType` flat — feeding the GET > straight back 400s on `destinationType`. Build the body from > `omni schedules update --schema` and carry across every value you mean to > keep. **Email-only users** are the recipients that exist only to receive deliveries: `omni users list-email-only` / `create-email-only` / `create-email-only-bulk`, and `delete-email-only-bulk` (CLI ≥ 1.4.0). The bulk delete is **partially successful by design** — a 200 carries `deleted[]` alongside `notFound: {emails: [], userIds: []}`, so check those two arrays, not the status, before reporting it done. ## AI Credits Read and manage AI credit controls and usage (entity-group commands and usage reads require CLI ≥ 1.1.2). Org-level controls require the AI-admin permission; per-user controls and usage require manage-user-attributes; entity-group controls and usage require add/remove-users. Per-user and per-entity-group limits are also behind instance feature flags. ```bash # Org-level credit controls omni ai credit-controls-get omni ai credit-controls-update --body '{ ... }' # run with --schema for the body shape # Per-user and per-entity-group limits omni ai credit-controls-users-list omni ai credit-controls-users-update --body '{ ... }' omni ai credit-controls-entity-groups-list omni ai credit-controls-entity-groups-update --body '{ ... }' # Usage for the current billing period (reads work even when controls editing is disabled) omni ai credit-usage-users-read --body '{ "userIds": [""] }' omni ai credit-usage-entity-groups-read --body '{ ... }' ``` > **Gotcha**: `credit-usage-users-read` takes **membership ids** (the user's membership in this organization), not base user ids — an unknown id 404s the whole request, naming the offending id. At most 1000 ids per request, no duplicates; users with no usage report 0. ## Color Palettes Custom chart color palettes for the organization (CLI ≥ 1.3.1). Writes need the **Manage Config** permission. Run `create` / `update` with `--schema` for the body; `type` is `discrete` (colors categories in order) or `continuous` (a gradient for numeric scales). ```bash omni color-palettes list # custom palettes only; built-ins are not listed omni color-palettes get omni color-palettes create --body '{ "name": "Brand colors", "type": "discrete", "colors": ["#1f77b4", "#ff7f0e"] }' omni color-palettes update --body '{ "colors": ["#1f77b4", "#2ca02c"] }' omni color-palettes delete ``` - **Updates and deletes reach every chart that uses the palette.** An update recolors those charts in place; a delete makes them fall back to the org default palette. Neither reports which charts were affected. - The org's current default palette cannot be deleted, and names must be unique per `type`. ## Uploads Manage CSV/spreadsheet uploads (the files users upload to query alongside warehouse data). `create` and `replace-data` are multipart file uploads: pass the CSV path with `--file` and the other fields as flags. Run either with `--schema` for the full field list. (On CLI < 1.2.0 these flags don't exist — the same fields go through `--body` as *multipart* fields, with file paths as the binary values.) ```bash # List uploads — filter by connection or model, search by file name omni uploads list --connection-id omni uploads list --model-id --search-term "forecast" --type csv # Upload a CSV into a model (--file and --model-id are required) omni uploads create --file ./forecast.csv --model-id # Upload onto a branch, overriding the generated view name omni uploads create --file ./forecast.csv --model-id \ --branch-name --view-name forecast_v2 # Replace the data behind an existing upload, keeping its id (CLI ≥ 1.1.2) omni uploads replace-data --file ./forecast_november.csv # Delete an upload omni uploads delete ``` `replace-data` fully replaces the upload's data while its id stays stable — views and document tabs reference the upload by id, so they serve the new data with no model or document changes. Column renames/removals may break content referencing the old columns, so compare headers before replacing. For `uploads list --model-id`: shared models return connection uploads; workbook models return their own uploads. > **Flags vs. `--body`**: `--file` takes a **path**, not file contents, and `--branch-id` / `--branch-name` are mutually exclusive. `--file` and `--model-id` are required on `create` (`--file` on `replace-data`) unless you supply the same fields through `--body`, which on these two commands carries **multipart fields** — binary values are still file paths, not inline data. ## Verification After Changes Admin operations can silently fail or partially apply. Always read back the state after any write to confirm the change took effect. ### After User Operations ```bash # After creating or updating a user, verify they exist with correct state omni scim users-list --filter 'userName eq "newuser@company.com"' ``` Check that: `active` matches what you set, `displayName` is correct, and the user ID was returned (not an error). ### After Group Operations ```bash # After creating a group or modifying members, verify membership omni scim groups-list ``` Check that: the group exists with the expected `displayName`, and `members` array contains the expected user UUIDs. ### After Permission Changes ```bash # After setting document permissions, verify the principal and role omni documents access-list # For a specific user, also check effective permissions omni documents get-permissions --user-id # After setting folder permissions, verify omni folders get-permissions ``` Check that: the principal is listed and the `role` matches what you set (`VIEWER`, `EDITOR`, etc.). ### After User Attribute Changes ```bash # Verify the user's assigned attribute value was set omni scim users-list --filter 'userName eq "user@company.com"' ``` Check that: the response contains the target user, the user's `urn:omni:params:1.0:UserAttribute` object includes the requested attribute name, and the value exactly matches what you set. `omni user-attributes list` only verifies that the attribute definition exists. After a definition change (`user-attributes create` / `update` / `delete`), read it back with `omni user-attributes list` — `Number` defaults come back as strings, so compare them as strings. If the attribute is used for row-level security (`access_filters`), test it by running a query as the target user: ```bash omni query run --body '{ "query": { ... }, "userId": "" }' ``` Verify the results are correctly filtered — the user should only see rows matching their attribute value. ### After Schedule Operations ```bash # Verify schedule was created with correct settings omni schedules list -o json # Verify recipients were added omni schedules recipients-get ``` Check that: the created schedule id appears in the list and the returned fields match the requested `schedule` cron, `timezone`, `destinationType`, `content`, `format`, and dashboard `identifier`. If the list/get response shape is not parseable, report that schedule setting verification was inconclusive instead of silently treating an empty parser result as success. Always verify recipients with `recipients-get`. ### Verification Checklist | Operation | Verify With | What to Check | |-----------|-------------|---------------| | Create/update user | `omni scim users-list --filter ...` | User exists, `active` status correct | | Create/update group | `omni scim groups-list` | Group exists, members list correct | | Set document permissions | `omni documents get-permissions` | Access level and target correct | | Set folder permissions | `omni folders get-permissions` | Access level and target correct | | Set user attribute | `omni scim users-list --filter ...` | User attribute extension contains requested value | | User attribute + access filter | `omni query run` with `userId` | Row-level filtering works | | Create schedule | `omni schedules list` | Schedule settings correct | | Add recipients | `omni schedules recipients-get` | All recipients listed | ## Cache and Validation ```bash # Reset cache policy omni models cache-reset --body '{ "resetAt": "2025-01-30T22:30:52.872Z" }' # Content validator (find broken field references across all dashboards and tiles) # Useful for blast-radius analysis: remove a field on a branch, then run the # validator against that branch to see what content would break. # See the Field Impact Analysis section in omni-model-explorer for the full workflow. omni models content-validator-get # Run against a specific branch (e.g., after removing a field) omni models content-validator-get --branch-id # On large content the validator may only check references; plan every query (slower) omni models content-validator-get --force-full-validation true # Git configuration omni models git-get ``` ## Docs Reference - [Connections](https://docs.omni.co/api/connections.md) · [Users (SCIM)](https://docs.omni.co/api/users.md) · [Groups (SCIM)](https://docs.omni.co/api/user-groups.md) · [User Attributes](https://docs.omni.co/api/user-attributes.md) · [Document Permissions](https://docs.omni.co/api/document-permissions.md) · [Folder Permissions](https://docs.omni.co/api/folder-permissions.md) · [Schedules](https://docs.omni.co/api/schedules.md) · [Schedule Recipients](https://docs.omni.co/api/schedule-recipients.md) · [Content Validator](https://docs.omni.co/api/content-validator.md) · [API Authentication](https://docs.omni.co/api/authentication.md) ## Related Skills - **omni-model-builder** — edit the model that access controls apply to - **omni-content-explorer** — find documents before setting permissions - **omni-content-builder** — create dashboards before scheduling delivery - **omni-embed** — manage embed users and user attributes for embedded dashboards