Skip to content

Configuration

VariableDescription
QUALIA_API_KEYYour API key (used if not passed to constructor)
QUALIA_BASE_URLOverride the API base URL (default: https://api.qualiastudios.dev)
QUALIA_ACCOUNTOptional account UUID; default the API key’s own (see Account)
QUALIA_WORKSPACEWorkspace UUID to act in instead of the API key’s own workspace; rarely needed
QUALIA_QUIETSet to 1 to silence the live per-phase upload progress written to stderr
QUALIA_UPLOAD_SERVER_BUDGET_SECONDSHow long the server may go without reporting an upload step before the client gives up (default 3600)
QUALIA_SERVER_STALE_SECONDSHow long the server’s live preparation heartbeat may go stale before the client gives up (default 240)
QUALIA_UPLOAD_BOUNDARY_CACHE_DIRDirectory for the upload boundary cache; set to an empty string to disable it (see below)
from qualia import Qualia
# Uses QUALIA_API_KEY environment variable
client = Qualia()
# Or pass explicitly
client = Qualia(api_key="your-api-key")

Each setting comes from the constructor argument, then the environment variable, then the CLI’s config file (written by qualia auth login). The file’s base URL and account are used only together with its key: once QUALIA_API_KEY is set or api_key= is passed, nothing is read from the file.

Tasks, datasets, episodes, versions, mixtures and checks live under an account, at accounts/{account}/…. A new user has one personal account: an organization of one, with one workspace.

An API key belongs to exactly one account, so the client needs no account setting. It names the account accounts/- in every call, and the API reads - as the key’s own account. No call is made first.

from qualia import Qualia
client = Qualia(api_key="your-api-key")
client.tasks.list() # GET /v1/accounts/-/tasks: the key's own account

account=, QUALIA_ACCOUNT and qualia auth login --account remain as an explicit override; with a key they can only name the key’s own account.

To learn the account’s UUID, ask GET /v1/whoami (any valid key may). client.whoami() returns that answer: the account and workspace ids and names, and the key’s name, prefix, scope (organization, workspace or restricted), scopes and expiry.

A full resource name, such as accounts/<ACCOUNT_UUID>/datasets/<DATASET_UUID>, carries its own account. An explicit account must be a lowercase UUID; accounts/<uuid> is accepted too. To find yours, see Finding your account id.

An upload’s server-side phases — staged-data verification and ingest — are bounded by silence, not elapsed time. Ingest has no upper bound: it parses every bag and transcodes every camera, so a large upload is free to take as long as it takes.

QUALIA_UPLOAD_SERVER_BUDGET_SECONDS is how long the server may go without reporting a step on your upload; each step it does report restarts that clock. Two signals end the wait earlier: the server publishing a failure for the upload, and its live preparation heartbeat going stale for QUALIA_SERVER_STALE_SECONDS (which must stay under the 300 s lease TTL).

Raise them for a slow link or a very large session; lower them to fail faster in CI. To watch progress instead of waiting blind, pass on_progress to client.data.upload().

client.data.upload() sizes its send from the machine it runs on, so most uploads need no setting. Every value below is bounded; a value out of range fails at import and names the variable.

VariableDefaultWhat it bounds
QUALIA_UPLOAD_BUFFER_BUDGET_BYTES1/64 of memory, 256 MiB–1 GiBPacked bytes held in memory at once, across every file. Peak memory is about 3–4× this
QUALIA_UPLOAD_R2_CONCURRENCY16Storage connections, so PUTs in flight (1–64)
QUALIA_UPLOAD_WINDOWS_IN_FLIGHTstarts at 3, adapts between a floor and 864 MiB windows in flight. The floor keeps every storage connection busy; setting this caps it (1–8)
QUALIA_UPLOAD_TREE_WINDOWS2 with a budget of 512 MiB or more, else 1256 MiB groups of small files packed and sent at once, at most one per 192 MiB of budget (1–4)
QUALIA_UPLOAD_FILE_CONCURRENCYhalf the cores, 4–8Large (over 64 MiB) or edited files packed at once; the CLI’s --upload-workers (1–32)

“Memory” is the lower of the machine’s RAM and the smallest memory limit on the process’s cgroup or any parent of it, such as a container’s limit or a systemd service’s MemoryMax/MemoryHigh. “Cores” are the ones the process may run on.

The live progress reports the window depth it settled at (upload window depth …), and the receipt’s transfer_metrics has each phase’s time and request count. On a thin uplink, lower QUALIA_UPLOAD_R2_CONCURRENCY before anything else; on a small machine, lower QUALIA_UPLOAD_BUFFER_BUDGET_BYTES.

client.data.upload() keeps a small on-disk cache of chunk boundaries so that an edited file can reuse its unchanged chunks even after the client process restarts. It holds chunk IDs, offsets and sizes only — no file bytes, API keys, presigned URLs or manifest IDs — and every reuse is re-authorized and re-checked for presence on the server at upload time. A file’s cached boundaries are only ever compared against a fresh chunking of the current file; the cache cannot change how a file is cut.

It saves stored bytes on changed-file re-uploads after a restart. It does not make uploads faster, and no speedup is claimed.

Location. $XDG_CACHE_HOME/qualia/upload-boundaries-v1, or ~/.cache/qualia/upload-boundaries-v1 when XDG_CACHE_HOME is unset. The cache is one SQLite file, boundaries.sqlite3, in that directory. The directory is created with mode 0700 and must stay private and owned by you, otherwise the cache is treated as unavailable.

Overriding or disabling. Set QUALIA_UPLOAD_BOUNDARY_CACHE_DIR to a private directory to move it, or to an empty string to disable it. The cache is also disabled automatically when its directory is inside the directory being uploaded (for example, uploading your home directory with the default location), so cache files never become part of a dataset.

What is cached. Changed files of at most 64 MiB, cut into between 2 and 8,192 chunks, in ordinary directory uploads. A file small enough to be a single chunk is skipped: there is no interior boundary for a later edit to reuse. Entries are keyed by the API base URL, the repository authorized for the upload, the file’s format label, the native chunker profile, and the file’s content hash, so a different account, server, repository or SDK build never matches another’s entry.

Bounds and eviction. The cache holds at most 128 entries and 32 MiB of encoded hints, and the database file is capped at 64 MiB. When a new entry would exceed the entry or payload limit, the least recently used entries (by last store or load time) are deleted first, before the new one is written.

When it is written. An upload collects its hints in memory, bounded by the same 128-entry and 32 MiB limits, and writes them once after the transfer finishes and before the dataset is published. An upload that fails during transfer writes nothing. One that fails after that, such as a source file changing before publication, may leave its hints behind; they are harmless, because every hint is checked against the server before it is used. When a directory offers more hints than the cache can hold, the largest files win, since they are the ones a later edit can save the most bytes on. Nothing is vacuumed, repaired or migrated automatically.

Failure behaviour. Any filesystem, SQLite or data error is a cache miss, and a failed write is skipped; the upload continues on the ordinary path. A cache damaged by a crash or power loss stays a miss until you delete the directory. Deleting it at any time is safe.

Use a custom httpx client for advanced configuration like timeouts and connection limits.

import httpx
from qualia import Qualia
custom_client = httpx.Client(
timeout=60.0,
limits=httpx.Limits(max_connections=10),
)
client = Qualia(api_key="...", httpx_client=custom_client)

Automatically close the client when done using a context manager.

from qualia import Qualia
with Qualia(api_key="...") as client:
models = client.models.types()
# Client is automatically closed