Get API key

Overview

The platform's Python SDK: training jobs, image generation, and batch work. On PyPI as privatemind; pre-installed in every workspace.

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

shell
pip install privatemind

Works 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:

Python
import privatemind

The 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:

  1. token= passed to Client(...)
  2. the PRIVATEMIND_TOKEN environment variable
  3. the ~/.privatemind/auth file

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:

Python
import privatemind

privatemind.reconfigure(token="PMIND...:abcdef...")  # keeps the injected URLs

For 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:

Python
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.

Python
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.