privatemind is the platform's Python SDK. It covers the surfaces that go beyond the OpenAI-compatible API: distributed training jobs, image generation, and batch work. For chat completions, embeddings, and everything else OpenAI-shaped, keep using the OpenAI SDK with your PrivateMind base URL.
Install
pip install privatemindWorks with uv too: uv add privatemind. The package and the import share the name, and pip install privatemind-python resolves to the same package. Python 3.11 or newer.
Pre-configured in every workspace
The SDK ships pre-installed and pre-configured in every PrivateMind workspace. Open a notebook and import it; there is nothing to set up:
import privatemindThe workspace injects everything the SDK needs as environment variables:
| Variable | What it provides |
|---|---|
PRIVATEMIND_TOKEN |
a scoped bearer token issued to the workspace |
PRIVATEMIND_URL |
the platform API base URL (image generation, batch) |
PRIVATEMIND_APP_URL |
the app backend base URL (training jobs) |
PRIVATEMIND_TARGET_CLUSTER |
the GPU cluster the workspace runs on |
PRIVATEMIND_TRAINING_IMAGE |
the default training worker image |
PRIVATEMIND_HOME_VOLUME / PRIVATEMIND_HOME_PATH |
the home Volume used to stage promoted code |
Every example in this section runs as-is inside a workspace: no token to paste, no URL, no cluster name. Outside a workspace (a laptop, CI) you supply a token yourself, as below.
Two base URLs
The SDK talks to two services:
| Service | Configure with | Default | Serves |
|---|---|---|---|
| Platform API | base_url= or PRIVATEMIND_URL |
https://api.privatemind.com |
image generation, batch |
| App backend | app_url= or PRIVATEMIND_APP_URL |
https://privatemind.com |
training jobs |
Both default to the production platform, so off-platform you configure a token and nothing else. The kwarg wins over the environment variable, which wins over the default. Inside a workspace both variables are injected and you never think about either.
Authentication
The client resolves the bearer token in order:
token=passed toClient(...)- the
PRIVATEMIND_TOKENenvironment variable - the
~/.privatemind/authfile
Outside a workspace, the token is a PrivateMind API key: the full PMIND...:abcdef... string. If you keep it in ~/.privatemind/auth, the SDK checks the file before reading it: it must be mode 0600, owned by the user running Python, and not a symlink. Anything else raises AuthError and says what to fix.
Base URLs must be https://. An escape hatch exists for local development (allow_insecure_http=True, or PRIVATEMIND_ALLOW_INSECURE_HTTP=1); leave it off anywhere that matters.
Module functions and the explicit Client
Every API call exists twice: as a module-level function (from privatemind import generate_image) and as a method on Client. The module functions share one process-wide client, created lazily from the environment on first use. When credentials or URLs change mid-session, as they do in a long-lived notebook, reconfigure(...) builds a replacement and closes the old one:
import privatemind
privatemind.reconfigure(token="PMIND...:abcdef...") # keeps the injected URLsFor explicit control, construct a Client yourself and use it as a context manager (or call close() when done). Configuration is resolved eagerly, so a missing token fails at construction, not on the first request:
from privatemind import Client
with Client(
base_url="https://api.privatemind.com", # optional: this is the default
app_url="https://privatemind.com", # optional: this is the default
token="PMIND...:abcdef...",
) as pm:
for run in pm.list_jobs():
print(run.name, run.phase)Client also takes timeout= (per-request seconds, default 30) and verify= (TLS verification, on by default); reconfigure(...) accepts the same keyword arguments. Every module function accepts client= to route a single call through an explicit client. Clients refuse to pickle, because the token would travel with them: construct one per process.
Timeouts and retries
Each request gets the client's timeout (default 30 seconds). Raise it for slow calls: image generation at high step counts, large batch result downloads.
Retries follow one rule: reads retry, writes never do.
| Calls | Behavior |
|---|---|
| Reads: job status, batch status, listings, file downloads | Up to 3 attempts on network failures, timeouts, and 5xx responses. A 503 waits for the server's Retry-After (capped at 60 seconds); everything else backs off exponentially with jitter. |
| Writes: submit a job, upload a file, create or cancel a batch, generate an image | Sent exactly once, so nothing non-idempotent is ever duplicated. A transient failure raises ServerError, and you decide whether to resend. |
4xx responses are never retried. A 429 raises RateLimitError immediately, carrying the server's Retry-After; the SDK does not sleep on your behalf.
Errors
Everything the SDK raises derives from PrivatemindError. HTTP-mapped errors carry the response status_code and the server's error message; errors raised before a request leaves have status_code = None.
| Exception | Raised when |
|---|---|
ConfigError |
configuration cannot be resolved: an http:// URL without the escape hatch, or an invalid URL scheme |
AuthError |
no token, an unsafe ~/.privatemind/auth file, or HTTP 401 |
ForbiddenError |
HTTP 403: authenticated but not permitted. Subclasses AuthError, so except AuthError catches it too |
ValidationError |
a bad argument caught client-side, or HTTP 400 |
NotFoundError |
HTTP 404 |
ConflictError |
HTTP 409, e.g. a job name that already exists |
RateLimitError |
HTTP 429; retry_after holds the parsed Retry-After header in seconds, or None |
ServerError |
HTTP 5xx after retries are exhausted, and network or timeout failures |
PrivatemindError |
the base class; raised directly only for statuses with no mapping above |
Validation is fail-fast: a malformed image size, an invalid job name, or an out-of-range worker count raises ValidationError before any request is made.
from privatemind import NotFoundError, PrivatemindError, RateLimitError, get_job
try:
run = get_job("tj-abc12")
except NotFoundError:
print("no such job")
except RateLimitError as e:
print(f"rate limited; retry in {e.retry_after or 30:.0f}s")
except PrivatemindError as e:
print(e.status_code, e.message)Where next
- Training jobs: submit and manage distributed training, promote a notebook function into a distributed job.
- Image generation: text to image in one typed call.
- Batch: bulk chat-completion work at 50% of the normal token rate.
- Workspaces: the environment the SDK lives in, pre-wired.