<!-- Source: https://docs.biosimulant.com/cli -->

# Biosimulant CLI

There is one headless `biosimulant` CLI. The same package, command parser, JSON schema and exit codes run in Studio, local terminals, CI, servers and containers.

The CLI drives the [Biosimulant runtime](/runtime) on your own machine. It can't create account workspaces, managed Runs or Evidence Passports. Those come from [Studio](/studio), the [Developer API](/developer-api) or an [agent](/agents). Its registry token and an agent's authorization are separate credentials for separate services, so see [Which surface do I want?](/overview/surfaces) if you're not sure which you need.

```bash
pipx install biosimulant
biosimulant doctor
```

## Common commands

```bash
biosimulant auth login        # only needed for private labs and publishing
biosimulant labs pull acme/cell-study@1.0.0
biosimulant labs validate ./cell-study
biosimulant labs run ./cell-study --json
biosimulant labs publish ./cell-study
```

`biosimulant validate` and `biosimulant run` are aliases for the `labs` commands. To build and publish a package, see [Package and publish](/hub/publish).

## Install and upgrade

The `biosimulant` package on PyPI is the CLI. `pipx` keeps it isolated and puts the command on your `PATH`:

```bash
pipx install biosimulant
pipx upgrade biosimulant
pipx install "biosimulant==0.0.28"   # an exact release
```

`uv` works the same way:

```bash
uv tool install biosimulant
uv tool upgrade biosimulant
uv tool install "biosimulant==0.0.28"
```

In a virtual environment or CI job, pin the version:

```bash
python -m pip install "biosimulant==0.0.28"
python -m biosimulant --version
```

Studio workspaces already include the CLI, so don't reinstall it there.

### Shell completion

For bash:

```bash
register-python-argcomplete biosimulant >> ~/.bash_completion
```

For zsh:

```bash
autoload -U bashcompinit
bashcompinit
eval "$(register-python-argcomplete biosimulant)"
```

## Registries and authentication

The Hub is the default registry, so a plain reference resolves there. For another registry, put its host in front:

```text
acme/cell-study@1.0.0
registry.example.com/acme/cell-study@1.0.0
```

Versions are immutable. `labs pull` without a version gets the registry's current default, so pin the version for reproducible work. Publishing needs an explicit package name and version.

### Sign in

**Most work needs no sign-in.** Local runs (`biosimulant labs run`) and pulls of
public labs are anonymous. You need credentials only to pull a private lab or to
publish.

On a machine with a browser, sign in there:

```bash
biosimulant auth login --web
```

This opens a Biosimulant page, asks you to approve a key for this machine, and
hands it back to the CLI over a listener on `127.0.0.1`. The key never travels
through a URL and you never paste anything. It appears in the
[developer console](https://studio.biosimulant.com/developer) named after your
hostname, and you can revoke it there. Add the global `--no-open` flag to print
the approval URL instead of launching a browser.

If you belong to a [team](/studio/teams), the page asks which account the key
is for. A team key publishes under the team's namespace and reads only the
team's private labs, never your personal ones. To publish under a team with a
personal key, name the team's namespace in the reference instead. See
[Team namespaces](/hub/team-namespaces#publishing-under-the-team).

Without a browser, paste a key instead:

```bash
biosimulant auth login
```

The CLI's registry token **is a developer API key**. Create one at the
[developer console](https://studio.biosimulant.com/developer). It needs the
`packages:read` scope to pull private labs and `packages:write` to publish.

Either way the CLI resolves the key to an account before storing it, so a bad
key fails at sign-in rather than at your next publish. Check an existing
credential the same way:

```bash
biosimulant auth status --verify
```

One sign-in covers both surfaces: `biosimulant runs` reuses the stored
developer key when `BIOSIMULANT_API_KEY` is unset, so you do not have to set an
environment variable as well. Only a developer key is reused, and only against
the default API host.

**Info:**

  Signing in to the Hub is not how you connect Claude or Codex. Agents use the
  MCP server, which has its own browser sign-in and needs no token and no CLI —
  see [Codex and Claude](/agents) and
  [Which surface do I want?](/overview/surfaces).

#### Another registry

The Hub is the default, but any Registry API v1 service works. Pass its host and
put the token on stdin so it never appears in process arguments or shell history:

```bash
printf '%s\n' "$TOKEN" | \
  biosimulant auth login registry.example.com --token-stdin
biosimulant auth status registry.example.com
biosimulant auth logout registry.example.com
```

In CI, put the key on stdin or in `BIOSIMULANT_TOKEN`; `--web` needs a browser
and a person, so it is not for automation.

A registry that doesn't implement the identity endpoint can't verify the token
at sign-in; the CLI says so and stores it anyway. Use `--no-verify` to skip the
check deliberately.

### Where credentials come from

For each operation, the CLI checks these in order:

1. `BIOSIMULANT_TOKEN`
2. Workspace-scoped token exchange
3. A configured credential helper
4. The OS keychain
5. An owner-only credentials file at `~/.config/biosimulant/credentials.json` (mode `0600` on Unix)

Keep credentials out of references, command history, lab files and logs.

Inside a Studio workspace, Studio injects a workspace-bound identity token. The CLI exchanges it for a short-lived operation token only when a private pull or publish starts, so long-lived user credentials never enter the workspace. Automation that uses a developer key needs `packages:read`, `packages:write`, or both.

## Machine output

`--json` prints one schema-v1 result:

```json
{
  "ok": true,
  "data": {},
  "error": null,
  "meta": {
    "schemaVersion": "1",
    "command": "labs.validate",
    "cliVersion": "0.0.28"
  }
}
```

Failures use the same envelope with `ok: false`, `data: null` and a structured `error`.

`--json-stream` prints ordered JSONL events and then exactly one final result. Each event has a `type`, an increasing `seq`, a timestamp, the command, a stage, a message and an optional percentage.

Global flags can go before or after the subcommand:

```bash
biosimulant --json labs validate ./lab
biosimulant labs validate ./lab --json
```

Use `--legacy-json` only while you move off an older machine contract.

### Exit codes

| Code | Meaning |
|---:|---|
| `0` | Success |
| `1` | Operation or internal failure |
| `2` | Invalid command usage |
| `3` | Authentication or permission failure |
| `4` | Resource not found |
| `5` | Invalid content or validation failure |
| `6` | Conflict, including immutable-version conflicts |
| `7` | Capability unavailable in this environment |
| `130` | Cancelled |

Every command exists wherever the CLI runs. If the current environment can't provide what a command needs, it exits with `7`.

## Commands

Generated from `biosimulant` 0.0.37. Global options can go before or after the subcommand:

- `--data-dir`
- `--json`
- `--json-stream`
- `--legacy-json`
- `--no-open`
- `--profile`

### Utilities

#### `biosimulant doctor`

Check the headless CLI and local runtime installation

```text
usage: biosimulant doctor [-h]

options:
  -h, --help  show this help message and exit
```

#### `biosimulant commands list`

List the canonical machine-readable command catalog

```text
usage: biosimulant commands list [-h]

options:
  -h, --help  show this help message and exit
```

### Authentication

#### `biosimulant auth login`

Verify and store a registry token for one registry

```text
usage: biosimulant auth login [-h] [--token-stdin] [--web] [--no-verify]
                              [registry]

positional arguments:
  registry

options:
  -h, --help     show this help message and exit
  --token-stdin
  --web          Sign in through the browser instead of pasting a token
  --no-verify    Store the token without checking it against the registry
```

#### `biosimulant auth status`

Inspect, and optionally verify, credentials for one registry

```text
usage: biosimulant auth status [-h] [--verify] [registry]

positional arguments:
  registry

options:
  -h, --help  show this help message and exit
  --verify    Resolve the stored credential to an account
```

#### `biosimulant auth logout`

Remove credentials for one registry

```text
usage: biosimulant auth logout [-h] [registry]

positional arguments:
  registry

options:
  -h, --help  show this help message and exit
```

### Labs and registry

#### `biosimulant labs init`

Create a local runnable lab

```text
usage: biosimulant labs init [-h] --name NAME [--description DESCRIPTION]
                             [--force] [--empty] [--json]
                             [path]

positional arguments:
  path

options:
  -h, --help            show this help message and exit
  --name NAME
  --description DESCRIPTION
  --force
  --empty               Create only lab.yaml without a starter model
  --json
```

#### `biosimulant labs create`

Create a managed local lab

```text
usage: biosimulant labs create [-h] --name NAME [--description DESCRIPTION]
                               [--force] [--empty] [--json]
                               [path]

positional arguments:
  path

options:
  -h, --help            show this help message and exit
  --name NAME
  --description DESCRIPTION
  --force
  --empty
  --json
```

#### `biosimulant labs list`

List local labs

```text
usage: biosimulant labs list [-h] [--json] [root]

positional arguments:
  root

options:
  -h, --help  show this help message and exit
  --json
```

#### `biosimulant labs get`

Inspect a local lab

```text
usage: biosimulant labs get [-h] [--root ROOT] [--json] [lab]

positional arguments:
  lab

options:
  -h, --help   show this help message and exit
  --root ROOT
  --json
```

#### `biosimulant labs save`

Save a local lab

```text
usage: biosimulant labs save [-h] [--root ROOT]
                             [--manifest-file MANIFEST_FILE]
                             [--wiring-layout-file WIRING_LAYOUT_FILE]
                             [--clear-wiring-layout] [--json]
                             [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --root ROOT
  --manifest-file MANIFEST_FILE
  --wiring-layout-file WIRING_LAYOUT_FILE
  --clear-wiring-layout
  --json
```

#### `biosimulant labs rename`

Rename a local lab

```text
usage: biosimulant labs rename [-h] [--root ROOT] [--json]
                               target_or_name [name]

positional arguments:
  target_or_name
  name

options:
  -h, --help      show this help message and exit
  --root ROOT
  --json
```

#### `biosimulant labs delete`

Delete a local lab

```text
usage: biosimulant labs delete [-h] [--root ROOT] [--yes] [--json] [lab]

positional arguments:
  lab

options:
  -h, --help   show this help message and exit
  --root ROOT
  --yes
  --json
```

#### `biosimulant labs add-model`

Add a model to a lab

```text
usage: biosimulant labs add-model [-h] [--lab LAB] [--root ROOT]
                                  [--alias ALIAS] [--json]
                                  model

positional arguments:
  model

options:
  -h, --help     show this help message and exit
  --lab LAB
  --root ROOT
  --alias ALIAS
  --json
```

#### `biosimulant labs change-model`

Change a lab model

```text
usage: biosimulant labs change-model [-h] [--lab LAB] [--root ROOT] [--json]
                                     alias model

positional arguments:
  alias
  model

options:
  -h, --help   show this help message and exit
  --lab LAB
  --root ROOT
  --json
```

#### `biosimulant labs vendor-model`

Vendor a model into a lab

```text
usage: biosimulant labs vendor-model [-h] [--lab LAB] [--root ROOT]
                                     [--alias ALIAS] [--replace] [--json]
                                     model

positional arguments:
  model

options:
  -h, --help     show this help message and exit
  --lab LAB
  --root ROOT
  --alias ALIAS
  --replace
  --json
```

#### `biosimulant labs inspect-owned`

Inspect lab-owned model sources

```text
usage: biosimulant labs inspect-owned [-h] [--root ROOT] [--json] [lab]

positional arguments:
  lab

options:
  -h, --help   show this help message and exit
  --root ROOT
  --json
```

#### `biosimulant labs validate`

Validate a lab

```text
usage: biosimulant labs validate [-h] [--json] [lab]

positional arguments:
  lab

options:
  -h, --help  show this help message and exit
  --json
```

#### `biosimulant labs capabilities`

Check local CPU, memory, and GPU support

```text
usage: biosimulant labs capabilities [-h] [--target TARGET] [--force]
                                     [--registry-url REGISTRY_URL] [--json]
                                     [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --target TARGET       Destination for auto-pulled registry refs
  --force
  --registry-url REGISTRY_URL
  --json
```

#### `biosimulant labs run`

Run a lab

```text
usage: biosimulant labs run [-h] [--target TARGET] [--force]
                            [--registry-url REGISTRY_URL] [--no-install-deps]
                            [--dependency-root DEPENDENCY_ROOT]
                            [--require-local-capability]
                            [--results-file RESULTS_FILE]
                            [--report-file REPORT_FILE]
                            [--run-input-file RUN_INPUT_FILE] [--json]
                            [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --target TARGET       Destination for auto-pulled registry refs
  --force               Replace an existing auto-pull target
  --registry-url REGISTRY_URL
  --no-install-deps
  --dependency-root DEPENDENCY_ROOT
                        Writable Lab-local dependency state directory for a
                        .bsilab archive
  --require-local-capability
                        Fail before execution unless declared CPU, memory, and
                        GPU needs fit this host
  --results-file RESULTS_FILE
  --report-file REPORT_FILE
                        Write a standalone HTML run report
  --run-input-file RUN_INPUT_FILE
                        JSON object containing parameters and
                        simulation_config overlays
  --json
```

#### `biosimulant labs serve`

Serve a lab locally

```text
usage: biosimulant labs serve [-h] [--target TARGET] [--force]
                              [--registry-url REGISTRY_URL] [--host HOST]
                              [--port PORT] [--open] [--no-open]
                              [--no-install-deps] [--no-agent]
                              [--agent-read-only] [--json]
                              [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --target TARGET       Destination for auto-pulled registry refs
  --force               Replace an existing auto-pull target
  --registry-url REGISTRY_URL
  --host HOST
  --port PORT
  --open                Open browser automatically (default)
  --no-open             Do not open a browser automatically
  --no-install-deps
  --no-agent            Do not expose this lab to a local agent over MCP
  --agent-read-only     Let a connected agent read the lab but not change or
                        run it
  --json
```

#### `biosimulant labs package`

Build a lab package

```text
usage: biosimulant labs package [-h] [--out OUT] [--package PACKAGE_NAME]
                                [--version VERSION]
                                [--visibility {private,public}]
                                [--vendor-dependencies]
                                [--registry-url REGISTRY_URL] [--json]
                                [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --out OUT
  --package PACKAGE_NAME
  --version VERSION
  --visibility {private,public}
  --vendor-dependencies
  --registry-url REGISTRY_URL
  --json
```

#### `biosimulant labs search`

Search a Biosimulant registry

```text
usage: biosimulant labs search [-h] [--page PAGE] [--page-size PAGE_SIZE]
                               [--tags TAGS] [--registry-url REGISTRY_URL]
                               [--json]
                               [query]

positional arguments:
  query

options:
  -h, --help            show this help message and exit
  --page PAGE
  --page-size PAGE_SIZE
  --tags TAGS
  --registry-url REGISTRY_URL
  --json
```

#### `biosimulant labs info`

Inspect registry lab metadata

```text
usage: biosimulant labs info [-h] [--registry-url REGISTRY_URL] [--json]
                             reference

positional arguments:
  reference

options:
  -h, --help            show this help message and exit
  --registry-url REGISTRY_URL
  --json
```

#### `biosimulant labs versions`

List immutable registry versions

```text
usage: biosimulant labs versions [-h] [--page PAGE] [--page-size PAGE_SIZE]
                                 [--registry-url REGISTRY_URL] [--json]
                                 reference

positional arguments:
  reference

options:
  -h, --help            show this help message and exit
  --page PAGE
  --page-size PAGE_SIZE
  --registry-url REGISTRY_URL
  --json
```

#### `biosimulant labs pull`

Pull a registry lab

```text
usage: biosimulant labs pull [-h] [--target TARGET] [--force] [--no-deps]
                             [--registry-url REGISTRY_URL] [--json]
                             reference

positional arguments:
  reference

options:
  -h, --help            show this help message and exit
  --target TARGET
  --force
  --no-deps
  --registry-url REGISTRY_URL
  --json
```

#### `biosimulant labs publish`

Publish an immutable lab version

```text
usage: biosimulant labs publish [-h] [--visibility {private,public}]
                                [--registry-url REGISTRY_URL]
                                lab [reference]

positional arguments:
  lab
  reference

options:
  -h, --help            show this help message and exit
  --visibility {private,public}
  --registry-url REGISTRY_URL
```

#### `biosimulant labs sync-status`

Compare a local lab with its registry version

```text
usage: biosimulant labs sync-status [-h] [--registry-url REGISTRY_URL] [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --registry-url REGISTRY_URL
```

#### `biosimulant labs release validate`

Validate a release manifest

```text
usage: biosimulant labs release validate [-h] [--json] manifest

positional arguments:
  manifest

options:
  -h, --help  show this help message and exit
  --json
```

#### `biosimulant labs release build`

Build a release manifest

```text
usage: biosimulant labs release build [-h] [--out OUT] [--json] manifest

positional arguments:
  manifest

options:
  -h, --help  show this help message and exit
  --out OUT
  --json
```

#### `biosimulant labs release publish`

Build and publish a release manifest

```text
usage: biosimulant labs release publish [-h] [--out OUT]
                                        [--registry-url REGISTRY_URL]
                                        [--dry-run]
                                        manifest

positional arguments:
  manifest

options:
  -h, --help            show this help message and exit
  --out OUT
  --registry-url REGISTRY_URL
  --dry-run
```

#### `biosimulant labs release ci`

Validate, build, and publish a release manifest

```text
usage: biosimulant labs release ci [-h] [--out OUT]
                                   [--registry-url REGISTRY_URL] [--dry-run]
                                   manifest

positional arguments:
  manifest

options:
  -h, --help            show this help message and exit
  --out OUT
  --registry-url REGISTRY_URL
  --dry-run
```

### Compatibility

#### `biosimulant compatibility standard`

Show the installed compatibility standard release

```text
usage: biosimulant compatibility standard [-h]

options:
  -h, --help  show this help message and exit
```

#### `biosimulant compatibility profiles`

List installed compatibility profiles

```text
usage: biosimulant compatibility profiles [-h]

options:
  -h, --help  show this help message and exit
```

#### `biosimulant compatibility show`

Show one exact compatibility profile

```text
usage: biosimulant compatibility show [-h] profile

positional arguments:
  profile

options:
  -h, --help  show this help message and exit
```

#### `biosimulant compatibility validate`

Validate compatibility declarations in model.yaml

```text
usage: biosimulant compatibility validate [-h] manifest

positional arguments:
  manifest

options:
  -h, --help  show this help message and exit
```

#### `biosimulant compatibility compare`

Compare one output port with one input port

```text
usage: biosimulant compatibility compare [-h] [--sample-json SAMPLE_JSON]
                                         source target

positional arguments:
  source                model.yaml#outputs.port_name
  target                model.yaml#inputs.port_name

options:
  -h, --help            show this help message and exit
  --sample-json SAMPLE_JSON
                        Optional JSON value to check
```

### Runtime

#### `biosimulant runtime status`

Inspect the managed runtime

```text
usage: biosimulant runtime status [-h]

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runtime detect-python`

Report the current Python runtime

```text
usage: biosimulant runtime detect-python [-h]

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runtime prepare`

Prepare an isolated execution runtime

```text
usage: biosimulant runtime prepare [-h] [--python PYTHON]

options:
  -h, --help       show this help message and exit
  --python PYTHON
```

### Runs

#### `biosimulant runs list`

List managed runs

```text
usage: biosimulant runs list [-h] [--limit LIMIT] [--cursor CURSOR]

options:
  -h, --help       show this help message and exit
  --limit LIMIT
  --cursor CURSOR
```

#### `biosimulant runs get`

Inspect a managed run

```text
usage: biosimulant runs get [-h] run_id

positional arguments:
  run_id

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runs create`

Create a managed run

```text
usage: biosimulant runs create [-h] [--inputs INPUTS]
                               [--compute-profile COMPUTE_PROFILE]
                               [--idempotency-key IDEMPOTENCY_KEY]
                               ref

positional arguments:
  ref

options:
  -h, --help            show this help message and exit
  --inputs INPUTS
  --compute-profile COMPUTE_PROFILE
  --idempotency-key IDEMPOTENCY_KEY
```

#### `biosimulant runs cancel`

Cancel a managed run

```text
usage: biosimulant runs cancel [-h] run_id

positional arguments:
  run_id

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runs results`

Get managed run results

```text
usage: biosimulant runs results [-h] run_id

positional arguments:
  run_id

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runs logs`

Get managed run events or logs

```text
usage: biosimulant runs logs [-h] [--after AFTER] [--limit LIMIT] run_id

positional arguments:
  run_id

options:
  -h, --help     show this help message and exit
  --after AFTER
  --limit LIMIT
```

#### `biosimulant runs remote catalog`

List remote compute profiles

```text
usage: biosimulant runs remote catalog [-h]

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runs remote create`

Create a remote managed run

```text
usage: biosimulant runs remote create [-h] [--inputs INPUTS]
                                      [--compute-profile COMPUTE_PROFILE]
                                      ref

positional arguments:
  ref

options:
  -h, --help            show this help message and exit
  --inputs INPUTS
  --compute-profile COMPUTE_PROFILE
```

#### `biosimulant runs remote get`

Inspect a remote managed run

```text
usage: biosimulant runs remote get [-h] run_id

positional arguments:
  run_id

options:
  -h, --help  show this help message and exit
```

#### `biosimulant runs remote results`

Get remote run results

```text
usage: biosimulant runs remote results [-h] run_id

positional arguments:
  run_id

options:
  -h, --help  show this help message and exit
```

### Stable aliases

#### `biosimulant validate`

Alias for labs validate

```text
usage: biosimulant labs validate [-h] [--json] [lab]

positional arguments:
  lab

options:
  -h, --help  show this help message and exit
  --json
```

#### `biosimulant run`

Alias for labs run

```text
usage: biosimulant labs run [-h] [--target TARGET] [--force]
                            [--registry-url REGISTRY_URL] [--no-install-deps]
                            [--dependency-root DEPENDENCY_ROOT]
                            [--require-local-capability]
                            [--results-file RESULTS_FILE]
                            [--report-file REPORT_FILE]
                            [--run-input-file RUN_INPUT_FILE] [--json]
                            [lab]

positional arguments:
  lab

options:
  -h, --help            show this help message and exit
  --target TARGET       Destination for auto-pulled registry refs
  --force               Replace an existing auto-pull target
  --registry-url REGISTRY_URL
  --no-install-deps
  --dependency-root DEPENDENCY_ROOT
                        Writable Lab-local dependency state directory for a
                        .bsilab archive
  --require-local-capability
                        Fail before execution unless declared CPU, memory, and
                        GPU needs fit this host
  --results-file RESULTS_FILE
  --report-file REPORT_FILE
                        Write a standalone HTML run report
  --run-input-file RUN_INPUT_FILE
                        JSON object containing parameters and
                        simulation_config overlays
  --json
```

### Listed but not available

These appear in `biosimulant commands list` with `"available": false`. Calling one fails with exit code 7 and the error code `capability_unavailable`.

| Command | Why |
| --- | --- |
| `biosimulant runs start` | A managed run starts when it is created; use `biosimulant runs create`. |
| `biosimulant runs upload` | Uploading run artifacts is not available in this CLI. |
| `biosimulant jobs list` | Background jobs are not exposed by the public API; use `biosimulant runs list` for managed runs. |
| `biosimulant jobs get` | Background jobs are not exposed by the public API; use `biosimulant runs get` for a managed run. |

### Removed commands

These older commands are no longer part of the public CLI. They fail with exit code 7 and the error code `deprecated_desktop_command`: `raw`, `settings`, `which`, `install-cli`, `uninstall-cli`, `self`, `config`, `agent`, `agents`, `chat`, `labs open`, `labs import` and `runs open`. Use `pipx upgrade biosimulant` instead of `self update`.

## Registry API v1

This section is for anyone running their own registry. Registry API v1 is the protocol the CLI uses to talk to the Hub and compatible registries. The examples use `https://hub.biosimulant.com` as the origin. Clients must discover the API base instead of assuming its path.

### Discovery

Request the well-known document without authentication:

```http
GET /.well-known/biosimulant-registry HTTP/1.1
Host: hub.biosimulant.com
```

The response gives the protocol version, API base, authentication endpoints and capabilities:

```json
{
  "protocolVersion": "1",
  "apiBase": "/api/registry/v1",
  "auth": {
    "type": "bearer",
    "tokenEndpoint": "/api/registry/v1/auth/exchange",
    "workspaceExchangeTokenEndpoint": "/api/registry/v1/auth/workspace-exchange-tokens"
  },
  "capabilities": [
    "search",
    "metadata",
    "versions",
    "pull",
    "publish",
    "private-packages",
    "workspace-token-exchange",
    "immutable-versions",
    "checksum-idempotency"
  ]
}
```

Resolve relative URLs against the registry origin. Reject an unsupported `protocolVersion`, or a registry that doesn't advertise a capability the operation needs.

### Package endpoints

Paths are relative to the discovered `apiBase`.

| Method | Path | Purpose | Required access |
| --- | --- | --- | --- |
| `GET` | `/packages?q=&type=&page=&pageSize=` | Search visible packages | Anonymous for public packages; `packages:read` for private ones |
| `GET` | `/packages/{namespace}/{name}` | Package metadata and its default version | Same as search |
| `GET` | `/packages/{namespace}/{name}/versions` | List immutable versions | Same as search |
| `GET` | `/packages/{namespace}/{name}/{version}` | One exact version and its checksum | Same as search |
| `GET` | `/packages/{namespace}/{name}/{version}/download` | Download the exact artifact | Same as search |
| `POST` | `/packages` | Publish an artifact | `packages:write` |

Search takes `type=model` or `type=lab`, uses one-based pages and caps `pageSize` at 100. Package and version responses include the canonical reference, SHA-256 digest, byte size, visibility, status and download URL.

Private packages return `404` when the caller can't access them, so an outsider can't tell whether they exist.

### Token exchange

Send an operation token as a bearer credential:

```http
Authorization: Bearer <operation-token>
```

Public reads can be anonymous. Private reads need `packages:read`, and publishing needs `packages:write`.

Long-lived user credentials must not be copied into Studio workspaces. Instead, an authenticated application issues a workspace-bound exchange token:

```http
POST /auth/workspace-exchange-tokens
Content-Type: application/json

{
  "workspaceSessionId": "16a74428-370d-4557-a92f-92dce233505f",
  "scopes": ["packages:read", "packages:write"]
}
```

The workspace exchanges it when an operation starts:

```http
POST /auth/exchange
Authorization: Bearer <workspace-exchange-token>
Content-Type: application/json

{
  "registry": "https://hub.biosimulant.com",
  "scopes": ["packages:read"]
}
```

The result is a short-lived `registry_operation` bearer token with its scopes, audience and expiry. Requested scopes can't exceed what the workspace token was granted, and the workspace session must still be active.

### Publishing

Send the package archive as the request body:

```http
POST /packages
Authorization: Bearer <operation-token>
Content-Type: application/gzip
X-Biosimulant-Filename: cell-study-1.0.0.tar.gz
X-Biosimulant-Sha256: 68b0…
X-Biosimulant-Package: acme/cell-study
X-Biosimulant-Version: 1.0.0
X-Biosimulant-Visibility: private

<artifact bytes>
```

`X-Biosimulant-Filename` and the full lowercase hex `X-Biosimulant-Sha256` are required. The package, version and visibility headers state what you intend, and the server checks them against the artifact manifest.

A new version returns `201`:

```json
{
  "artifact": {
    "reference": "acme/cell-study@1.0.0",
    "version": "1.0.0",
    "sha256": "68b0…"
  },
  "idempotent": false
}
```

Publishing the same version with the same checksum again returns `200` with `idempotent: true`. The same version with different content returns `409 immutable_version_conflict`.

### Errors and verification

Registry errors use the HTTP status plus a JSON `detail`, which is either a message or an object with a stable `code` and supporting fields.

| Status | Meaning |
| --- | --- |
| `401` | Missing, expired or invalid credential |
| `403` | The caller lacks the required scope |
| `404` | Package, version or active workspace session not found or not visible |
| `409` | The immutable version already exists with a different checksum |
| `422` | Invalid manifest, checksum, scope, reference, version or request header |

Clients must compute the downloaded artifact's SHA-256 and compare it with the resolved version before extracting it. A mismatch is a hard failure and must never be cached as a successful pull.

### Compatibility requirements

A compatible Registry API v1 implementation must:

- Serve the well-known discovery document.
- Return immutable, checksum-addressed versions.
- Allow anonymous public reads.
- Enforce `packages:read` and `packages:write` where authentication is required.
- Treat identical publishes as idempotent.
- Reject a same-version, different-content publish with `409`.
- Keep credentials out of references, redirect URLs and logs.
