import requests BASE = "https://aidd.rc.ufl.edu/app/smartflexdb" # hosted service # BASE = "http://localhost:8013" # local development PAIR = "1EI2_NMY_A_B_26_pocket" # an example pair ID (see /api/pairs)
Download the entire dataset as flat files, or pull it programmatically. The derived data are released under CC-BY-4.0; atomic coordinates are from the RCSB PDB.
| File / endpoint | Contents |
|---|---|
smartflexdb_pairs.tsv |
one row per served pocket (apo_role=default) plus one row per cross-PDB alternative apo (apo_role=alternative). 31 columns: id, holo_pdb, holo_chain, lig_auth_chain, nearest_rna_chain, ligand, lig_resseq, apo_pdb, apo_chain, apo_role, rna_type, method, resolution, identity, global_identity, holo_len, apo_len, num_chains, has_protein, has_dna, rmsd, host_chain_rmsd, tm_score, pocket_rmsd, maxd, pocket_size, hbonds, water, mw, logp, rg_delta. Chain columns. holo_chain is the pocket's RNA chain (identical to holo_rna_chain — the chain every panel, the superposition and the SEQRES block use, and the same concept apo_chain names on the apo side); lig_auth_chain is the auth chain the ligand itself sits on. nearest_rna_chain is a diagnostic column: the RNA chain geometrically nearest the ligand (the pre-repoint holo_rna_chain semantics, kept when the host chain was repointed to lig_auth_chain); it equals the published host chain on 223 of the 266 served pockets and differs on the other 43. Both identity columns are the ligand's auth chain's: identity and global_identity are measured on lig_auth_chain, not on holo_chain (the rule since 2026-07-28; before that the published value was the minimum over the pocket's chains). The two chains differ for 43 of the 266 served pockets — 58 of the 497 rows once the alternative rows are counted — so a per-chain join of the identity columns on (holo_pdb, holo_chain) files those 58 rows under the wrong chain, and on 2 of them it also records the wrong number: 4F8U_SIS_A_C_101_pocket and 4F8U_SIS_B_D_101_pocket, whose two RNA chains are 100% and 95.5% identical to the apo. Join the identity columns on (holo_pdb, lig_auth_chain). Per-chain identities are not in the TSV; GET /api/pair/{id} carries them as seq_identity_chains / global_identity_chains. Correction (2026-07-25): earlier downloads published the ligand chain under the holo_chain name — that column was mislabeled, not re-defined; the ligand chain now has its own lig_auth_chain column, so no information was dropped. Re-download if you joined on (holo_pdb, holo_chain). RMSD columns (redefined 2026-07-31). The superposition is now pinned to the ligand's deposited auth chain (lig_auth_chain, the "host chain"), not the geometric ligand-nearest strand — a geometric tie between duplex/homodimer strands had been setting the reported shift. host_chain_rmsd (new column) is the C1′ RMSD of that host chain alone; rmsd is redefined to the C1′ RMSD over all pocket chains that have a distinct injective apo counterpart, every chain in the one global superposition frame (it reduces to host_chain_rmsd on single-chain pockets and on the 11 apo-copy-reuse records). pocket_rmsd stays the ≤ 8 Å binding-site value, now measured in that same global frame. Alternative rows carry the alternative's own rmsd/host_chain_rmsd/tm_score/pocket_rmsd/maxd/rg_delta/apo_chain/apo_len and its own two identities (identity = local/BLAST pident, global_identity = full-length Needleman–Wunsch); either is blank only where the upstream run never scored that apo. |
GET /api/pairs |
Not the same set as the TSV. Returns the 266 default pocket-level records only,
with the full per-pair fields. The 231 alternative apo comparisons that the TSV lists as their own
rows are not separate records here — each pocket carries its alternatives in its
apo_alts field, and a full record for one is fetched with
GET /api/pair/{id}?apo={pdb}. So 266 records here == 497 rows in the TSV. |
| cluster assignments (CSV) | sequence / structure / pocket cluster IDs — for redundancy-controlled train/test splits |
GET /api/bundle/{id} |
per-pair structure bundle (holo + apo + superposed apo + ligand) |
GET /api/structure/{pdb}, /api/sup/{name} |
individual mmCIF / superposed-PDB files |
GET /api/pairs
→ {"pairs": [ … ]}List every apo–holo pair (filtered to the public dataset) with summary fields: PDB IDs, ligand, method, resolution, RNA type, global / host-chain / pocket RMSD, whole-structure geometry, H-bond / water-bridge counts, ligand MW/logP, thumbnail. This is the table to start from. See the columns reference below.
pairs = requests.get(f"{BASE}/api/pairs").json()["pairs"]
print(len(pairs)) # number of public pairs
print(pairs[0]["id"], pairs[0]["pocket_rmsd"], pairs[0]["tm_score"])
GET /api/pair/{id}
→ full pair objectFull record for one pair: 2D (sequence + dot-bracket + per-position seqids/coords),
per-residue displacement, atom-level interactions, water bridges,
ligand properties, the pocket_dyn / geom sub-objects, and metadata.
It also carries DSSR base pairs for both holo and apo
(dssr_pairs / apo_dssr_pairs) and DSSR structural motifs for the
holo state (dssr_motifs; no apo equivalent).
Returns 404 for an unknown id.
Path · id - a pair ID from /api/pairs.
Query · apo=<pdb> - return the pocket compared against one of its
curated cross-PDB apo states (the keys of apo_alternatives; the pair page's
"Apo state" dropdown). copy=<chain> - compare against another same-entity
apo copy in the same apo crystal (the keys of apo_copies; the "Apo copy"
chooser). Either one returns the record for that combination. The apo-side fields
(apo_pdb/apo_chain, rmsd, host_chain_rmsd,
displacement,
apo_2d, aux_chains, geom, pocket_dyn,
bp_rewire, seq_identity, global_identity, the superposed
/ morph filenames) are those of the selected apo. The JSON therefore matches the
pair.html?id=…&apo=… / ©=… page exactly. A value the record
does not carry is a 400, never a silent fall back to the default apo.
Not carried per apo state / copy and therefore null on those responses:
qc, apo_dssr_pairs, apo_clean and
apo_pocket_ligand - they are measured against the pocket's default apo only. Every
holo-side field (dssr_pairs, dssr_motifs, interactions3d,
lig_props, method/resolution/RNA type) is unchanged by design.
pair = requests.get(f"{BASE}/api/pair/{PAIR}").json()
print(pair["holo_2d"]["seq"]) # holo sequence
print(pair["pocket_dyn"]["pocket_rmsd"])# pocket C1′ RMSD
print(len(pair["interactions3d"])) # atom-level contacts
# the same pocket against one of its alternative apo states (400 if this pair has no such state)
alt = requests.get(f"{BASE}/api/pair/{PAIR}", params={"apo": "7R6M"}).json()
print(alt["apo_pdb"], alt["rmsd"], alt["apo_dssr_pairs"]) # 7R6M … None (default-apo-only field)
Interaction fields & provenance. The served type
values in interactions3d (and in the bundled contacts.csv) are
stable and unchanged for backward compatibility — notably hbond and
vdw. These are not a single unified RNA-ligand interaction standard; they come
from different sources and are surfaced in the UI under clearer labels:
Served type | UI label | Provenance |
|---|---|---|
hbond (with dssr_hbonds) | Hydrogen bond | DSSR-derived (x3dna-dssr --get-hbond, atom-level, donor/acceptor quality) |
hbond (fallback, no dssr_hbonds) | Polar contact | custom geometry: N/O atoms ≤3.5 Å, no angle (25 of 266 served pairs) |
vdw | RNA–ligand proximity | custom geometry: closest heavy-atom distance per nucleotide within a user-defined cutoff (up to 15 Å, default 8 Å); a proximity measure, not a formal vdW/contact assignment |
ionic | Putative phosphate electrostatic contact | custom rule approximating pH 7.0: pH-7 cationic ligand atom ≤4.0 Å of an RNA non-bridging phosphate O (replaces PLIP salt_bridge) |
pi_cation | Charge-filtered π-cation interaction | PLIP (--dnareceptor) + custom pH-7 filter; unfiltered hits kept in plip_raw |
| water bridges | Putative water-mediated contact | custom geometry: ligand-water and water-RNA both ≤3.5 Å, N/O only, no angle (not PLIP's water_bridge) |
pi_stack | π-stacking | PLIP-derived (--dnareceptor, default cutoffs) |
halogen | Halogen bond | PLIP-derived (--dnareceptor, default cutoffs) |
metal | Metal coordination | PLIP-derived (--dnareceptor, default cutoffs) |
DSSR base pairs / motifs / base-pair rewiring
(dssr_pairs, apo_dssr_pairs, dssr_motifs) are
DSSR-derived (RNA-internal) and keep their own labels.
GET /api/pockets
→ {"pockets": [ … ]}Groups of matched RNA pockets bound to multiple ligands — holo pockets that share an RNA binding site and accommodate ≥2 distinct ligands. Light summary per group (size, RNA type, ligand list, members);
per-ligand 2D / properties come from /api/pocket/{gid}.
groups = requests.get(f"{BASE}/api/pockets").json()["pockets"]
print(groups[0]["gid"], groups[0]["n_ligands"], groups[0]["ligands"])
GET /api/pocket/{gid}
→ pocket group objectOne pocket group expanded: each distinct ligand's 2D depiction (SVG), SMILES, formula and
RDKit properties, plus the shared pocket residues. Returns {"error": "not found"}
for an unknown gid.
Path · gid - integer group ID from /api/pockets.
gid = groups[0]["gid"]
grp = requests.get(f"{BASE}/api/pocket/{gid}").json()
for lig in grp["ligands"]:
print(lig["ligand"], lig["smiles"], lig["mw"])
GET /api/entry/{pdb}
→ RCSB metadataRCSB Data API metadata for a PDB entry (title, experimental method, resolution, release/revision dates, authors, molecular weight, entity counts). Proxied and cached.
Path · pdb - a 3–5 char PDB ID.
meta = requests.get(f"{BASE}/api/entry/1EI2").json()
print(meta["title"], meta["method"], meta["resolution"])
GET /api/classification
→ cluster + t-SNE JSONRNA-only non-redundancy clustering plus 2D t-SNE coordinates for the served pairs
(n_pairs, method, axes: [sequence, structure, pocket],
and pairs: [{id, cluster ids, x, y}]). Single-linkage components
(RMscore ≥0.75 / MMseqs2 0.7) define non-redundant groups for ML train/test splits.
cls = requests.get(f"{BASE}/api/classification").json()
print(cls["n_pairs"], cls["method"], cls["axes"])
print(cls["pairs"][0]) # {id, cluster IDs, x/y t-SNE coords}
GET /api/structure/{pdb}
→ mmCIFmmCIF for a PDB ID (proxied and cached from RCSB). media_type
chemical/x-cif.
cif = requests.get(f"{BASE}/api/structure/1EI2").text
open("1EI2.cif", "w").write(cif)
GET /api/structure_pdb/{pdb}
→ PDBPDB format, model 1 only — auth numbering + atom names match the contacts in
interactions3d. Used by the 3D interaction viewer.
pdb_text = requests.get(f"{BASE}/api/structure_pdb/1EI2").text
GET /api/sup/{name}
→ PDB / mmCIFSuperposed apo structures and morph trajectories. The transform is the lowest-RMSD of a
US-align fit and a sequence-anchored Kabsch fit on the paired C1′ atoms. Kabsch is the
analytic minimiser of that RMSD, so it is the transform published for every served record.
The exact filename is on the
pair object (e.g. pair["apo_sup"]); must end in .pdb or
.cif.
name = pair["apo_sup"] # e.g. "1EI2_..._apo_sup.pdb"
sup = requests.get(f"{BASE}/api/sup/{name}").content
GET /api/thumb/{id}
→ PNGPre-rendered 3D cartoon thumbnail (holo + superposed apo, same transform as
/api/sup) for the gallery cards.
404 if not yet rendered.
png = requests.get(f"{BASE}/api/thumb/{PAIR}").content
open(f"{PAIR}.png", "wb").write(png)
GET /api/bundle/{id}
→ application/zipDownload one pair as a ZIP: holo/apo CIFs, the superposed apo (pocket-trimmed, exactly the overlay the viewer draws), ligand SDF, contacts CSV, and the full pair JSON.
Query · apo=<pdb> / copy=<chain> -
the same apo-state / apo-copy selection /api/pair/{id} accepts (same
400 on an unknown value). The ZIP then contains that combination's superposed apo
and pair record, and its folder/filename carries a __alt_<pdb> /
__copy_<chain> suffix so two downloads of one pocket are distinguishable.
z = requests.get(f"{BASE}/api/bundle/{PAIR}").content
open(f"{PAIR}.zip", "wb").write(z)
z = requests.get(f"{BASE}/api/bundle/{PAIR}", params={"apo": "7R6M"}).content # → {PAIR}__alt_7R6M.zip
POST /api/query
→ {"job_id": …}Multipart upload. apo and holo are the two mmCIF files;
ligand is a HET code (e.g. CNY) or, for a multi-copy ligand, a
pinned instance LIG:CHAIN:RESSEQ (e.g. CNY:C:41). On success returns
{job_id, ligand, lig_auth_chain, lig_resseq, pocket_pos}; a validation failure
returns 400 with the message in detail.
Form · apo, holo - mmCIF files (≤30 MB
each) · ligand - HET code or LIG:CHAIN:RESSEQ.
files = {"apo": open("apo.cif", "rb"), "holo": open("holo.cif", "rb")}
r = requests.post(f"{BASE}/api/query", files=files, data={"ligand": "CNY:C:41"})
r.raise_for_status()
job_id = r.json()["job_id"]
GET /api/query/{job_id}/status
→ {"state": …}Job status — poll until state is done (or error,
with a message). While running it reports the current pipeline
stage.
Path · job_id - from the POST /api/query reply.
import time
while True:
s = requests.get(f"{BASE}/api/query/{job_id}/status").json()
if s["state"] in ("done", "error"): break
time.sleep(2)
GET /api/query/{job_id}/pair
→ full pair objectThe finished pair — the same response schema as /api/pair/{id}. Returns
404 ("result not ready") until the job is done.
Path · job_id - from the POST /api/query reply.
pair = requests.get(f"{BASE}/api/query/{job_id}/pair").json()
print(pair["pocket_dyn"]["pocket_rmsd"])
import requests
import pandas as pd
BASE = "https://aidd.rc.ufl.edu/app/smartflexdb" # hosted service (or http://localhost:8013 locally)
pairs = requests.get(f"{BASE}/api/pairs", timeout=60).json()["pairs"]
df = pd.DataFrame(pairs)
# geometry columns for modelling
ml = df[["id", "holo_pdb", "apo_pdb", "ligand",
"tm_score", "rg_delta", "ligand_burial",
"rmsd", "pocket_rmsd"]]
print(df.shape)
print(df["pocket_rmsd"].describe())
/api/pairs columns| column | meaning |
|---|---|
tm_score | apo–holo whole-structure TM-score (US-align) |
rg_apo / rg_holo / rg_delta |
radius of gyration (Å) of each state and the holo − apo difference |
ligand_burial | fraction of the ligand surface buried by the RNA |
lig_bsa | ligand buried surface area (Ų) |
rmsd | C1′ RMSD (Å) over all pocket chains with a distinct injective apo counterpart, one global superposition frame |
host_chain_rmsd | C1′ RMSD (Å) of the ligand host chain (lig_auth_chain) alone, same global frame |
pocket_rmsd | C1′ RMSD (Å) over the ≤ 8 Å binding-site residues (all pocket chains) |
mw / logp | ligand molecular weight & logP (RDKit) |
A runnable Jupyter notebook that fetches
/api/pairs, builds the DataFrame, summarizes the ML columns, and plots the
pocket_rmsd distribution. Set BASE in the first cell, then run all.