Command line
Pharma Observatory CLI
Read Pharma Observatory data from your terminal with the pharma command: install it, sign in, and run a dataset command or a SQL query.
pharma is a command-line client for the Pharma Observatory REST API. The command is pharma in every example. It talks to the REST API only and needs a plan with API access.
- Install
- Sign in and profiles
- Dataset commands
- SQL with query
- Output formats
- Exit codes and quota
- Configuration
Install
brew install scientist-labs/pharmaobservatory/pharma
sudo install -d /usr/share/keyrings
curl -fsSL https://packages.pharmaobservatory.com/pharmaobservatory.gpg \
| sudo tee /usr/share/keyrings/pharmaobservatory-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/pharmaobservatory-archive-keyring.gpg] https://packages.pharmaobservatory.com/apt stable main" \
| sudo tee /etc/apt/sources.list.d/pharmaobservatory.list
sudo apt-get update && sudo apt-get install pharma
sudo curl -fsSL -o /etc/yum.repos.d/pharmaobservatory.repo https://packages.pharmaobservatory.com/rpm/pharmaobservatory.repo
sudo dnf install pharma
The repository metadata is signed; the individual packages are not.
Direct download: each GitHub release carries archives for Linux, macOS and Windows. Verify the checksums with cosign (keyless) and then check the archive against them; replace the tag with the release you downloaded.
cosign verify-blob \
--certificate-identity 'https://github.com/scientist-labs/pharmaobservatory-cli/.github/workflows/release.yml@refs/tags/v0.1.0' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txt
pharma version
Sign in and profiles
pharma login # opens your browser; loopback redirect on 127.0.0.1:53682
pharma login --no-browser # prints the URL; sign in anywhere, then paste the callback URL (or just its code)
pharma whoami # profile, origin, who, organization, plan, roles and remaining quota
pharma whoami --json # the raw body
pharma logout # removes the stored credential for the profile
Profiles: prod (the default) and staging are built in. Pick one with --profile, $PHARMA_PROFILE or default_profile in config.yml. Create a custom profile with pharma login --profile NAME --origin URL.
The refresh token lives in the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager). Where none is available, or with PHARMA_CREDENTIAL_STORE=file, it lives in credentials.json (mode 0600) in the config directory. pharma logout deletes it locally and does not revoke it server-side.
Dataset commands
One command group per object type, then the datasets of that type. The page for each object type lists its datasets, columns and source credits.
- pharma companies: Companies
- pharma legal-entities: Legal entities
- pharma biomarkers: Biomarkers
- pharma cell-lines: Cell lines
- pharma sites: Sites
- pharma conditions: Conditions
- pharma trials: Trials
pharma companies list # the object type's primary dataset
pharma companies get <id> # one record and the datasets reachable from it
pharma companies get <id> sponsor-trials # one dataset of a record
pharma datasets list # every published dataset
pharma datasets get <name> # one dataset's grain and columns
Command and dataset names are the API names with - for _; the API spelling also works (pharma cell_lines list). Ids are passed through verbatim. Put the id and the dataset before any flags. pharma <object> --help lists the datasets and pharma <object> get <id> <dataset> --help lists a dataset's flags.
pharma biomarkers get ESR1 biomarker-evidence
pharma trials get NCT01234567 trial-drugs
pharma conditions get D001943 condition-trials
pharma companies get <id> sponsor-trials --scope group
Every dataset read takes --limit, --cursor, --sort, --group-by and --measure (and --scope on company datasets), plus --filter, repeatable and keyed by column. --filter column=value is equality (a repeated column is an IN list); column.gt, .gte, .lt, .lte, .ne, .contains and .null are the other operators.
pharma biomarkers list --filter gene_symbol=EGFR --filter evidence_tier.lte=2 --sort -n_trial_mention
pharma biomarkers list --group-by biomarker_kind --measure count
--all follows the cursor to the last page and prints one table (or one CSV); it cannot be combined with --json. The walk stops early, prints what it has and exits 9 when the operation takes no --cursor, the server repeats a cursor, or it reaches 1000 pages.
pharma api <path> makes an authenticated GET and prints the body; -F key=value adds a query parameter.
pharma api biomarkers -F kind=gene -F limit=5
pharma api /api/v1/whoami
SQL with query
pharma query runs one SQL SELECT over one published dataset, with no JOIN. It needs a signed-in account (pharma login first). Pass - to read the SQL from stdin.
pharma query "SELECT biomarker_kind, count(*) AS n FROM biomarkers GROUP BY biomarker_kind"
pharma query "SELECT display_name, gene_symbol FROM biomarkers WHERE gene_symbol = 'EGFR'" --csv
pharma query - < query.sql
The server stores the full result for 24 hours and enforces its own limits on rows, statement length and time. The footer names the stored result and the next page; page it with --result and --cursor, or add --all to follow the cursor to the end.
pharma query --result <result_id> --cursor <cursor>
pharma query --result <result_id> --all
A refused query prints the server's error code, message and hint, and exits with the code in the table below (most refusals exit 2).
Output formats
The default is a table, with a footer on stderr (source credit, as-of date, paging).
pharma biomarkers list --limit 5
pharma biomarkers list --limit 5 --json
pharma biomarkers list --limit 5 --csv
--json prints the server's body unchanged and --csv builds CSV from the response columns; they cannot be combined. --quiet (alias --no-footer) drops the as-of and paging lines. The source credit always prints: on stderr, and as trailing # Source: lines in CSV output. To skip those lines in pandas, read the file with comment='#'.
Exit codes and quota
A 429 exits 6 and says which limit was hit. The CLI does not wait by default; --wait sleeps for the Retry-After (at most 2 minutes) and retries once. A paging-depth refusal has no Retry-After: narrow the query. pharma exit-codes prints this table.
pharma biomarkers list --all --wait
| Exit | Class | When |
|---|---|---|
| 0 | ok | Success |
| 1 | unexpected | An error the table does not name, or a failure in the CLI itself |
| 2 | usage | A bad flag or argument, an unknown command, or a query the server refuses |
| 3 | auth | Not logged in, or a rejected token |
| 4 | forbidden | Access pending, a missing role, or not entitled |
| 5 | not found | An unknown record or page, or a stored query result that expired |
| 6 | rate limited | A quota was reached; the message says which, when it resets and how long to wait |
| 7 | unavailable | A 5xx, a network failure, a timeout, or a changed warehouse generation |
| 8 | not answered | A 200 whose body says unbuilt or not applicable |
| 9 | partial | `--all` printed some pages and then stopped |
Configuration
The config directory is $PHARMA_CONFIG_DIR, else $XDG_CONFIG_HOME/pharma, else ~/.config/pharma. It holds config.yml and, where no keychain is available, credentials.json.
PHARMA_PROFILE: the profile to usePHARMA_CONFIG_DIR: the config directoryPHARMA_CREDENTIAL_STORE=file: keep the refresh token incredentials.json
New API endpoints arrive in the commands automatically: they are generated from the API document.