API

Read-only JSON access to the Endoderm Perturb-Seq dataset. No authentication, no rate limit.
https://huangfulab.com/endoderm-perturbseq/api/v1
Using this from a script or an AI agent? Point it at https://huangfulab.com/endoderm-perturbseq/api/v1/ — a single JSON document describing every endpoint, parameter, enum value, error code and example response. Nothing on this page is missing from it.
Quickstart
Python
import requests

BASE = "https://huangfulab.com/endoderm-perturbseq/api/v1"

gene = requests.get(f"{BASE}/gene/SOX17").json()["data"]
print(gene["supermodule"], len(gene["elements"]), "linked peaks")

# Paginated endpoints expose links.next; follow it until it is null.
def fetch_all(path, **params):
    rows, page = [], 1
    while True:
        r = requests.get(f"{BASE}{path}", params={**params, "page": page, "per_page": 500})
        r.raise_for_status()
        body = r.json()
        rows += body["data"]
        if not body["links"]["next"]:
            return rows
        page += 1
R
library(httr2)
library(jsonlite)

base <- "https://huangfulab.com/endoderm-perturbseq/api/v1"

gene <- request(paste0(base, "/gene/SOX17")) |>
  req_perform() |>
  resp_body_json(simplifyVector = TRUE)

gene$data$supermodule
as.data.frame(gene$data$elements)
Conventions
Response shape
Single objects return {"data": {...}, "links": {...}}. Collections return {"data": [...], "page", "per_page", "total", "pages", "links"}. links sits beside data, never inside it, so data flattens cleanly into a data frame.
Errors
Always JSON: {"error": "...", "status": 404, "code": "gene_not_found", "path": "..."}. The code is stable and safe to branch on; the error text is for humans and may be reworded.
Pagination
1-based page with per_page (default 25, maximum 500). A per_page above the maximum is rejected rather than silently reduced, so a short response always means you reached the end. Follow links.next until it is null.
Missing and infinite values
Absent measurements are null. Where a Fisher test separated perfectly, the odds ratio is reported as null with odds_ratio_infinite: true beside it — bare Infinity is not valid JSON and a sentinel number would be mistaken for a measurement.
Optional lists
Single-object responses stay small. Long lists — a gene's GO terms and perturbation effects, a module's genes and regulators — are opt-in with include=, and the default response carries their size as n_<field>.
Links
Every URL in a response is absolute and can be fetched as-is, so an assistant that only fetches URLs it has already seen can still follow links, row url fields and pagination.
Truncation
Bounded lists carry a companion *_truncated flag, so a capped list is always distinguishable from a genuinely short one.
Names
Genes accept a symbol, an Ensembl gene ID, or a synonym; when the input was not the canonical symbol, meta.resolved_from and meta.match_type say what matched. Gene clusters accept GC1, TC-1, gene_cluster_1 or cluster_1 and always answer with the public GC1 form.
Limits
Responses are capped at 5 MiB and queries at 5 s (2 s for collections and search). Exceeding either returns a JSON error, not a truncated body.
CORS
Open to all origins on https://huangfulab.com/endoderm-perturbseq/api/v1, so browser-side tools can call it directly.
Objects
GET https://huangfulab.com/endoderm-perturbseq/api/v1/gene/{gene}

One gene: identifiers, coordinates, expression over the differentiation time course, module membership and gRNAs, plus counts of its GO annotations, perturbation effects and linked ATAC peaks. The lists themselves are opt-in via include=.

ParameterTypeDescription
gene required string HGNC symbol, Ensembl gene ID, or a synonym. Symbols with duplicate entries resolve to the primary_gene=1 row. Example: SOX17
include enum_list Comma-separated optional lists, each counted in the default response as n_<name>. perturbation_effects is the effect of every gRNA in the screen on this gene (~400 rows); coexpression is the slowest (~2 s cold). One of: go_terms, perturbation_effects, elements, coexpression.
coexpression_limit integer · default 100 · max 500 Top co-expressed partners by |z|. Only used with include=coexpression.

When the input was not the canonical symbol, meta.resolved_from and meta.match_type report what was matched.

404 gene_not_found — No symbol, Ensembl ID or synonym matches.
https://huangfulab.com/endoderm-perturbseq/api/v1/gene/SOX17
GET https://huangfulab.com/endoderm-perturbseq/api/v1/tf/{tf}

One transcription factor: which modules it regulates, by perturbation (GSEA over the CRISPR screen) and by binding (ChIP/ATAC enrichment).

ParameterTypeDescription
tf required string Gene symbol of a TF. There is no separate TF table — a TF is a gene that is either perturbed in the screen or has binding datasets. Example: ARID1A
level enum · default hotspot_supermodule Which module collection module_regulation covers. The default (13 supermodules) keeps the response small; n_module_regulation gives the row count for every collection. 'all' returns ~300 rows. One of: hotspot_supermodule, hotspot_submodule, mfuzz_k7, all.
include enum_list Comma-separated optional blocks. 'binding_datasets' can be large (up to ~1,073 datasets for CTCF); 'pathway_enrichment' adds GO/KEGG GSEA. One of: binding_datasets, pathway_enrichment.

is_perturbed_tf and is_binding_tf are reported separately; they are different populations (75 perturbed, ~1,705 with binding data).

odds_ratio is null with odds_ratio_infinite=true where the underlying Fisher test returned an infinite estimate.

404 tf_not_found — The gene exists but is neither perturbed nor has binding data — the error points at /gene/{name}.
404 gene_not_found — No such gene at all.
https://huangfulab.com/endoderm-perturbseq/api/v1/tf/ARID1A
GET https://huangfulab.com/endoderm-perturbseq/api/v1/module/{module}

One co-expression module: title, description, size and hierarchy, plus counts of its member genes, enrichment terms and TF regulators. The lists themselves are opt-in via include=.

ParameterTypeDescription
module required string Supermodule (DE-1), submodule (DE-1.2), or gene cluster (GC1). GC1 / TC-1 / gene_cluster_1 / cluster_1 all resolve to the same module. Example: DE-1
source enum · default Module collection. Required when the name is ambiguous — 'unassigned' exists in both hotspot_supermodule and hotspot_submodule. One of: hotspot_supermodule, hotspot_submodule, mfuzz_k7.
include enum_list Comma-separated optional lists. 'genes' is paged by genes_offset and genes_limit; 'tf_regulators' merges perturbation and binding evidence (up to 500 rows); 'expression' is skipped above 2,500 genes. One of: genes, enrichment, tf_regulators, grna_gsea, expression.
genes_offset integer · default 0 Offset into the member-gene list. Used with include=genes.
genes_limit integer · default 100 · max 500 Member genes per response. Used with include=genes. The largest module has 4,691; /link/gene-module pages through membership too.

Gene clusters are reported with their public display name (GC1), never the internal cluster_1 form; the full alias set is in the 'aliases' field.

400 ambiguous_name — The name exists in more than one collection (e.g. 'unassigned'); pass ?source=.
404 module_not_found — No module with that name.
https://huangfulab.com/endoderm-perturbseq/api/v1/module/DE-1
GET https://huangfulab.com/endoderm-perturbseq/api/v1/submodule/{submodule}

One submodule. Identical in shape to /module, but pinned to the hotspot_submodule collection so the name can never be ambiguous.

ParameterTypeDescription
submodule required string Submodule name, e.g. DE-1.1. Example: DE-1.1
include enum_list Comma-separated optional lists, as for /module. One of: genes, enrichment, tf_regulators, grna_gsea, expression.
genes_offset integer · default 0 Offset into the member-gene list. Used with include=genes.
genes_limit integer · default 100 · max 500 Member genes per response. Used with include=genes.
404 module_not_found — No such submodule.
https://huangfulab.com/endoderm-perturbseq/api/v1/submodule/DE-1.1
GET https://huangfulab.com/endoderm-perturbseq/api/v1/go-term/{go_term}

One Gene Ontology term: its definition and the number of genes annotated to it. The gene list and enrichment results are opt-in via include=.

ParameterTypeDescription
go_term required string GO accession. 'GO:0030183', 'GO_0030183' and '0030183' are all accepted. Example: GO:0030183
include enum_list Optional lists: genes (paged by genes_offset/genes_limit), module_enrichment, tf_enrichment. One of: genes, module_enrichment, tf_enrichment.
genes_offset integer · default 0 Offset into the annotated-gene list. Used with include=genes.
genes_limit integer · default 100 · max 500 Annotated genes per response. Used with include=genes. The largest terms have ~12,800.

n_genes is the exact annotation count, whether or not include=genes is set.

404 go_term_not_found — No such GO accession. An unparseable accession is treated as missing, not as a client error.
https://huangfulab.com/endoderm-perturbseq/api/v1/go-term/GO:0030183
GET https://huangfulab.com/endoderm-perturbseq/api/v1/dataset/{namespace}/{dataset_id}

One source dataset — either a TF binding experiment or a histone-PTM experiment. The two are separate namespaces that happen to share the word.

ParameterTypeDescription
namespace required enum 'tf' for TF binding datasets, 'ptm' for histone PTM. Example: tf
dataset_id required integer Numeric dataset id within that namespace. Example: 1

Peak lists are not served. A TF dataset averages ~6,900 peaks over a 121M-row table; use the original accession in the 'dataset' field to fetch peaks from ENCODE or ReMap.

404 dataset_not_found — No dataset with that id.
https://huangfulab.com/endoderm-perturbseq/api/v1/dataset/tf/1
GET https://huangfulab.com/endoderm-perturbseq/api/v1/atac-peak/{peak}

One ATAC peak: coordinates, accessibility across the time course, linked genes, and overlapping histone PTM datasets.

ParameterTypeDescription
peak required string Numeric atac_peak_id or a peak name such as ESC_DE_peak_99081. Example: 58938

Only the count of TF overlaps is returned here — a peak can overlap 10,000+ TF peaks. Use /atac-peak/{peak}/tfs for the list.

404 peak_not_found — No peak with that id or name.
https://huangfulab.com/endoderm-perturbseq/api/v1/atac-peak/58938
GET https://huangfulab.com/endoderm-perturbseq/api/v1/atac-peak/{peak}/tfs

Transcription factors with a binding peak overlapping this ATAC peak, aggregated per TF.

ParameterTypeDescription
peak required string Numeric atac_peak_id or a peak name. Example: 58938
page integer · default 1 1-based page number.
per_page integer · default 25 · max 500 Rows per page. Values above 500 are rejected, not clamped.

total is null: counting distinct TFs on a busy peak is as expensive as the query itself, so it is deliberately not computed.

404 peak_not_found — No peak with that id or name.
https://huangfulab.com/endoderm-perturbseq/api/v1/atac-peak/58938/tfs
Links
GET https://huangfulab.com/endoderm-perturbseq/api/v1/link/tf-gene/{tf}/{gene}

Evidence that a TF regulates a gene: how many datasets place the TF at the gene and the strongest binding score, plus the perturbation effect of knocking the TF out. Per-dataset and per-peak detail are opt-in.

ParameterTypeDescription
tf required string TF gene symbol. Example: FOXA2
gene required string Target gene symbol. Example: SOX17
include enum_list 'datasets' lists each binding dataset with its scores (up to 500; CTCF has 1,000+). 'elements' adds peak-level evidence — the most expensive query in the API, skipped entirely when there is no dataset-level binding to explain. One of: datasets, elements.

binding_evidence is false when no dataset places this TF at this gene; the elements block is then empty regardless of include.

404 gene_not_found — Either name does not resolve.
https://huangfulab.com/endoderm-perturbseq/api/v1/link/tf-gene/FOXA2/SOX17
GET https://huangfulab.com/endoderm-perturbseq/api/v1/link/tf-module/{tf}/{module}

Evidence that a TF regulates a module, from both the perturbation screen and binding enrichment, with the per-gRNA detail behind it.

ParameterTypeDescription
tf required string TF gene symbol. Example: ARID1A
module required string Module name; the same aliases as /module are accepted. Example: DE-1
source enum · default Module collection. Required when the name is ambiguous — 'unassigned' exists in both hotspot_supermodule and hotspot_submodule. One of: hotspot_supermodule, hotspot_submodule, mfuzz_k7.
400 ambiguous_name — The module name needs ?source=.
404 module_not_found — No such module.
404 gene_not_found — No such TF.
https://huangfulab.com/endoderm-perturbseq/api/v1/link/tf-module/ARID1A/DE-1
GET https://huangfulab.com/endoderm-perturbseq/api/v1/link/gene-module

Gene-to-module membership edges. At least one filter is required.

ParameterTypeDescription
gene string Return this gene's memberships.
module string Return this module's members.
source enum · default Module collection. Required when the name is ambiguous — 'unassigned' exists in both hotspot_supermodule and hotspot_submodule. One of: hotspot_supermodule, hotspot_submodule, mfuzz_k7.
include_unassigned boolean · default False Include the 'unassigned' pseudo-modules, which hold 5,805 genes between them and are a non-result rather than a module.
page integer · default 1 1-based page number.
per_page integer · default 25 · max 500 Rows per page. Values above 500 are rejected, not clamped.
400 filter_required — None of gene, module or source was supplied — an unfiltered dump is 26K rows.
https://huangfulab.com/endoderm-perturbseq/api/v1/link/gene-module?module=DE-1
GET https://huangfulab.com/endoderm-perturbseq/api/v1/edges

The TF-to-module regulatory network as an edge list, merging perturbation and binding evidence.

ParameterTypeDescription
level enum · default hotspot_supermodule Which module collection to build the network over. One of: hotspot_supermodule, hotspot_submodule, mfuzz_k7.
evidence enum · default any Restrict to edges with this kind of support. One of: any, perturbation, binding, both.
min_odds_ratio number · default 1.0 Minimum binding enrichment odds ratio.
max_padj number · default 0.05 · max 1 Maximum adjusted p-value for the binding arm.
min_abs_nes number · default 0 Minimum |NES| for the perturbation arm.
page integer · default 1 1-based page number.
per_page integer · default 25 · max 500 Rows per page. Values above 500 are rejected, not clamped.

At the default supermodule level the whole network is small enough to page through in full. The submodule level is ~20x larger, so the significance defaults matter more.

https://huangfulab.com/endoderm-perturbseq/api/v1/edges
Collections & search
GET https://huangfulab.com/endoderm-perturbseq/api/v1/genes

All genes in the dataset, filterable by biotype, chromosome and perturbation-library membership.

ParameterTypeDescription
biotype string Exact gene_biotype, e.g. protein_coding or lncRNA.
chr string Chromosome. '7' and 'chr7' are both accepted.
perturbed boolean Restrict to genes that are (or are not) in the perturbation library.
page integer · default 1 1-based page number.
per_page integer · default 25 · max 500 Rows per page. Values above 500 are rejected, not clamped.

Only primary_gene=1 rows are listed, so each symbol appears once.

https://huangfulab.com/endoderm-perturbseq/api/v1/genes?per_page=5
GET https://huangfulab.com/endoderm-perturbseq/api/v1/tfs

Transcription factors. There is no TF table in the database, so which population you get is an explicit choice.

ParameterTypeDescription
set enum · default perturbed 'perturbed' = the 75 TFs targeted in the CRISPR screen; 'binding' = the ~1,705 genes with a binding dataset; 'all' = either. One of: perturbed, binding, all.
page integer · default 1 1-based page number.
per_page integer · default 25 · max 500 Rows per page. Values above 500 are rejected, not clamped.

A few perturbed TFs carry stale HGNC symbols with no gene_table row; they are returned with gene_id null and symbol_status 'stale' rather than dropped.

https://huangfulab.com/endoderm-perturbseq/api/v1/tfs
GET https://huangfulab.com/endoderm-perturbseq/api/v1/modules

All modules across the three collections, with sizes and titles. Full descriptions are on /module/{module}.

ParameterTypeDescription
source enum · default Module collection. Required when the name is ambiguous — 'unassigned' exists in both hotspot_supermodule and hotspot_submodule. One of: hotspot_supermodule, hotspot_submodule, mfuzz_k7.
include_unassigned boolean · default False Include the 'unassigned' pseudo-modules.
page integer · default 1 1-based page number.
per_page integer · default 25 · max 500 Rows per page. Values above 500 are rejected, not clamped.

Gene clusters carry both display_name (GC1) and the full alias list.

https://huangfulab.com/endoderm-perturbseq/api/v1/modules
GET https://huangfulab.com/endoderm-perturbseq/api/v1/search

Prefix search across genes, synonyms, TFs, modules and GO terms.

ParameterTypeDescription
q string Search string, 2-64 characters. Matched as a prefix for names and as a substring for GO term descriptions. Wildcards are escaped, not interpreted.
type enum_list Restrict to these result types. One of: gene, tf, module, submodule, gene_cluster, go_term, synonym.
limit integer · default 25 · max 100 Maximum results across all types.

Each result carries matched_on, so an exact symbol hit can be told apart from a synonym or a GO description match.

400 invalid_param — q is missing, shorter than 2 characters, or longer than 64.
https://huangfulab.com/endoderm-perturbseq/api/v1/search?q=SOX
Meta
GET https://huangfulab.com/endoderm-perturbseq/api/v1/

Machine-readable index of every endpoint, with limits and enum values.

ParameterTypeDescription
examples boolean · default True Set false for a compact listing without example response bodies.
https://huangfulab.com/endoderm-perturbseq/api/v1/
More

https://huangfulab.com/endoderm-perturbseq/api/v1/ returns this endpoint list as JSON, including every enum value and limit, so a script can discover the API without scraping this page. For column-level detail on the underlying tables, see the database schema.