---
name: omni-client-library
description: "Reference and coding guide for the Omniverse Client Library (omni.client / OmniClient.h) — a general-purpose file I/O, authentication, live collaboration, and storage abstraction layer for any file type (not just USD). Use this skill whenever the user mentions omni.client, OmniClient, Omniverse Client Library, Nucleus file operations, omniverse:// URLs, OmniStorage, live updates, live sessions, or any file/storage operations targeting Omniverse servers. Also use when the user is writing Kit extensions that read/write files, working with S3/Azure/HTTP through Omniverse, handling Omniverse authentication, or doing live collaborative editing. Trigger even if the user just says 'read a file from Nucleus' or 'upload to OmniStorage' without naming the library explicitly."
---

**Scope:** Runtime API usage and configuration. Building, packaging, or modifying the client-library source tree is out of scope for this skill (use the repository’s own docs for that).

# Omniverse Client Library

The Omniverse Client Library is a **general-purpose file I/O and collaboration layer** — it is not specific to USD or any particular file format. It handles reading, writing, listing, copying, moving, and deleting *any* file type (USD, MDL, textures, configs, binaries, etc.) across multiple storage backends: Nucleus, OmniStorage (gRPC), S3, Azure, HTTP, and local filesystem. It provides a single API surface in C with Python bindings.

**The library operates on raw bytes** — it does not parse or understand file contents. If a user needs to parse USD files (resolve references, read scene structure, etc.), they need additional dependencies beyond this library. See [Working with USD Files](#working-with-usd-files) below.

**Public docs**: https://docs.omniverse.nvidia.com/kit/docs/client_library/latest/index.html
**Connect samples**: https://github.com/NVIDIA-Omniverse/connect-samples

## When to Read Reference Files

These files cover **using** the library from application or extension code. For building or changing the client-library itself, use the repository’s documentation.

| Task | Reference |
|---|---|
| C/C++ against `OmniClient.h` | `references/c-api.md` |
| Python `omni.client` | `references/python-api.md` |
| Config, env vars, auth, routing knobs | `references/configuration.md` |
| Callbacks/threading, URL routing order, HTTP list/stat cache, live sessions vs live objects | `references/runtime-behavior.md` |

Read the relevant reference(s) before writing code or answering detailed API questions.

## Key Concepts

### URL Scheme = Backend

Every operation takes a URL. The scheme determines which provider handles it:

| Scheme | Backend | Capabilities |
|---|---|---|
| `omniverse://` / `omni://` | Nucleus server | Full CRUD, live updates, checkpoints, ACLs, channels |
| `omniverse://` (with OmniStorage registered) | OmniStorage gRPC | Full CRUD, live sessions, versioning |
| `http://` / `https://` | Generic HTTP | Read-only (stat via HEAD, read via GET) |
| `https://*.amazonaws.com` | S3 | Read-only (stat, list, read) |
| `https://*.blob.core.windows.net` | Azure Blob | Read-only (stat, list, read; write with SAS) |
| `file:///` or raw paths | Local filesystem | Full CRUD, directory watching |

OmniStorage's `StorageProviderFactory` registers with scheme `*` and gets first routing priority — if it recognizes a URL via discovery or env vars, it intercepts before scheme-specific routing.

### Async Pattern

All I/O operations are asynchronous at their core. In C, every function takes a `void* userData` and callback, returns an `OmniClientRequestId`. In Python, each operation has three variants:

```python
# Blocking — simplest, waits for completion
result, entries = omni.client.list("omniverse://server/path")

# Async — returns asyncio.Future
result, entries = await omni.client.list_async("omniverse://server/path")

# Callback — returns Request object for manual control
request = omni.client.list_with_callback("omniverse://server/path", my_callback)
request.wait()   # optional: block until done
request.stop()   # optional: cancel
```

In C:
```c
OmniClientRequestId id = omniClientList(
    "omniverse://server/path",
    userData,
    [](void* ud, OmniClientResult result, uint32_t numEntries, OmniClientListEntry const* entries) {
        // handle results
    }
);
omniClientWait(id);  // optional: block
omniClientStop(id);  // optional: cancel
```

### Lifecycle

```python
# Python
import omni.client
omni.client.initialize()
# ... use the library ...
omni.client.shutdown()
```

```c
// C
omniClientInitialize(kOmniClientVersion);
// ... use the library ...
omniClientShutdown();
```

`initialize()` / `shutdown()` are optional but encouraged — they enable extra checks. **Must only be called once per process.** In test code, use `setUpModule` / `tearDownModule`, not per-test setup.

### Authentication Priority

For Nucleus servers, auth is attempted in order:
1. Application callbacks (`register_authentication_callback`)
2. Environment variables (`OMNI_USER` + `OMNI_PASS`)
3. Cached tokens from previous logins
4. Device flow (if `OMNICLIENT_NO_BROWSER=1` or device flow callback registered)
5. Browser-based interactive login

For OmniStorage with discovery, auth uses OAuth2 authorization-code flow by default, or device flow when `OMNICLIENT_NO_BROWSER=1`.

### Live Collaboration

Two systems exist:

**Live Sessions** (newer, works across providers): Create/list/join sessions on a base layer URL. Provides user presence (join/leave events) and messaging.

**Live Objects** (Nucleus-only): Direct read/update of omni-objects with jitter reduction. Requires calling `live_process()` from the main thread.

Both are detailed in the reference files.

## Common Tasks — Quick Reference

### Read a file
```python
# Works with any file type — USD, textures, configs, binaries, etc.
result, version, content = omni.client.read_file("omniverse://server/path/texture.png")
if result == omni.client.Result.OK:
    data = memoryview(content).tobytes()
```

### Write a file
```python
result = omni.client.write_file("omniverse://server/path/config.toml", file_bytes)
```

### List a directory
```python
result, entries = omni.client.list("omniverse://server/Projects/")
for entry in entries:
    print(f"{entry.relative_path}  size={entry.size}  flags={entry.flags}")
```

### Check if a file exists
```python
result, entry = omni.client.stat("omniverse://server/path/scene.usd")
exists = (result == omni.client.Result.OK)
```

### Watch a directory for changes
```python
def on_change(result, event, entry):
    print(f"{event}: {entry.relative_path}")

request = omni.client.list_subscribe_with_callback(
    "omniverse://server/Projects/",
    lambda r, entries: None,  # initial list callback
    on_change                  # subscribe callback
)
```

### Copy / Move
```python
result = omni.client.copy("omniverse://src/file.usd", "omniverse://dst/file.usd",
                           behavior=omni.client.CopyBehavior.OVERWRITE)
result, copied = omni.client.move("omniverse://src/file.usd", "omniverse://dst/file.usd")
# copied=True means cross-server copy+delete; False means server-side rename
```

### URL manipulation
```python
url = omni.client.combine_urls("omniverse://server/Projects/", "SubDir/file.usd")
parsed = omni.client.break_url("omniverse://server:3009/Projects/file.usd?branch=main&checkpoint=5")
normalized = omni.client.normalize_url(some_url)
```

### Authentication callback
```python
def my_auth(prefix):
    if "my-server" in prefix:
        return ("username", "password")  # or return "bearer_token"
    return None  # fall through to next auth method

reg = omni.client.register_authentication_callback(my_auth)
# ... do work ...
reg = None  # unregister by dropping the Registration object
```

### Create a checkpoint (version snapshot)
```python
result, query = omni.client.create_checkpoint(
    "omniverse://server/Projects/file.usd",
    comment="Before refactor",
    force=True  # create even if no changes
)
```

## Working with USD Files

The Client Library reads and writes files as raw bytes — it has no built-in understanding of USD or any other file format. To actually parse USD content (resolve references, inspect scene hierarchy, read properties), users need two additional components:

1. **usd-core** (`pip install usd-core`) — The OpenUSD Python library (`pxr.Usd`, `pxr.Sdf`, etc.) for parsing USD files
2. **NVIDIA USD Resolver** (https://github.com/NVIDIA-Omniverse/usd-resolver) — An `ArResolver` plugin that teaches USD's asset resolution system how to find and load files via `omniverse://` URLs

Without the USD resolver, `Usd.Stage.Open("omniverse://server/file.usd")` will fail because USD doesn't know how to resolve `omniverse://` paths. The resolver bridges this gap by using the Client Library under the hood.

**Typical workflow for USD + Omniverse:**
```python
# 1. Install: pip install usd-core omni-client
# 2. Install/configure the NVIDIA USD resolver plugin
# 3. Then you can open USD files directly from Omniverse:
from pxr import Usd, Sdf
stage = Usd.Stage.Open("omniverse://server/Projects/scene.usd")

# Or use the Client Library to download first, then parse locally:
import omni.client
result, _, content = omni.client.read_file("omniverse://server/scene.usd")
# Write to temp file, open with Sdf.Layer.FindOrOpen(temp_path)
```

**When users ask about USD references, sublayers, or payloads**, always mention that:
- The Client Library itself only handles the file transfer
- Parsing USD structure requires `usd-core` (pxr)
- Resolving `omniverse://` paths within USD requires the NVIDIA USD Resolver plugin
- Text-based `.usda` files can be grep-parsed as a quick alternative, but `.usd`/`.usdc` (crate binary format) require the `pxr` libraries
