Skip to content
OpenConceptLabPublic

About

ocl command line interface

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

99 Commits

Folders and files

Repository files navigation

OCL CLI

Command-line interface for the Open Concept Lab API. Search, browse, and manage concepts, mappings, sources, and collections from your terminal. Multi-server ready.

Designed for both humans and AI agents.

Installation

Recommended: pipx (isolated global install)

pipx install openconceptlab

This installs ocl as a global command with no dependency conflicts. Get pipx if you don't have it.

Alternative: pip

pip install openconceptlab

Alternative: uv

uv tool install openconceptlab

One-off (no install)

Run without installing using pipx or uvx:

pipx run openconceptlab --help
uvx openconceptlab --help

For development

git clone https://github.com/OpenConceptLab/ocl-cli.git
cd ocl-cli
pip install -e ".[dev]"

Verify installation

ocl --version
ocl --help

Use as a Python library

The openconceptlab package also installs the API client the ocl command uses:

from openconceptlab import OCLAPIClient

with OCLAPIClient(token="<your OCL API token>", client_name="my-tool/1.0") as ocl:
    hits = ocl.search_concepts(query="malaria", owner="CIEL", repo="CIEL", limit=5)
    for concept in hits["results"]:
        print(concept["id"], concept["display_name"])

Pass auth_scheme="Bearer" to use an OIDC access token instead of an API token.

HTTP errors raise openconceptlab.APIError, with status_code, detail and the parsed response body. Network failures raise the httpx exception (httpx.TimeoutException or httpx.TransportError) once retries run out. A client holds one token, so create one per user.

Each ID, version or key you pass is encoded as one segment of the request path. An empty value, ., .. or a value containing / raises APIError (status_code 0) before anything is sent. So does a request to a URL outside the client's base_url, such as one taken from a response.

Quick Start

1. Authenticate

You need an API token from app.openconceptlab.org:

# Store your token (prompts securely)
ocl login

# Verify authentication
ocl whoami

Or pass a token directly:

ocl --token YOUR_TOKEN whoami

Or set an environment variable:

export OCL_API_TOKEN=your_token_here
ocl whoami

2. Explore the API

# Search for organizations
ocl org list WHO

# Search for sources
ocl repo list --owner CIEL --type source

# Search for concepts
ocl concept search malaria --owner CIEL --repo CIEL

# Get a specific concept with its cross-terminology mappings
ocl concept get CIEL CIEL 116128 --include-mappings

# Search for mappings (--owner/--repo scope the mapping's source)
ocl mapping search --owner CIEL --repo CIEL --to-concept 116128

3. JSON output for scripting and agents

Use the -j flag (before the subcommand) for machine-readable JSON output:

ocl -j concept search malaria --owner CIEL --repo CIEL
ocl -j concept get CIEL CIEL 116128

4. Verbose vs summary output

Search/list commands return a summary by default (compact, fast). Use --verbose to get full details (names, descriptions, extras, timestamps):

# Summary (default)
ocl concept search malaria --owner CIEL --repo CIEL

# Verbose — more columns, more data from API
ocl concept search malaria --owner CIEL --repo CIEL --verbose

# Debug — show HTTP requests on stderr
ocl -d concept search malaria --owner CIEL --repo CIEL

The --verbose flag controls the OCL API's ?verbose=true parameter. With -j, it returns more fields in the JSON response.

Commands

Organizations

ocl org list [QUERY] [--verbose] [--limit N] [--page N]
ocl org get ORG
ocl org members ORG [--limit N]
ocl org repos ORG [--type source|collection|all] [--verbose] [--limit N] [--page N]

# Organization management
ocl org create ORG_ID NAME [--company ...] [--website ...] [--location ...] [--public-access View|Edit|None]
ocl org delete ORG_ID [--yes]
ocl org add-member ORG USERNAME
ocl org remove-member ORG USERNAME [--yes]

Users

ocl user list [QUERY] [--verbose] [--limit N] [--page N]
ocl user get USERNAME
ocl user repos USERNAME [--type source|collection|all] [--verbose] [--limit N] [--page N]
ocl user orgs USERNAME [--limit N]

Repositories (sources & collections)

Sources and collections are unified under ocl repo with a --type flag.

# Browse
ocl repo list [QUERY] [--owner OWNER] [--type source|collection|all] [--custom-validation-schema SCHEMA] [--updated-since YYYY-MM-DD] [--all-versions] [--verbose]
ocl repo get OWNER REPO [--type source|collection] [--repo-version VERSION]
ocl repo versions OWNER REPO [--type source|collection] [--updated-since YYYY-MM-DD]

# Create & update
ocl repo create OWNER REPO_ID NAME --type source|collection [options]
ocl repo update OWNER REPO [--name NAME] [--description DESC]
ocl repo version-create OWNER REPO VERSION_ID [--released/--no-released] [--match-algorithms es,llm]
ocl repo version-update OWNER REPO VERSION_ID [--released/--no-released] [--match-algorithms es,llm]

# Without --match-algorithms, a new source version is vectorized (semantic matching)
# when its HEAD is. Opt in or out when you create it:
ocl repo version-create Regenstrief LOINC 2.82 --match-algorithms es,llm   # opt in: vectorized
ocl repo version-create Regenstrief LOINC 2.82 --match-algorithms es       # opt out: not vectorized

--match-algorithms applies to sources only. Set it when you create a version rather than with version-update afterwards. Changing an existing version's match algorithms makes the server reindex its concepts: adding llm embeds them, which can take hours for a large repository, and removing it can drop their vectors. version-update --match-algorithms prints a warning to that effect.

# Custom attributes
ocl repo extras OWNER REPO
ocl repo extra-set OWNER REPO KEY VALUE
ocl repo extra-del OWNER REPO KEY

Exports

Manage cached ZIP exports of repository versions (sources and collections). Exports contain the full JSON representation of a version — metadata, concepts, mappings, and references.

# Check if an export is available
ocl repo export status OWNER REPO VERSION --type source|collection

# Trigger export creation (if not already cached)
ocl repo export create OWNER REPO VERSION --type source|collection

# Download, saved under the API's name; never replaces an existing file
# (-o FILE saves it as FILE, overwriting; -o DIR saves it in that existing directory)
ocl repo export download OWNER REPO VERSION --type source|collection [-o FILE|DIR]

# Delete a cached export
ocl repo export delete OWNER REPO VERSION --type source|collection

Example — download the latest CIEL release export:

ocl repo export status CIEL CIEL v2026-03-23 --type source
ocl repo export download CIEL CIEL v2026-03-23 --type source
# Saved to orgs_CIEL_sources_CIEL_v2026-03-23_<lastUpdated>.zip

The API names exports [owner-type]_[owner]_[repo-type]_[repo]_[version]_[expansion]_[lastUpdated].zip, with the expansion when the export has one. An older export the API can't name keeps its storage name; if no usable name comes back at all, the command asks for -o FILE.

Concepts

# Search & browse
ocl concept search [QUERY] [--owner OWNER] [--repo REPO] [--concept-class CLASS]
ocl concept list [--owner OWNER] [--repo REPO] [--repo-type source|collection]
ocl concept get OWNER SOURCE CONCEPT_ID [--repo-version VERSION] [--include-mappings] [--include-inverse-mappings]
ocl concept versions OWNER SOURCE CONCEPT_ID
ocl concept names OWNER SOURCE CONCEPT_ID [--verbose]
ocl concept descriptions OWNER SOURCE CONCEPT_ID [--verbose]
ocl concept extras OWNER SOURCE CONCEPT_ID

# Create & update
ocl concept create OWNER SOURCE CONCEPT_ID --concept-class CLASS --name NAME [--datatype TYPE]
ocl concept update OWNER SOURCE CONCEPT_ID [--concept-class CLASS] [--datatype TYPE]
ocl concept retire OWNER SOURCE CONCEPT_ID
ocl concept delete OWNER SOURCE CONCEPT_ID [--yes]      # hard delete; staff only
ocl concept name-add OWNER SOURCE CONCEPT_ID NAME --locale LOCALE
ocl concept description-add OWNER SOURCE CONCEPT_ID TEXT --locale LOCALE
ocl concept extra-set OWNER SOURCE CONCEPT_ID KEY VALUE
ocl concept extra-del OWNER SOURCE CONCEPT_ID KEY

# Intelligent matching
ocl concept match TERM... --target-source SOURCE [--target-owner OWNER] [--verbose] [--limit N]
ocl concept match "glucose" --target-source CIEL --concept-class Diagnosis --limit 5
ocl concept match "malaria" "diabetes" --target-source CIEL --include-mappings --limit 1

Mappings

# Search & browse (--owner/--repo scope the mapping's source, not the concept sources)
ocl mapping search [QUERY] [--owner OWNER] [--repo REPO] [--map-type TYPE]
ocl mapping search --owner CIEL --repo CIEL --from-concept 138041 --verbose
ocl mapping search --owner CIEL --repo CIEL --to-concept 116128
ocl mapping get OWNER SOURCE MAPPING_ID [--repo-version VERSION]
ocl mapping versions OWNER SOURCE MAPPING_ID

# Create & update
ocl mapping create OWNER SOURCE --map-type TYPE --from-concept-url URL --to-concept-url URL
ocl mapping update OWNER SOURCE MAPPING_ID [--map-type TYPE]
ocl mapping retire OWNER SOURCE MAPPING_ID
ocl mapping delete OWNER SOURCE MAPPING_ID [--yes]      # hard delete; staff only

Cascade

Navigate concept hierarchies and related mappings:

ocl cascade OWNER REPO CONCEPT_ID [--repo-version VERSION] [--levels N] [--reverse]

Collection References

ocl ref list OWNER COLLECTION [--collection-version VERSION]
ocl ref add OWNER COLLECTION EXPRESSION... [--cascade sourcemappings|sourcetoconcepts]
ocl ref remove OWNER COLLECTION EXPRESSION...

Expansions

ocl expansion list OWNER COLLECTION VERSION
ocl expansion get OWNER COLLECTION [--collection-version VERSION] [--expansion-id ID]
ocl expansion create OWNER COLLECTION VERSION

Bulk Import

Submit files for bulk import. Supports JSON Lines (.json, .jsonl), CSV (.csv), and OCL export ZIP (.zip):

ocl import file data.jsonl                              # submit JSON Lines file
ocl import file export.zip --queue myqueue              # submit to named queue
ocl import file data.csv --no-update --parallel 3       # CSV, skip existing, 3 threads
ocl import file data.jsonl --wait                       # submit and poll until complete
ocl import status TASK_ID                               # check import status
ocl import status TASK_ID --wait                        # poll until complete
ocl import list                                         # list active/recent imports
ocl import list --queue myqueue                         # list imports in specific queue

Task Management

Monitor async operations on the server:

ocl task list [--state SUCCESS|FAILURE|PENDING|STARTED] [--verbose]
ocl task get TASK_ID

Server Management

OCL CLI supports multiple OCL server environments:

# List configured servers
ocl server list

# Add a custom server
ocl server add my-server https://api.my-ocl.org --name "My OCL Server"

# Switch default server
ocl server use ocl-dev

# Use a specific server for one command
ocl -s ocl-qa concept search malaria

Pre-configured servers: ocl-prod (default), ocl-dev, ocl-qa, ocl-staging

Versioning

OCL resources are versioned. Use --repo-version (for sources/repos) or --collection-version (for collections) to access specific versions:

# Get latest released version of a source
ocl repo get CIEL CIEL --repo-version latest

# Get a named version
ocl repo get CIEL CIEL --repo-version v2026-01-26

# Get a concept as it existed in a specific source version
ocl concept get CIEL CIEL 116128 --repo-version v2026-01-26

# Omit --repo-version to get HEAD (working draft)
ocl concept get CIEL CIEL 116128

Configuration

Config is stored at ~/.ocl/config.json. It's created automatically on first use.

Token resolution order

  1. --token CLI flag
  2. OCL_API_TOKEN environment variable
  3. Server-specific env var (e.g. OCL_API_TOKEN_PROD)
  4. Token stored via ocl login

Server resolution order

  1. --server / -s CLI flag
  2. OCL_SERVER environment variable
  3. default_server in config file

Agent Integration

The CLI is designed for use by AI agents via subprocess calls. Key features:

  • ocl reference --json dumps the full command tree in one call — all commands, args, options
  • -j flag outputs structured JSON on stdout
  • Exit codes: 0 = success, 1 = client error, 2 = server error, 3 = auth error
  • Errors go to stderr, data goes to stdout
  • Non-interactive: all parameters via flags/args (except ocl login)

Example agent usage:

# Search and parse results
ocl -j concept search "diabetes" --owner CIEL --repo CIEL --limit 10

# Get a specific concept
ocl -j concept get CIEL CIEL 116128

# Create a mapping (with token via env var)
OCL_API_TOKEN=xxx ocl -j mapping create MYORG MYSOURCE \
  --map-type SAME-AS \
  --from-concept-url /orgs/CIEL/sources/CIEL/concepts/116128/ \
  --to-concept-url /orgs/WHO/sources/ICD-10-WHO/concepts/B54/

Advanced: Real-World Queries with jq

The CLI's --json output pairs naturally with jq for ad-hoc analytics that would otherwise require direct database access.

Example: OpenMRS repo activity report

Count source versions, collection versions, distinct orgs, and distinct users with OpenMRS Validation Schema repos updated in the last 6 months (ocl_issues#2032):

ocl -j repo list --custom-validation-schema OpenMRS --updated-since 2025-09-27 \
  --all-versions --limit 500 \
| jq '{
  source_versions: [.results[] | select(.repo_type == "Source")] | length,
  collection_versions: [.results[] | select(.repo_type == "Collection")] | length,
  organizations: [.results[] | select(.owner_type == "Organization") | .owner] | unique | length,
  users: [.results[] | select(.owner_type == "User") | .owner] | unique | length
}'

Key flags used:

  • --custom-validation-schema OpenMRS — server-side filter via Elasticsearch
  • --updated-since 2025-09-27 — only repos updated in the last 6 months
  • --all-versions — return one row per repo version (not just HEAD), so version counts are direct
  • -j (before subcommand) — structured JSON output for piping to jq

Requirements

  • Python 3.10+
  • Dependencies: click, httpx, tenacity, python-dotenv

About

ocl command line interface

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages