Generated banner for Imaging Data Commons
Generated logo for Imaging Data Commons

Imaging Data Commons

K-DenseCommunityCreated by Andrey Fedorov, @fedorovDistributed by K-Dense

  • Skill
  • Tool integration
  • Science and research
  • MIT
  • Low risk
  • Has scripts

From the author

Queries and downloads public cancer imaging data from NCI Imaging Data Commons. Supports IDC collection discovery, DICOM access, radiology (CT, MR, PET) and pathology AI datasets, metadata SQL, visualization, licensing, and citations. Uses public metadata and download routes without authentication; optional BigQuery and Google Healthcare routes require Google credentials.
— the description by Andrey Fedorov, @fedorov

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Imaging Data Commons (IDC) skill provides a programmatic interface for accessing large-scale public cancer imaging data. It supports discovery, metadata querying, and file downloads for radiology (CT, MR, PET) and pathology datasets. The skill offers multiple access paths, including a Python client for local workflows, a REST API for lightweight metadata retrieval, and an MCP server for interactive discovery. It handles complex data structures, including DICOM hierarchies and derived analysis results, while ensuring users can verify licensing and citation requirements for research and clinical applications.

What it does

  • Queries and filters public cancer imaging metadata using SQL or attribute-based selection.
  • Downloads DICOM imaging files directly from public cloud storage buckets.
  • Provides browser-based visualization URLs for radiology and pathology images.
  • Retrieves licensing information and citation formats for selected datasets.
  • Supports integration with DICOMweb-compatible tools and PACS systems.
  • Enables programmatic cohort building and manifest generation for large-scale data analysis.

When to use it

  • When you need to find and download specific cancer imaging datasets for research.
  • When you need to perform metadata-driven cohort selection across large imaging collections.
  • When you want to visualize medical images without installing local DICOM viewer software.
  • When you need to integrate IDC data access into automated Python or shell-based pipelines.
  • When you need to verify the licensing terms of specific imaging series before use.

Advantages

  • No authentication required for standard public data access.
  • Supports both lightweight REST-based metadata queries and heavy-duty local downloads.
  • Provides consistent access to versioned data for reproducible research.
  • Includes built-in tools for citation and license management.
  • Offers flexible directory templating for organized local data storage.

Usage examples

“Find all MR series for the 'rider_pilot' collection.”
Returns a list of SeriesInstanceUIDs and metadata for the requested MR images.
“Download the first 5 CT series from the 'nlst' collection to my local data folder.”
Downloads the specified DICOM files into the target directory using the default hierarchy.
“Get a viewer URL for a specific study.”
Provides a direct link to an OHIF or SLIM viewer instance for the requested study.
“What are the license terms for the current selection?”
Returns a breakdown of license types (e.g., CC BY, CC BY-NC) for the selected series.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/k-dense-ai/imaging-data-commons/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip imaging-data-commons.zip -d .agents/skills

Documentation by Andrey Fedorov, @fedorov

Imaging Data Commons

Overview

Query and download public cancer imaging data from the National Cancer Institute Imaging Data Commons (IDC). No authentication required for data access.

Expected network access: IDC metadata is reachable three ways — a bundled local DuckDB index (offline after installation; additional indices are fetched from GitHub and clinical tables from S3), or the hosted IDC service over MCP or REST (api.imaging.datacommons.cancer.gov, no authentication). File downloads use public GCS (storage.googleapis.com) and AWS S3 (s3.amazonaws.com) — no authentication required. DICOMweb access uses either the public IDC proxy (proxy.imaging.datacommons.cancer.gov, no auth) or the Google Cloud Healthcare API (healthcare.googleapis.com, requires GCP authentication). Optional BigQuery queries (bigquery.googleapis.com) also require GCP authentication. Citation resolution contacts DOI services. Public IDC routes require no credentials; optional Google clients use Application Default Credentials.

Reviewed 2026-09-30: idc-index 0.12.5, idc-index-data 24.2.2, IDC v24; hosted API 3.0.0b3. Recheck at use time.

Choose the access path first. There is no single default: the cheapest correct path depends on the session and the task.

  1. Session already has the IDC MCP server? Route discovery and metadata there — see IDC MCP Server.

  2. Otherwise, is idc-index installed and current? Run python scripts/check_version.py. If it passes, use idc-index for everything.

  3. Not installed, and the task is read-only metadata — counts, attribute values, collection lookups, SQL under 10 000 rows, licenses, citations, viewer URLs? Use the REST API over curl; do not install anything. Installing costs ~77 MB of packaged index data plus pandas, pyarrow, and duckdb, which a metadata question does not need. See Data Access Options.

  4. Not installed, and the task needs more than metadata — downloading files, pandas or plotting, pydicom/SimpleITK, pathology tiling, results past 10 000 rows, or a version-pinned script the user re-runs? Install idc-index: check_version.py exits non-zero and prints the exact install command for the running interpreter. Prefer a virtual environment, then restart Python.

idc-index (GitHub) is still the most capable Python path, with query and download helpers in one client. check_version.py never installs anything itself — it also flags a newer idc-index or skill release when one exists.

Setup for the idc-index path: use the intended interpreter for scripts/check_version.py and confirm its meets pinned minimum message before continuing. A launch failure is not a pass.

from idc_index import IDCClient
client = IDCClient()

# Verify IDC data version (should be "v24")
print(f"IDC data version: {client.get_idc_version()}")

Download and image-processing examples are illustrative unless noted; see the reference review notes for verification scope.

Core workflow: query metadata with client.sql_query() → download with client.download_from_selection() → visualize with client.get_viewer_URL(). Python examples below assume this client; Data Access Options has the REST equivalents. For current data scale, run the summary query in references/sql_patterns.md or GET /v3/stats.

IDC MCP Server

IDC operates a hosted MCP server at https://api.imaging.datacommons.cancer.gov/mcp (streamable HTTP, no authentication). Where it is available it complements — it does not replace — the idc-index workflow below.

Identify it by the MCP resource idc://guide, or by three or more of the tool names build_cohort, get_cohort_urls, list_analysis_results, and get_idc_version. Generic names such as run_sql are not evidence on their own. If identification is ambiguous, use idc-index.

If this session has the server, treat it as authoritative for discovery and metadata — IDC version, counts, attribute values, cohort building, metadata SQL — and follow the server's own instructions rather than re-deriving them from this file. Its data version is whatever the server reports: call get_idc_version instead of relying on the version pinned in this file.

Return here for what the server does not do: downloading files, local pandas/notebook analysis, DICOMweb, BigQuery, digital pathology tiling, and reproducible scripts. Hand off by passing SeriesInstanceUIDs from the server to client.download_from_selection(...), and run scripts/check_version.py at that point.

If it is not available, the identical service is reachable with no configuration as a REST API at https://api.imaging.datacommons.cancer.gov/v3 — use it for read-only metadata rather than installing idc-index, per the routing gate in Overview. Suggest connecting the MCP server at most once, only for repeated interactive discovery, and never change the user's configuration yourself.

See references/mcp_guide.md for the tool inventory, handoff patterns, and per-host notes.

When to Use This Skill

  • Finding publicly available radiology (CT, MR, PET) or pathology (slide microscopy) images

  • Selecting image subsets by cancer type, modality, anatomical site, or other metadata

  • Downloading DICOM data from IDC

  • Checking data licenses before use in research or commercial applications

  • Visualizing medical images in a browser without local DICOM viewer software

Quick Navigation

Inline below: the MCP/REST routing rules, the IDC data model, the index tables and how they join, the core API patterns (query, download, visualize, license, cite), best practices, and troubleshooting.

Reference Guides (load on demand):

GuideWhen to Load
index_tables_guide.mdComplex JOINs, schema discovery, DataFrame access
use_cases.mdEnd-to-end workflows: training datasets, batch downloads, DICOM reading with pydicom/SimpleITK, pipeline integration
sql_patterns.mdQuick SQL patterns for filter discovery, annotations, size estimation
clinical_data_guide.mdClinical/tabular data, imaging+clinical joins, value mapping
licensing_and_citation.mdCommercial-use questions, mixed-license cohorts, citation formats
cloud_storage_guide.mdDirect S3/GCS access, versioning, UUID mapping
dicomweb_guide.mdDICOMweb endpoints, PACS integration
digital_pathology_guide.mdSlide microscopy (SM), annotations (ANN), pathology workflows
bigquery_guide.mdFull DICOM metadata, private elements (requires GCP)
cli_guide.mdCommand-line tools (idc download, manifest files)
parquet_access_guide.mdDirect Parquet queries via GCS (no idc-index install needed)
mcp_guide.mdHosted IDC MCP server: tool inventory, identification, handoff to idc-index
rest_api_guide.mdHosted IDC REST API: endpoints, filter syntax, SQL over HTTP, manifests

IDC Data Model

IDC adds two grouping levels above the standard DICOM hierarchy (Patient → Study → Series → Instance):

  • collection_id: Groups patients by disease, modality, or research focus (e.g., tcga_luad, nlst). Treat (collection_id, PatientID) as the patient key; do not assume PatientID is globally unique.

  • analysis_result_id: Identifies derived objects (segmentations, annotations, radiomics features) across one or more original collections. Use it to find AI-generated or expert annotations, while collection_id finds original imaging data (which may itself include deposited annotations).

Key identifiers for queries:

IdentifierScopeUse for
collection_idDataset groupingFiltering by project/study
PatientIDPatientGrouping images by patient
StudyInstanceUIDDICOM studyGrouping of related series, visualization
SeriesInstanceUIDDICOM seriesGrouping of related series, visualization

Index Tables

The idc-index package provides multiple metadata index tables, accessible via SQL or as pandas DataFrames. The REST API exposes the same tables through GET /tables and POST /sql.

Important: client.indices_overview is the authoritative source for current table descriptions, available columns, and their types — query it when writing SQL or exploring data structure. It also answers "which table contains column X"; see references/index_tables_guide.md for that search pattern and full schema discovery.

Available Tables

Always call client.fetch_index("table_name") before querying any index table — it is safe and idempotent for all tables, including those loaded automatically at startup.

FamilyTablesGranularity
Coreindex (primary metadata for all current data), collections_index, analysis_results_indexseries / collection / analysis result
Modality acquisition parametersct_index, mr_index, pt_index, contrast_index1 row = 1 series of that modality
Derived objectsseg_index, rtstruct_index, ann_index, ann_group_index1 row = 1 series (or annotation group)
Microscopysm_index, sm_instance_index1 row = 1 SM series / instance
Geometry, clinical, historyvolume_geometry_index, clinical_index, version_metadata_index, prior_versions_indexsee guide

references/index_tables_guide.md has the full inventory with each table's columns and contents — load it when you need to know what a specialized table actually holds.

prior_versions_index contains historical series versions, including revised versions whose DICOM SeriesInstanceUID still occurs in index. Pin crdc_series_uuid for historical content. For "what's new" in the current release use series_init_idc_version / series_revised_idc_version in the main index table, which are not equivalent to this table's min_idc_version / max_idc_version.

Joining Tables

SeriesInstanceUID is the universal join key for all series-level specialized tables: sm_index, sm_instance_index, seg_index, ann_index, ann_group_index, contrast_index, volume_geometry_index, rtstruct_index, ct_index, mr_index, pt_index. Always join these to index on SeriesInstanceUID. The exceptions below use different column names.

Join ColumnTablesUse Case
collection_idindex, prior_versions_index, collections_index, clinical_indexLink series to collection metadata or clinical data
analysis_result_idindex, analysis_results_indexLink series to analysis result metadata (annotations, segmentations)
source_DOIindex, analysis_results_indexLink by publication DOI
segmented_SeriesInstanceUIDseg_index → indexLink segmentation to its source image series (seg_index.segmented_SeriesInstanceUID = index.SeriesInstanceUID)
referenced_SeriesInstanceUIDann_index → index, rtstruct_index → indexLink annotation or RTSTRUCT to its source image series

Note: subjects, updated, and description appear in multiple tables but have different meanings (counts vs identifiers, different update contexts). A UID-only join to prior_versions_index can match several historical revisions; use CRDC UUIDs to distinguish them.

For detailed join examples, schema discovery patterns, key columns reference, and DataFrame access, see references/index_tables_guide.md.

Clinical Data Access

Clinical (non-imaging) attributes — staging, demographics, therapy — live in per-collection tables. client.fetch_index("clinical_index") loads the dictionary mapping columns to collections; client.get_clinical_table(name) returns one table as a DataFrame.

See references/clinical_data_guide.md for the discovery workflow, coded-value mapping, and joining clinical data with imaging.

Data Access Options

MethodAuthBest ForReference
idc-indexNoDownloads, pandas analysis, unbounded queries — the most capable pathThis document
IDC MCP serverNoDiscovery, cohort building, metadata when the session already has itmcp_guide.md
IDC REST APINoMetadata with no install, from any language or shell — the default when idc-index is absentrest_api_guide.md
Direct Parquet (GCS)NoVersion-pinned queries, or results past the REST row capparquet_access_guide.md
Cloud storage (S3/GCS)NoDirect file access, bulk transfer, custom pipelinescloud_storage_guide.md
DICOMweb via IDC proxyNoTool and PACS integration; daily quota, so testing and moderate usedicomweb_guide.md
DICOMweb via Google HealthcareYes (GCP)The same DICOMweb API at production volume, without the proxy quotadicomweb_guide.md
SlicerIDCBrowserNo3D visualization and analysis in 3D Slicerhttps://github.com/ImagingDataCommons/SlicerIDCBrowser
BigQueryYes (GCP)Full DICOM metadata, private elements, SR measurements — last resortbigquery_guide.md

The IDC Portal (https://portal.imaging.datacommons.cancer.gov/) is interactive only — browser-based exploration, manual cohort selection, and download. Unlike every option above it has no programmatic interface, so point a user there to browse or click through data themselves; never use it as a step in a script or workflow.

REST API — the no-install metadata path

https://api.imaging.datacommons.cancer.gov/v3, no authentication: discovery, cohort counts and manifests, read-only SQL, clinical tables, viewer URLs, licenses, citations. It is the same service as the MCP server over plain HTTP, so it needs no configuration. It never moves image bytes — switch to idc-index to download, to get a DataFrame, or for results past 10 000 rows.

B=https://api.imaging.datacommons.cancer.gov/v3
curl -s $B/version   # idc_version, idc_index_data_version, api_version
curl -s $B/stats     # collections, patients, studies, series, instances, size_TB
curl -s "$B/attributes/Modality/values?limit=5"   # real filter values, with counts
curl -s $B/sql -H 'content-type: application/json' \
  -d '{"sql":"SELECT collection_id, COUNT(*) n FROM index GROUP BY 1 ORDER BY n DESC LIMIT 3"}'
curl -s $B/cohort/counts -H 'content-type: application/json' \
  -d '{"filters":{"terms":{"collection_id":["rider_pilot"]}}}'

The filter object always goes under filters — on cohort/counts, cohort/manifest, cohort/manifest.txt, licenses, and citations alike. A bare filter or an unrecognized key is a 422 naming the fix; an unfiltered series-enumerating request is a 400, not the whole archive. Filtered JSON responses echo filters_applied and warnings (inside counts for manifests) — read them, because they name any predicate the server dropped. A zero count with empty warnings therefore means the filter matched nothing, not that a value was miscased; miscasing produces a warning that says so.

POST /sql takes one read-only SELECT/WITH over the tables idc-index exposes plus clinical.<table>; max_rows defaults to 5 000, caps at 10 000, and truncated flags clipping. GET /attributes lists the 19 filterable attributes — clinical values, segmented anatomy, and acquisition parameters are not among them and need SQL. Request limits still apply. Use v3 only: V1 and V2 are superseded and scheduled for shutdown, so port any /v1/- or Modality_btw-style example a user brings rather than extending it.

Both sides build on idc-index-data, so compare the API's idc_index_data_version against local idc_index_data.__version__ before mixing them: the major is the IDC data release (24.x.y serves v24); minor/patch index builds may correct metadata. If the API is a whole release ahead, idc-index cannot download the extra series — mixed selections can omit unrecognized UIDs, while wholly unmatched selections raise — so either upgrade it (run scripts/check_version.py for the right command) or transfer directly from the bucket with s5cmd --no-sign-request.

See references/rest_api_guide.md for the endpoint reference, filter grounding, limits, and the manifest-based download flow.

Cloud storage organization

All DICOM files live in public buckets mirrored between AWS S3 and GCS, organized by CRDC UUIDs (not DICOM UIDs) to support versioning, as <crdc_series_uuid>/<crdc_instance_uuid>.dcm. Access is free (no egress fees) via AWS CLI, gsutil, or s5cmd with anonymous access; use the series_aws_url column for S3 URLs. Bucket names do not establish a license; query license_short_name for each selected series. See references/cloud_storage_guide.md for the full bucket list and UUID mapping.

DICOMweb access

IDC data is available via DICOMweb (Google Cloud Healthcare API) for PACS integration and DICOMweb-compatible tools: a public proxy (no auth, daily quota) for testing and moderate queries, or Google Healthcare (GCP auth) for production volumes. See references/dicomweb_guide.md.

Direct Parquet access

The idc-index metadata tables are also published as Parquet on a public GCS bucket (idc-index-data-artifacts), queryable with DuckDB or pandas. This needs DuckDB installed For ad-hoc metadata prefer REST /sql; choose Parquet for pinned versions or large results. A separate public S3 export also includes clinical tables and full BigQuery metadata. See references/parquet_access_guide.md.

Core Capabilities

The patterns below are the ones that go wrong when recalled from memory rather than checked. Worked examples for each area live in the reference guides named inline.

1. Discovery — enumerate values before filtering on them

Filtering on a guessed Modality or BodyPartExamined string is the most common cause of an empty result set. Enumerate first:

modalities = client.sql_query("""
    SELECT DISTINCT Modality, COUNT(*) as series_count
    FROM index
    GROUP BY Modality
    ORDER BY series_count DESC
""")
print(modalities)

The same pattern works for any filter column, optionally narrowed by another — BodyPartExamined within a Modality, Manufacturer, collection_id. On the REST path this grounding is a single call — GET /attributes/{attr}/values returns values with counts — and the cohort endpoints report a miscased value in warnings rather than as an empty result.

Two indices carry curated collection-level metadata the primary index does not, both requiring client.fetch_index(...) first: collections_index (cancer types, tumor locations, species, subject counts) and analysis_results_index (derived datasets — AI segmentations, expert annotations, radiomics — with their source collections and modalities).

Cancer type lives in collections_index.cancer_types, not in index — filtering by cancer type requires a join:

client.fetch_index("collections_index")
results = client.sql_query("""
    SELECT i.collection_id, i.PatientID, i.SeriesInstanceUID, i.Modality
    FROM index i
    JOIN collections_index c ON i.collection_id = c.collection_id
    WHERE c.cancer_types LIKE '%Breast%'
      AND i.Modality = 'MR'
    LIMIT 20
""")

client.sql_query() returns a pandas DataFrame. Confirm column names with client.get_index_schema('index') or client.indices_overview before writing a query rather than assuming them.

See references/sql_patterns.md for filter-value discovery, annotation and segmentation queries, size estimation, clinical linking, and version tracking ("what's new in vX" — use series_init_idc_version / series_revised_idc_version in index; historical objects use prior_versions_index).

2. Downloading DICOM files

The two download methods take their first two arguments in opposite order. This is the most common source of broken IDC code — check it rather than recalling it:

MethodFirst argSecond argUse when
download_from_selectiondownloadDir (required)filter kwargs (optional)Filtering by collection, patient, study, or series
download_dicom_seriesseriesInstanceUID (required)downloadDir (required)Downloading specific series by UID only

download_from_selection takes filter keyword arguments, NOT a DataFrame. The name "from_selection" refers to filtering the IDC index by criteria — not to accepting a pandas DataFrame. To download query results, extract the UIDs into a list first:

# Step 1: Query for series UIDs
series_df = client.sql_query("""
    SELECT SeriesInstanceUID
    FROM index
    WHERE Modality = 'CT'
      AND BodyPartExamined = 'CHEST'
      AND collection_id = 'nlst'
    LIMIT 5
""")

# Step 2: Extract UIDs as a list from the DataFrame
uids = list(series_df['SeriesInstanceUID'].values)

# Step 3: Pass the list to download_from_selection (NOT the DataFrame itself)
client.download_from_selection(
    downloadDir="./data/lung_ct",
    seriesInstanceUID=uids       # list of strings, not a DataFrame
)

# Alternative: download_dicom_series has seriesInstanceUID as FIRST arg (different order!)
client.download_dicom_series(
    seriesInstanceUID=uids,      # FIRST arg here
    downloadDir="./data/lung_ct"
)

# Whole collection: downloadDir is still the FIRST positional argument
client.download_from_selection(downloadDir="./data/rider", collection_id="rider_pilot")

Both default to AWS; use source_bucket_location="gcs" for Google. In 0.12.5 the most-specific selector wins, so use SQL first for intersecting criteria and verify every requested UID exists.

Downloaded files are named <crdc_instance_uuid>.dcm, not by SOPInstanceUID. The DICOM UIDs are preserved inside the file metadata, not in the filename. Read DICOM headers for the series UID; crdc_instance_uuid is not a column of the series-level index.

idc download <collection|series-uid|manifest> --download-dir ./data does the same from a shell. See references/cli_guide.md for the dirTemplate hierarchy options (Python default: %collection_id/%PatientID/%StudyInstanceUID/%Modality_%SeriesInstanceUID; dirTemplate="" flattens), manifest downloads with resume, and dry-run size estimation.

3. Visualizing IDC images
viewer_url = client.get_viewer_URL(seriesInstanceUID=uid)        # one series
viewer_url = client.get_viewer_URL(studyInstanceUID=study_uid)   # all series in a study

Returns a browser URL — nothing is downloaded. The method selects OHIF v3 for radiology or SLIM for slide microscopy automatically. Viewing by study is useful when a single DICOM Study holds several Series (T1, T2, and DWI from one MRI session).

4. Licenses and citations — obligations, not optional steps

IDC data carries license terms and attribution requirements that follow it into any downstream publication or product, and neither is inferable from the pixel data. Check the license before use, and generate citations for whatever you download.

# License breakdown for a selection
licenses = client.sql_query("""
    SELECT DISTINCT collection_id, license_short_name,
           COUNT(DISTINCT SeriesInstanceUID) as series_count
    FROM index GROUP BY collection_id, license_short_name
""")

# Citations for the same selection you downloaded (APA by default)
for citation in client.citations_from_selection(collection_id="rider_pilot"):
    print(citation)

In the reviewed v24 snapshot, about 97% of IDC data by size is CC BY (commercial use allowed with attribution) and about 3% is CC BY-NC (non-commercial only). Licenses attach to series, not collections — 39 of 176 collections carry more than one — so check the selection you actually intend to use, and note that each component retains its license obligations.

Both tasks are available from all three access paths, so stay on whichever one the session is already using: idc-index as above, POST /v3/licenses and POST /v3/citations over REST, or the get_licenses and get_citations MCP tools. See references/licensing_and_citation.md for the full license inventory, all three routes, the citation formats (APA, BibTeX, CSL JSON, RDF Turtle), and what to include when publishing.

5. Reaching past the index

Pick the access path with the routing gate in Overview; Data Access Options above is the full routing table.

Before reaching for BigQuery (which needs a Google Cloud project and access), check whether a specialized index table already has the column you want: search client.indices_overview, then client.fetch_index(...) and query locally for free. Full instance metadata, per-segment rows, and SR measurement tables require BigQuery or its public Parquet exports; these are outside the compact idc-index tables.

Best Practices

  • Check schema before writing queries — Use client.get_index_schema('index') (reads cached metadata, no SQL executed) or client.indices_overview to see all available columns and their descriptions. The version-tracking columns series_init_idc_version and series_revised_idc_version in the main index table directly answer "what's new / when was this added" questions without touching prior_versions_index.

  • Use the index for IDC data content questions - Query the IDC index directly, via client.sql_query() locally or POST /v3/sql over HTTP. Web sources (release notes, blog posts, documentation pages) are frequently out of date and will produce incorrect answers. The index is the authoritative source; use it even when web search is available.

  • Verify the IDC data version at the start of a session - client.get_idc_version(), GET /v3/version, or the MCP get_idc_version tool, depending on the path in use (currently v24). For a stale local index, run scripts/check_version.py and use the upgrade command it prints

  • Check licenses and generate citations - Query license_short_name and respect CC BY vs CC BY-NC terms; use citations_from_selection() to produce citations from source_DOI for publications

  • Explore small, then commit - Use LIMIT (or a low max_rows) while exploring, and check collection size before downloading — some collections are terabytes. See references/cli_guide.md

  • Keep downloads reproducible - Organize with dirTemplate (e.g. %collection_id/%PatientID/%Modality) and save the Series UIDs or manifest behind any dataset you build

Troubleshooting

Issue: ModuleNotFoundError: No module named 'idc_index'

  • Cause: idc-index package not installed

  • Solution: If the task is read-only metadata, do not install it — use the REST API instead (Data Access Options). Otherwise run scripts/check_version.py and use the install command it prints, which targets the running interpreter and pins the vetted version. For data analysis also add pandas, numpy, and pydicom (tested with pandas>=1.5, numpy>=1.23, pydicom>=2.3)

Issue: Download fails with connection timeout

  • Cause: Network instability or large download size

  • Solution: Download in smaller batches (10-20 series); see references/cli_guide.md for --use-s5cmd-sync resume and retry guidance

Issue: BigQuery quota exceeded or billing errors

  • Cause: Project quotas, sandbox limits, or billing configuration prevent the query

  • Solution: Use idc-index mini-index for simple queries (no billing required), or see references/bigquery_guide.md for cost optimization tips

Issue: Series UID not found or no data returned

  • Cause: Typo in UID, data not in the current IDC version, or wrong field name

  • Solution: Test with LIMIT 5 first, check field names against client.indices_overview, and confirm the series is in the current version (some old data is deprecated)

Issue: Column not found in index table (e.g., SliceThickness, PixelSpacing, KVP, EchoTime, InjectedDose)

  • Cause: The index table contains series-level metadata only; modality-specific acquisition and reconstruction parameters live in dedicated tables (ct_index, mr_index, pt_index)

  • Solution: Search client.indices_overview for the column to find its table — the loop is under Finding which table contains a column in references/index_tables_guide.md — then fetch and join on SeriesInstanceUID:

    client.fetch_index("ct_index")
    result = client.sql_query("""
        SELECT i.SeriesInstanceUID, i.Modality, c.SliceThickness, c.KVP, c.PixelSpacing_row_mm
        FROM index i
        JOIN ct_index c USING (SeriesInstanceUID)
        WHERE i.collection_id = 'your_collection'
    """)

Issue: Downloaded DICOM files won't open

  • Cause: Corrupted download, or an object type the viewer does not handle — SEG, RTSTRUCT, SR, and slide microscopy all need specialized tools

  • Solution: Inspect Modality, SOPClassUID, and transfer syntax with normal pydicom.dcmread; forced parsing is not validation. Check download integrity and decoder/viewer support before re-downloading; reserve force=True for diagnosed non-Part-10 inputs.

Resources

Reference guides and their decision triggers are listed in Quick Navigation above.

Files

references/rest_api_guide.md

IDC REST API Guide

Verified 2026-09-30: all 21 REST routes, OpenAPI request/response contracts, and representative validation errors. API 3.0.0b3 (build 0640860), IDC data version v24, idc_index_data_version 24.2.2

IDC operates a hosted REST API that exposes discovery, cohort building, metadata SQL, and download manifests over plain HTTP. No authentication, account, or credentials are required — HTTP examples use curl; the download step separately requires idc-index or a transfer tool.

The API and the MCP server are the same service behind two transports: the MCP tools wrap these endpoints. Both are hosted against a current IDC release, independently of the idc-index version installed locally.

When to Use This Guide

Load this guide when you need to:

  • Query IDC from a language or environment with no idc-index install (shell, R, Java, JS, a notebook without pip access)

  • Build cohorts and manifests over HTTP for a pipeline or web application

  • Run metadata SQL against a current IDC release without downloading local index files

  • Hand a user copy-pasteable curl commands they can run anywhere

For downloads, pandas/notebook analysis, reading pixel data, DICOMweb, BigQuery, or digital pathology tiling, use idc-index as documented in SKILL.md. The API never moves image bytes: it returns public s3:// URLs and manifests, and the transfer happens directly from cloud storage to the client.

Choosing among the three interfaces:

SituationUse
Session already has the hosted MCP serverMCP tools (mcp_guide.md) — same data, no HTTP plumbing
idc-index already installed, results feed pandas / downloadsidc-index (SKILL.md)
idc-index not installed and the task is read-only metadataThis REST API — do not install to answer a metadata question
No Python, or a non-Python client, or shell commands the user re-runsThis REST API
Installed idc-index is a whole IDC data release behind and cannot be upgraded hereREST API for the query, direct bucket transfer may be needed for the download — idc-index cannot fetch what its index does not list

Being hosted, the API always serves a current IDC release — but so does an up-to-date idc-index. "I want the newest data" is not on its own a reason to prefer the API: the normal fix for a stale local index is to upgrade it. Reach for the API on version grounds only when upgrading is not an option in that environment. Avoiding the install is a reason, though: the packaged index data is ~77 MB before pandas, pyarrow, and duckdb, which a metadata question does not need.

Endpoint and Versioning

PropertyValue
Base URLhttps://api.imaging.datacommons.cancer.gov/v3
AuthenticationNone
Content typeapplication/json (request and response), except cohort/manifest.txt → text/plain
Interactive docshttps://api.imaging.datacommons.cancer.gov/v3/docs (Swagger UI)
OpenAPI spechttps://api.imaging.datacommons.cancer.gov/v3/openapi.json
Sourcehttps://github.com/ImagingDataCommons/IDC-REST-MCP

v3 is in beta. The contract may still change before the final 3.0.0 release, so verify the running build rather than assuming the values in this guide:

curl -s https://api.imaging.datacommons.cancer.gov/v3/version
# {"idc_version":"v24","idc_index_data_version":"24.2.2","api_version":"3.0.0b3","build":"0640860"}

idc_version is the IDC data release the API serves and is the authority when the API is in use — prefer it over the idc-data-version pinned in the SKILL.md frontmatter, which records the release the skill was last verified against.

Checking the API against a local idc-index

The API and idc-index are built on the same idc-index-data package, and both report its version, so consistency is an exact check rather than a guess:

SideData release (coarse)idc-index-data version (exact)
APIGET /v3/version → idc_versionGET /v3/version → idc_index_data_version
LocalIDCClient().get_idc_version()idc_index_data.__version__
import idc_index_data
import requests

response = requests.get("https://api.imaging.datacommons.cancer.gov/v3/version", timeout=30)
response.raise_for_status()
api = response.json()
api_version, local_version = api["idc_index_data_version"], idc_index_data.__version__

if api_version.split(".")[0] != local_version.split(".")[0]:
    print(f"Different IDC data release: API {api_version}, local {local_version} — upgrade")
elif api_version != local_version:
    print(f"Same data release, different index build: API {api_version}, local {local_version}")
    # → Same data release, different index build: API 24.2.2, local 24.2.0

The major is the IDC data release; the rest is the index build. idc-index-data 24.x.y serves IDC v24 — 24.0.0 shipped with the v24 release, and 24.1.0 / 24.2.x are later builds of the same release. What differs between them is the index itself (added tables and columns, corrected metadata). Pin the full index build and compare requested UUIDs; matching release labels alone do not establish that two queries or exported manifests are identical.

So read a mismatch by its position:

DifferenceMeansConsequence
Major (24.x.y vs 25.x.y)Different IDC data releaseSeries added, revised, or removed. Counts legitimately differ, and idc-index cannot download what its index does not list
Minor or patch (24.2.0 vs 24.2.2)Same data release, different index buildSame intended data release; verify selected UUIDs. Metadata corrections can change query results

Comparing idc_version alone cannot make this distinction in the other direction either — the vNN label is exactly the major, so matching v24 on both sides tells you the release agrees but says nothing about the index build.

When the two disagree, say so and name both versions, then reconcile rather than mixing results: upgrading idc-index brings the local side to the newer idc-index-data, and python scripts/check_version.py reports whether an upgrade is available and prints the command for the interpreter you are running. Do not present API-derived and locally-derived counts side by side as if they came from one index.

A major behind also breaks downloads. idc-index resolves every s3:// URL it is given against its own index, so a manifest built from a newer IDC data release can name series it has never heard of — see When the local index is a data release behind the API under Getting the Data.

Use v3 only — V1 and V2 are being retired

Do not write new code against the V1 or V2 IDC APIs, and do not follow V1/V2 examples found in older tutorials, notebooks, blog posts, or forum answers. Both are superseded by v3 and are scheduled to be deprecated and shut down; code written against them will stop working. If a user brings V1 or V2 code, say so and port it to v3 rather than extending it.

Two signals that a snippet is V1/V2 rather than v3, both of which will fail against /v3:

  • a base URL other than https://api.imaging.datacommons.cancer.gov/v3 (for example a /v1/ or /v2/ path segment)

  • per-attribute filter suffixes such as Modality_btw or _lt / _gt, instead of v3's separate terms and ranges objects

V1/V2 documentation survives only in the IDC docs archive for historical reference. Treat it as read-only history, not as a source of working examples.

The Query Surfaces

The endpoints group into five surfaces that build on each other:

SurfaceAnswersEndpoints
DiscoveryWhat exists? What can I filter on?GET /version, /stats, /collections, /collections/{id}, /analysis_results, /attributes, /attributes/{attr}/values
CohortHow big is my selection, and what's in it?POST /cohort/counts, POST /cohort/manifest
RetrievalGive me the download linksPOST /cohort/manifest.txt
SQLAnything relational or aggregateGET /tables, GET /tables/{table}, POST /sql
Side toolsView / cite / license-check a cohortGET /viewer-url, POST /citations, POST /licenses

Discovery supplies the vocabulary (attribute names and their valid values) that the cohort filters consume. SQL is the escape hatch for questions structured filters cannot express.

Clinical data has its own discovery endpoints (GET /clinical/tables, /clinical/tables/{table}, /clinical/tables/{table}/rows) and is filtered or joined through SQL against the clinical schema.

Endpoint Reference

All paths are relative to https://api.imaging.datacommons.cancer.gov/v3.

Method & pathPurposeKey response fields
GET /versionIDC data release + API buildidc_version, idc_index_data_version, api_version, build
GET /statsHeadline totalscollections, patients, studies, series, instances, size_TB
GET /collectionsList collections (JSON array)collection_id, collection_name, cancer_types, tumor_locations, species, subjects, description
GET /collections/{id}Collection detailthe above plus patients, studies, series, instances, size_TB, modalities[], licenses[]
GET /analysis_resultsDerived datasets (JSON array)analysis_result_id, analysis_result_title, source_DOI, subjects, collections, modalities, license_short_name
GET /attributesFilterable attributesname, table, data_type, kind (term | range), categorical, description
GET /attributes/{attr}/values?limit=Distinct values with countsattribute, values[{value,count}], truncated, note
GET /tablesTables available to SQLtables[{name,description,column_count}]
GET /tables/{table}Column schemaname, description, columns[{name,type,description}]
GET /clinical/tables?collection_id=Clinical tables, optionally one collectiontables[{table_name,sql_path,collection_id,column_count}]
GET /clinical/tables/{table}Clinical columns + labelsname, columns[{name,type,description}]
GET /clinical/tables/{table}/rows?max_rows=Clinical rows (capped)columns, rows, row_count, truncated, max_rows
POST /cohort/countsDistinct counts for a filter (cheap)patients, studies, series, instances, size_TB
POST /cohort/manifestCounts + page of series + download payloadcounts, page, page_size, returned, total_series, series[], download
POST /cohort/manifest.txtFull manifest as text/plainone s3://…/* URL per line
POST /sqlGuarded read-only SQL (DuckDB)columns, rows, row_count, truncated, max_rows
GET /viewer-urlOHIF / Slim viewer linkviewer_url, viewer, study_instance_uid, series_instance_uid
POST /citationsCitations for a cohortformat, citations[], idc_acknowledgment, recommendation
POST /licensesLicense breakdown for a cohortlicenses[{license_short_name,series,size_TB}]

GET /v3/health and GET /v3 (API root) also exist for liveness checks.

Filter Syntax

Cohort filters are shared by cohort/counts, cohort/manifest, cohort/manifest.txt, licenses, and citations. The filter object always goes under a filters key, on every one of them. It has two parts:

  • terms — {attribute: [values]} for equality/membership. Values are OR'd within an attribute and AND'd across attributes.

  • ranges — {attribute: {"gte": x, "lte": y}} for numeric and date attributes. Either bound may be omitted for an open-ended range.

{
  "filters": {
    "terms": {"Modality": ["CT"], "collection_id": ["nlst"]},
    "ranges": {"instanceCount": {"gte": 100, "lte": 200}}
  }
}

Filters operate only on the index table's filterable attributes — 19 of them as of 3.0.0b3:

KindAttributes
termcollection_id, analysis_result_id, PatientID, StudyInstanceUID, SeriesInstanceUID, Modality, BodyPartExamined, Manufacturer, ManufacturerModelName, PatientSex, sop_class_name, license_short_name, source_DOI
rangeinstanceCount, series_size_MB, series_init_idc_version, series_revised_idc_version, StudyDate, SeriesDate

SeriesInstanceUID, StudyInstanceUID, and PatientID being filterable is what makes the side tools work at any granularity — the licenses or citations for a single series are just a one-value filter.

Anything outside this list — clinical values, segmented anatomy, per-modality acquisition parameters — is not filterable here; use the SQL surface instead.

Ground values before filtering. Call GET /attributes for what is filterable and whether it is a term or a range, then GET /attributes/{attr}/values for real values and their casing — values are matched case-sensitively.

The server reports what it filtered on

Filtered JSON responses echo filters_applied and warnings (nested under counts in a manifest response), and misuse is refused rather than ignored. Together these make a result self-describing, so you do not have to sanity-check a count against a number you happen to remember.

MistakeResponse
Bare filter object, no filters key422 naming the correct shape
Unrecognized key at any depth (term for terms, a range bound spelled min)422 pointing at the key
Unknown filter attribute, or a range attribute used as a term400 naming the discovery call
Unfiltered cohort/manifest or manifest.txt400 — it will not enumerate the whole archive
Unfiltered cohort/counts or licenses200 plus an explicit "ENTIRE IDC archive" warning
A predicate that constrains nothing ({"collection_id": []}, {"instanceCount": {}})200, and warnings names the ignored predicate
Miscased value (mr for MR)200, zero counts, and a warning naming the casing that exists
curl -s $B/cohort/counts -H 'content-type: application/json' \
  -d '{"filters": {"terms": {"collection_id": ["rider_pilot"]}}}'
# {"patients":8,"studies":154,"series":774,"instances":21111,"size_TB":0.011,
#  "filters_applied":{"terms":{"collection_id":["rider_pilot"]},"ranges":{}},"warnings":[]}

Read warnings before reporting any count. A zero count with warnings: [] means the filter applied and matched nothing — that is a real answer. A zero count with a casing warning means the filter was wrong. And filters_applied covers the case no shape check can: a request carrying one good predicate plus one that constrains nothing returns a perfectly plausible number, and only warnings reveals the dropped half.

cohort/manifest.txt returns text/plain and so cannot carry these fields; there the required-predicate 400 does the same job, appending the ignored-predicate reason to its message.

Worked Examples

Discovery
B=https://api.imaging.datacommons.cancer.gov/v3

curl -s $B/version                      # data release + API build
curl -s $B/stats                        # headline totals
curl -s $B/collections                  # collection array (176 in the reviewed snapshot)
curl -s $B/collections/rider_pilot      # one collection: counts, modalities, licenses
curl -s $B/analysis_results             # derived datasets (segmentations, annotations)
curl -s $B/attributes                   # what can be filtered, and how
curl -s "$B/attributes/Modality/values?limit=10"
Cohort building

Check size first — counts is cheap and answers "is this download sane?":

curl -s $B/cohort/counts \
  -H 'content-type: application/json' \
  -d '{"filters": {"terms": {"Modality": ["MR"], "BodyPartExamined": ["BREAST"]}}}'
# {"patients":3718,"studies":5689,"series":47986,"instances":6493262,"size_TB":2.421,
#  "filters_applied":{...},"warnings":[]}

Then request a page of series plus the download payload — same filter, same filters key.

curl -s $B/cohort/manifest \
  -H 'content-type: application/json' \
  -d '{"filters": {"terms": {"Modality": ["MR"], "BodyPartExamined": ["BREAST"]}},
       "page": 0, "page_size": 3}'

Each series[] row carries collection_id, PatientID, StudyInstanceUID, SeriesInstanceUID, Modality, SeriesDescription, instanceCount, series_size_MB, aws_bucket, crdc_series_uuid, and series_aws_url. Set "include_rows": false to get counts and the download payload without the rows.

SQL
curl -s $B/sql \
  -H 'content-type: application/json' \
  -d '{"sql": "SELECT Modality, count(*) n FROM index GROUP BY 1 ORDER BY n DESC", "max_rows": 20}'
Viewer, licenses, citations
curl -s "$B/viewer-url?study_instance_uid=1.3.6.1.4.1.14519.5.2.1.7695.4164.129908397467389975396031099306"

curl -s $B/licenses \
  -H 'content-type: application/json' \
  -d '{"filters": {"terms": {"collection_id": ["rider_pilot"]}}}'

curl -s $B/citations \
  -H 'content-type: application/json' \
  -d '{"filters": {"terms": {"collection_id": ["rider_pilot"]}}, "citation_format": "bibtex"}'

viewer-url takes series_instance_uid or study_instance_uid (and an optional viewer override); it picks OHIF v3 for radiology and Slim for slide microscopy automatically.

citation_format is one of apa (default), bibtex, csl-json, turtle. The response separates the per-dataset citations[] from idc_acknowledgment, the IDC paper — include both when publishing.

Python client
import requests

BASE = "https://api.imaging.datacommons.cancer.gov/v3"
session = requests.Session()

def get(path, **params):
    r = session.get(f"{BASE}{path}", params=params, timeout=60)
    r.raise_for_status()
    return r.json()

def post(path, payload):
    r = session.post(f"{BASE}{path}", json=payload, timeout=120)
    r.raise_for_status()
    return r.json()

# 1. Confirm which IDC release the API is serving
print(get("/version")["idc_version"])

# 2. Ground the filter values before using them
modalities = {v["value"] for v in get("/attributes/Modality/values", limit=1000)["values"]}
assert "MR" in modalities

# 3. Size the cohort, then page through it — the filter always goes under `filters`
filters = {"terms": {"collection_id": ["rider_pilot"], "Modality": ["CT"]}}
counts = post("/cohort/counts", {"filters": filters})
assert not counts["warnings"], counts["warnings"]   # nothing was silently dropped
print(counts)

uids = []
series_rows = []
page = 0
while True:
    manifest = post("/cohort/manifest", {"filters": filters, "page": page, "page_size": 100})
    assert not manifest["counts"]["warnings"], manifest["counts"]["warnings"]
    assert manifest["total_series"] == counts["series"], "Cohort changed during pagination"
    batch = [row["SeriesInstanceUID"] for row in manifest["series"]]
    uids.extend(batch)
    series_rows.extend(manifest["series"])
    if len(uids) >= manifest["total_series"]:
        break
    if not batch:
        raise RuntimeError("Manifest pagination ended before the declared total")
    page += 1
assert len(set(uids)) == counts["series"]

# 4. Hand off to idc-index for the download (see "Handing Off to idc-index" below)

Error responses raise through raise_for_status(). HTTP 400 commonly uses error.message; HTTP 422 uses detail[]. Inspect the actual body before retrying.

The SQL Surface

POST /sql runs read-only DuckDB SQL against the same tables idc-index exposes locally. Ground the schema with GET /tables and GET /tables/{table} — do not guess table or column names.

Guardrails (verified against 3.0.0b3):

  • Only single read-only SELECT / WITH … SELECT statements are accepted. Anything else is rejected with {"error": {"code": "invalid_query", "message": "Only read-only SELECT (or WITH ... SELECT) statements are allowed."}} and HTTP 400.

  • A server row cap and per-query timeout apply. max_rows defaults to 5000 and is clamped to 10000; results carry truncated: true when the cap was hit, and echo the max_rows actually applied.

  • Invalid SQL returns DuckDB's own error text, including its "Candidate bindings" suggestions — useful for fixing a misspelled column without another schema round-trip.

Tables reachable from SQL are the index table plus the specialized indices documented in SKILL.md (collections_index, analysis_results_index, version_metadata_index, prior_versions_index, seg_index, ann_index, ann_group_index, rtstruct_index, ct_index, mr_index, pt_index, sm_index, sm_instance_index, contrast_index, volume_geometry_index, clinical_index). Join them to index on SeriesInstanceUID, except where SKILL.md documents a different key (segmented_SeriesInstanceUID, referenced_SeriesInstanceUID, collection_id, analysis_result_id).

The SQL patterns in references/sql_patterns.md are written for client.sql_query() but the SQL itself transfers unchanged — send it as the sql field.

Array columns: columns typed STRING[] (e.g. SegmentedPropertyType_CodeMeanings) hold a list per row. Match with list_contains(col, 'value'), not = or LIKE:

curl -s $B/sql -H 'content-type: application/json' -d '{
  "sql": "SELECT i.collection_id, count(DISTINCT i.SeriesInstanceUID) AS slides FROM index i JOIN seg_index seg ON seg.segmented_SeriesInstanceUID = i.SeriesInstanceUID WHERE i.Modality = '\''SM'\'' AND list_contains(seg.SegmentedPropertyType_CodeMeanings, '\''Nucleus'\'') GROUP BY 1 ORDER BY slides DESC",
  "max_rows": 20}'

A SQL result can be a manifest. Selecting series_aws_url gives download URLs directly, and unlike cohort/manifest.txt you can carry extra columns alongside them — a per-row license_short_name, for instance, which the plain manifest does not include:

SELECT SeriesInstanceUID, license_short_name, series_aws_url
FROM index WHERE collection_id = 'rider_pilot'

For bulk series, cohort/manifest.txt has a separate limit. Compare its nonempty URL-line count with cohort/counts.series; it has no response-level truncation flag.

Clinical data

Clinical data comes in two layers:

  • clinical_index — a data dictionary, one row per (collection, table, column) with a human-readable column_label and coded values. It is an ordinary SQL table and joins to index on collection_id.

  • Per-collection clinical tables (e.g. nlst_canc) — the actual rows. There are ~150, so they are kept out of GET /tables and discovered through the /clinical/tables endpoints instead. In SQL they live under a separate schema and are addressed as clinical.<table>.

Clinical tables join to imaging on dicom_patient_id = index.PatientID, not on a series UID. Clinical data is not harmonized across collections — table and column names vary, so always discover before querying.

curl -s "$B/clinical/tables?collection_id=nlst"            # which tables this collection has
curl -s $B/clinical/tables/nlst_canc                       # columns + labels
curl -s "$B/clinical/tables/nlst_canc/rows?max_rows=100"   # rows, capped

curl -s $B/sql -H 'content-type: application/json' -d '{
  "sql": "SELECT count(DISTINCT i.PatientID) AS patients FROM index i JOIN clinical.nlst_canc c ON c.dicom_patient_id = i.PatientID WHERE i.collection_id = '\''nlst'\'' AND i.Modality = '\''CT'\'' AND c.clinical_stag = '\''400'\''"}'

See references/clinical_data_guide.md for value mapping and the wider clinical data model.

Getting the Data

The API returns manifests; a client moves the bytes. Every URL points at public AWS S3 or GCS buckets and needs no credentials.

# save a manifest after checking the cohort count
curl --fail -sS $B/cohort/manifest.txt \
  -H 'content-type: application/json' \
  -d '{"filters": {"terms": {"collection_id": ["rider_pilot"]}}}' > idc_manifest.txt

# download it (needs idc-index installed)
idc download-from-manifest --manifest-file idc_manifest.txt --download-dir ./idc-data

The reviewed server also emits a positional download-from-manifest idc_manifest.txt command in download.idc_commands; that command is incompatible with idc-index 0.12.5. Use the explicit --manifest-file form above, or idc download idc_manifest.txt.

For a filter that is a single collection_id, the download payload of cohort/manifest emits the simpler idc download <collection_id> --download-dir ./idc-data form.

When the local index is a data release behind the API

This applies when the two idc-index-data majors differ — the API serving 25.x.y against a local 24.x.y, say. Builds of the same release usually resolve the same series, but verify selected CRDC UUIDs before treating metadata or download manifests as equivalent.

idc download-from-manifest does not simply hand the URLs to a transfer client: it extracts each crdc_series_uuid from the manifest and joins it against the local index (then against prior_versions_index) to compute sizes and build the output hierarchy. A manifest produced by an API serving a newer IDC data release can therefore contain series the local index has never heard of.

Those rows are not downloaded, and the command does not fail — it logs, then continues with the rest:

The total of N copy commands are not recognized as referencing any associated series in the
main index. ... they may correspond to files available in a release of IDC different from v24
used in this version of idc-index.
...
The corresponding files could not be downloaded.

The result is a partial download that otherwise looks successful. download_from_selection(seriesInstanceUID=…) filters the local index: missing UIDs in a partially matched list can be omitted, while an entirely unmatched list raises ValueError in 0.12.5. Check the requested-minus-found UID set before transferring.

Fix it one of two ways:

  1. Upgrade — upgrade idc-index (python scripts/check_version.py prints the command), then re-run. This is the right answer whenever it is possible; it restores the hierarchy, size checks, and progress reporting.

  2. Bypass the index — transfer directly from the bucket. The manifest URLs are self-contained (s3://<bucket>/<crdc_series_uuid>/*), so no index is needed at all:

# one directory per series, named by crdc_series_uuid
awk -F/ '{print "cp " $0 " ./idc-data/" $4 "/"}' idc_manifest.txt > s5cmd_commands.txt
s5cmd --no-sign-request run s5cmd_commands.txt

# for source=gcs manifests, add the GCS endpoint
s5cmd --no-sign-request --endpoint-url https://storage.googleapis.com run s5cmd_commands.txt

s5cmd run expects one command per line, which is why the bare URLs are rewritten as cp commands; drop the $4 segment to land every file flat in ./idc-data/. aws s3 cp --no-sign-request --recursive works the same way per URL.

What you give up by bypassing idc-index is convenience, not data: files are laid out under CRDC UUIDs instead of %collection_id/%PatientID/%Modality, and there is no local size or disk-space check — so read size_TB from cohort/counts first. See references/cloud_storage_guide.md for bucket layout, aws/gsutil equivalents, and UUID-to-DICOM-UID mapping.

source: aws (default) vs gcs. Both return s3:// URLs — GCS is reached through its S3-compatible endpoint, never a gs:// URL. That is why idc download-from-manifest only recognizes s3:// lines. Driving s5cmd yourself, use --no-sign-request, and for source=gcs add --endpoint-url https://storage.googleapis.com.

Report series and size_TB from cohort/counts before a broad download and keep the transfer within the user-authorized scope (the reviewed v24 API reports ~99 TB archive-wide).

Limits, Defaults, and Errors

Verified against 3.0.0b3. POST SQL and manifest page sizes are clamped; GET query parameters outside their OpenAPI bounds return HTTP 422. Read the response limits rather than assuming the requested size was honored.

EndpointParameterDefaultCap
GET /attributes/{attr}/valueslimit10010000
POST /sqlmax_rows500010000
GET /clinical/tables/{table}/rowsmax_rows5000100000
POST /cohort/manifestpage_size1005000
POST /cohort/manifestpage0—
POST /cohort/manifestinclude_rowstrue—
POST /cohort/manifest.txtlimit100000No enforced ceiling in reviewed source; keep requests bounded
POST /cohort/manifest.txtsourceaws—
POST /citationscitation_formatapa—

cohort/manifest.txt defaults to 100 000 URL lines and silently drops overflow from the text response. It has no pagination or truncated field. Compare line count against the matching cohort count, then explicitly set a bounded limit or partition the cohort. The JSON manifest paginates series[]; its download.manifest_truncated concerns the default full-manifest limit, not whether the current page is complete.

The reviewed server source has no application-level per-caller rate limiter. This does not guarantee unlimited infrastructure capacity or exclude HTTP 429 from a gateway. Source defaults include a 30 s SQL timeout and 4 GB query memory; deployed settings can differ. Honor Retry-After on 429 and retry transient 503 responses with bounded backoff. For sustained heavy metadata access, query the idc-index Parquet files (references/parquet_access_guide.md) or BigQuery instead of driving this API hard.

Size-capped responses carry a truncated boolean: false means the result is complete, true means raise the limit or aggregate/narrow instead. Explore narrow, then widen.

Errors come in two shapes. Semantic problems are HTTP 400 with a uniform body:

{"error": {"code": "invalid_query", "message": "Unknown or non-term filter attribute: 'NotAnAttribute'. Use list_attributes to see valid attributes."}}

Request-shape problems are HTTP 422 with FastAPI's detail[] array, which names the offending key — a bare filter object, an unrecognized key, or a misspelled range bound all land here. Both kinds are actionable: an unknown attribute names the discovery call to make, and a bad column name carries DuckDB's candidate bindings. Read the message and fix the request rather than retrying it unchanged.

What does not produce an error is a filter that is valid but empty or over-broad. Those return HTTP 200 with a warnings entry saying so — see The server reports what it filtered on. Read warnings; do not infer from the count alone.

Handing Off to idc-index

The boundary artifact is a list of SeriesInstanceUID values (or a saved manifest.txt).

# UIDs from a cohort/manifest or /sql response
series_uids = uids  # all pages collected above, not just the last manifest page

from idc_index import IDCClient
client = IDCClient()
local = client.index[client.index["SeriesInstanceUID"].isin(series_uids)]
local_uuids = dict(zip(local["SeriesInstanceUID"], local["crdc_series_uuid"]))
mismatched = [r["SeriesInstanceUID"] for r in series_rows
              if local_uuids.get(r["SeriesInstanceUID"]) != r["crdc_series_uuid"]]
if mismatched:
    raise ValueError(f"{len(mismatched)} series absent or revised locally; use the pinned URL manifest")

client.download_from_selection(
    downloadDir="./data",
    seriesInstanceUID=series_uids,        # a list, not a DataFrame
    dirTemplate="%collection_id/%PatientID/%Modality",
)

Run python scripts/check_version.py before the first idc-index call in a session, even when discovery happened over the API — the two version independently. Compare idc_index_data_version on both sides first (see Checking the API against a local idc-index).

This UID handoff must resolve to the same CRDC series versions on both sides. idc-index can only select series its local index lists, so when the API is a release ahead, UIDs and manifest URLs it returned may resolve to nothing locally: download_from_selection can omit a missing subset (or raise when none match), and download-from-manifest logs unrecognized entries and skips them. Do not report that as "no data": name both versions, then either upgrade idc-index or download straight from the bucket, as described in When the local index is a data release behind the API.

What the API Does Not Cover

  • Image bytes. The API returns URLs and manifests only; files transfer from S3/GCS.

  • Pixel data access and DICOMweb. Use references/dicomweb_guide.md.

  • Full DICOM metadata, per-segment detail, SR quantitative/qualitative measurements, private DICOM elements. Use BigQuery or full public metadata Parquet exports — see references/bigquery_guide.md.

  • Writes. The service is read-only by construction; there are no POST endpoints that mutate state, and the SQL connection rejects anything but SELECT.

  • Local analysis. DataFrames, plotting, pydicom/SimpleITK, pathology tiling all stay with idc-index.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python

The author says: Requires network access for hosted APIs, index fetching, citations, and downloads. Local Python workflows target idc-index 0.12.5; BigQuery and Google Healthcare require Google credentials.

Security

What our scanner noticed· Low risk

  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Andrey Fedorov, @fedorov
Publisher
K-Dense (Community)
Distributed by
K-Dense
Source
K-Dense-AI/scientific-agent-skills · skills/imaging-data-commons · commit a25cf9e
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Andrey Fedorov, @fedorov or K-Dense.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download imaging-data-commons.zip