IKraph API Documentations#
Base URL: https://service.insilicom.com
Authentication#
- Header:
X-API-KEY— Your API key provided by the service - All requests must include this header. The proxy service handles signature generation and JWT authentication internally.
Endpoints#
| Endpoint | Purpose |
|---|---|
POST /open_api/iexplore/v3/search_direct_relations | Submit direct relation search (returns task_id) |
POST /open_api/iexplore/v3/search_direct_relations_details | Retrieve saved results (evidence counts only) |
POST /open_api/iexplore/v3/search_direct_relations_evidences | Retrieve evidence PMIDs for a specific direct relation (synchronous, paginated) |
POST /open_api/iexplore/v3/search_indirect_relations | Submit indirect relation search (returns task_id) |
POST /open_api/iexplore/v3/search_indirect_relations_details | Retrieve saved indirect results (evidence counts only) |
POST /open_api/iexplore/v3/search_indirect_relations_evidences | Retrieve evidence PMIDs for a specific indirect relation (synchronous, paginated) |
POST /open_api/iexplore/v3/search_path | Submit shortest / k-shortest path search between two entities (returns task_id) |
POST /open_api/iexplore/v3/search_specific_path | Submit an explicit user-specified hop-sequence path search (returns task_id) |
POST /open_api/iexplore/v3/task_status | Poll the status of an async search task |
POST /open_api/iexplore/v3/get_biokde_id | Resolve entity name to BioKDE ID (or synonym candidate list) |
GET /open_api/iexplore/v3/readme | Agent-readable API documentation (raw markdown) |
GET /open_api/iexplore/v3/entity-type | List all valid entity type names |
POST /open_api/iexplore/v3/entity-type-detail | Definitions of the requested entity types |
GET /open_api/iexplore/v3/relation-type | List all valid relation type names |
POST /open_api/iexplore/v3/relation-type-detail | Definitions of the requested relation types |
Note:
search_path,search_specific_path, and the path results are stored in the indirect result store — fetch them withsearch_indirect_relations_detailsandsearch_indirect_relations_evidencesusing the returnedresult_id.
Async Flows#
v3 search endpoints use an async task queue. Evidence endpoints are synchronous.
Search Flow#
Every search request is enqueued and returns a task_id. Cached results are resolved immediately by the worker (fast path) but use the same response shape and polling flow as new searches — callers do not need to branch on cache state.
┌─────────────────────┐
│ search_direct_ │
│ relations (POST) │
└──────┬──────────────┘
│
│ 202
{task_id}
│
▼
┌──────────────┐
│ task_status │◄── poll every 3s
└──┬────────┬──┘
│ │
pending completed
│
{result_id}
│
▼
┌──────────────────────┐
│ _details / │
│ _evidences │
└──────────────────────┘
Search → HTTP 202 with task_id → poll task_status → get result_id when completed → get details/evidences. Previously-cached searches complete on the first poll; new searches complete once the background worker finishes.
Credit System#
Every call is charged a flat base price. Details and evidence endpoints additionally charge a per-result fee on top of the base. The response includes:
| Field | Type | Description |
|---|---|---|
credits_consumed | int | Total credits charged for this call (base + per-unit, when applicable) |
remaining_credits | int | Credits remaining in your account after this call |
Pricing rules:
| Endpoint | Charge |
|---|---|
search_direct_relations | base |
search_indirect_relations | base |
get_biokde_id | base |
search_direct_relations_details | base + (returned_count × per-relation) |
search_indirect_relations_details | base + (total relation edges across returned paths × per-relation) |
search_direct_relations_evidences | base + (number of evidences in the returned page × per-evidence) |
search_indirect_relations_evidences | base + (number of evidences in the returned page × per-evidence) |
For indirect details, "total relation edges" is summed across all returned paths — a 2-hop path counts as 2 relations, a 3-hop path as 3, and so on. Lowering requested_paths_count (or requested_relations_count on direct details) reduces both the data returned and the credit cost.
Current prices: 10 credits per base call, 1 credit per relation, 10 credits per evidence. Prices may change without notice — always read the actual
credits_consumedfrom the response rather than computing it client-side.
Cache hits return
credits_consumed: 0— the gateway's 5-minute per-user response cache short-circuits billing for identical requests. This applies to all seven endpoints listed above. See the Response Cache section below.
Failed calls are not billed. If request validation, permission check, or balance check fails, the base charge never runs. If the IKraph backend itself returns a non-2xx response (4xx or 5xx), the base pre-authorization is cancelled and your balance is restored. The only edge case where the base is charged but no data is returned: a details/evidence call that succeeds at the base step but is denied at the per-unit step due to insufficient credits — in which case the response is HTTP 403 with
credits_consumedreflecting only the base portion already paid.
Response Cache#
All v3 search endpoints listed in this document are fronted by a 5-minute per-user response cache at the API gateway. When the same user re-issues a request with identical parameters within 5 minutes:
- The previous response body is returned verbatim (with the current request's
session_idsubstituted in). - No credits are consumed on the cache hit —
credits_consumedis0and no billing record is created. - The original HTTP status is preserved. A queued search that returned HTTP 202 with a
task_idthe first time returns 202 with the sametask_idon a cache hit; a details/evidences endpoint returning HTTP 200 is re-served as 200.
Cache keying:
- The cache key is derived from the authenticated
user_idcombined with a stable hash of the full validated request body. Each user has an independent cache, and any change to any request parameter (includingpage,page_size, or whitespace inside a name) produces a different cache entry. - This is a separate layer from the search-result cache inside the BioKDE backend: that one is user-agnostic, keyed on search parameters only, and has a much longer retention window. The two layers compose transparently.
Endpoints covered:
| Endpoint | Notes on cache hit |
|---|---|
search_direct_relations | Same task_id is returned; you can still poll task_status for the live task state. |
search_indirect_relations | Same task_id is returned; live polling via task_status continues to reflect the latest state. |
search_direct_relations_details | Cached details body with credits_consumed: 0. |
search_indirect_relations_details | Cached details body with credits_consumed: 0. |
search_direct_relations_evidences | Paginated — each (result_id, relID, page, page_size) combination is cached independently. |
search_indirect_relations_evidences | Paginated — each (result_id, relID, page, page_size) combination is cached independently. |
Endpoints not cached: task_status (always reflects live task state) and get_biokde_id.
The cache is TTL-only — there is no manual invalidation. To force a fresh response before the 5-minute TTL expires, change any request parameter.
Entity Types#
Valid values for entity_*_type on direct search (search_direct_relations) and entity resolution (get_biokde_id):
Gene, Chemical, Disease, Species, Anatomy, Biological Process, CellLine, Cellular Component, DNAMutation, Molecular Function, Pathway, Pharmacologic Class
Indirect search (search_indirect_relations) accepts only a subset: Chemical, Disease, Gene. Passing any other entity type (e.g., Pathway, Anatomy) to an indirect search returns HTTP 400 with a serializer error listing the accepted values.
Searching for drug entities (is_drug)#
Drug is not a valid entity type in v3 — it was removed because some drug entities exist under both the Chemical and Gene labels and would resolve to two biokdeids, which is not desired.
To search for a drug entity in v3, set entity_*_type to the underlying parent label and add the corresponding is_drug flag:
| Endpoint | Drug-scoping flag(s) | Allowed parent type(s) |
|---|---|---|
get_biokde_id | is_drug (default "0") | Chemical or Gene |
search_direct_relations | is_drug_one / is_drug_two (default "0") | Chemical or Gene |
search_indirect_relations | is_drug_one / is_drug_two (default "0") | Chemical or Gene |
When is_drug="1", the lookup is scoped to nodes with subtype="Drug" under the chosen parent label. Setting is_drug="1" with any other entity_*_type (e.g. Disease) returns HTTP 400.
Example — small-molecule drug stored under Chemical:
{
"entity_type": "Chemical",
"entity_name": "Aspirin",
"is_drug": "1"
}
Example — biologic/protein drug stored under Gene:
{
"entity_type": "Gene",
"entity_name": "Adalimumab",
"is_drug": "1"
}
Example — direct-search query asking "what diseases is the drug Aspirin related to?":
{
"entity_one_type": "Chemical",
"entity_one_name": "Aspirin",
"is_drug_one": "1",
"entity_two_type": "Disease",
"relation_type": ["-1"]
}
The node1Type / node2Type field in relation responses will be the parent label (Chemical or Gene), never Drug.
Curated vs Text-Mined Relations#
Relations in IKraph come from three distinct sources, and they behave differently under date and novelty filters. Knowing which is which is important — depending on the entity-type pair you query, the response can be all text-mined, all non-text-mined (curated and/or RNASeq), or a mix, and certain filter combinations will exclude the non-text-mined edges entirely.
| Aspect | Text-mined (method="MP") | Curated (method="MA") | RNASeq (method="RNASeq") |
|---|---|---|---|
| Source | NLP extraction from PubMed sentences | Imported curated knowledge graphs (Hetionet, primeKG, PubChem, GO_annotation, TTD) | Computed from RNASeq expression data |
source field | null | The curated source name (e.g., "Hetionet", "primeKG") | null |
| PMID-anchored evidence | Yes — fetchable via the *_evidences endpoints | No — evidence_count = 0 and evidences = [] | No — evidence_count = 0 and evidences = [] |
| Per-mention dates | Yes — used for date filtering and novelty | No — relation has no publication date | No — relation has no publication date |
| Per-mention probability | Per sentence | Source-level (often a fixed value such as 0.9 / 1.0) | Source-level |
Because curated and RNASeq edges share the same shape (no PMID-anchored locations, no per-mention dates), they are handled identically by the filter pipeline. The remainder of this section refers to both as non-text-mined edges.
Filter behavior — date and novelty drop non-text-mined edges#
Non-text-mined edges have no PMID-anchored locations and no per-mention publication date, so date and novelty filters are meaningful only for text-mined edges. Whenever the user activates one of these filters, the search drops non-text-mined edges at query time:
| Filter setting | Non-text-mined edges (method="MA" or "RNASeq") | Text-mined edges (method="MP") |
|---|---|---|
start_date == "" and end_date == "" and novelty == "-1" | Included (with evidence_count: 0; source populated for MA only) | Included |
start_date or end_date set to any non-empty value (e.g. "1900-01-01" / "2027-01-01") | Excluded | Filtered to that date range |
novelty set to "1" or "0" | Excluded | Filtered to that novelty value |
| Both date and novelty set | Excluded | Filtered by both |
Note that even a deliberately wide-open date range (e.g. "1900-01-01" to "2100-12-31") is treated as an explicit filter and will exclude non-text-mined edges. If you want them included, send start_date and end_date as empty strings (or omit them) and leave novelty at "-1".
This applies symmetrically to direct search (entire relation is dropped) and indirect search (entire path is dropped if any hop is non-text-mined).
Which entity-type pairs return only non-text-mined relations?#
Several entity-type pairs in the underlying graph have no text-mined edges at all — only curated and/or RNASeq. For these pairs, any query that activates a date/novelty filter will return zero results regardless of the entities involved:
Gene↔PathwayGene↔AnatomyGene↔Biological ProcessGene↔Cellular ComponentGene↔Molecular FunctionPathway↔Pathway,Chemical↔PathwayPharmacologic Class↔ anything- Most other GO-derived / MeSH-derived combinations
Pairs with both kinds (e.g. Gene↔Disease, Chemical↔Disease, Gene↔Gene, Chemical↔Chemical) will return a mix when no filter is active and only the text-mined subset when a filter is active.
Direct Relations Search#
Search for direct (1-hop) relationships between biological entities using Neo4j graph traversal. Supports disease sub-tree expansion and date/novelty filtering. Evidence is pre-validated using an LLM-based pipeline; entries that failed validation are excluded automatically.
1. POST /open_api/iexplore/v3/search_direct_relations#
Submits a direct relationship search. Always queues a background task and returns a task_id — regardless of whether the search has been run before. If enforce_refetch is "0" and a cached result exists, the worker resolves the task immediately from history on the first poll; otherwise it runs the full search. The response shape is the same in both cases.
Identical submissions from the same user within the last 5 minutes are served from the gateway response cache and return the same
task_idwithcredits_consumed: 0. See Response Cache.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_one_type | string | Yes | — | Entity type for the first entity |
entity_one_name | string | Cond. | — | Name of the first entity. Required unless entity_one_id is given. |
entity_one_id | string | No | "" | Pre-resolved BioKDE ID for entity one (e.g. from get_biokde_id). When set, name resolution is skipped. |
entity_one_species | string | No | "Homo Sapiens" | Species for entity one |
entity_two_type | string | Yes | — | Entity type for the second entity |
entity_two_name | string | No | "" | Name of the second entity. Leave empty for one-entity search (returns all relations for entity one filtered by entity_two_type). |
entity_two_id | string | No | "" | Pre-resolved BioKDE ID for entity two. When set, name resolution is skipped. |
entity_two_species | string | No | "Homo Sapiens" | Species for entity two |
relation_type | array[string] | Yes | — | List of relationship type filters. Accepts type names (e.g., ["Positive_Correlation", "Negative_Correlation"]) or numeric codes (e.g., ["2", "3"]). Use ["-1"] for no filter. Invalid values return HTTP 400 with a list of valid options. See Relation Type Reference below. |
relation_direction | string | No | "-1" | Direction filter: "12" (entity one → two), "21" (entity two → one), "0" (no direction), "NA" (not applicable), "-1" (no filter) |
correlation_type | array[string] | No | ["-1"] | Correlation type filter: "2" (Positive), "0" (Negative), "1" (Unknown). Use ["-1"] for no filter. |
novelty | string | No | "-1" | Novelty filter: "-1" (all), "1" (novel only), "0" (non-novel only). Setting any value other than "-1" also drops curated relations from the result — see Curated vs Text-Mined Relations. |
start_date | string | No | "" | Start of date range (YYYY-MM-DD). Filters text-mined evidence by publication date. Setting a non-empty value here or in end_date also drops curated relations from the result — see Curated vs Text-Mined Relations. |
end_date | string | No | "" | End of date range (YYYY-MM-DD). Same caveat as start_date. |
exclude_sub_type | string | No | "1" | "1" = single entity search. "0" = include disease sub-tree (expands Disease entities into MeSH sub-types and returns grouped results). |
prob_method | string | No | "ALL" | Method filter: "MP" (model predicted, text-mined), "MA" (manually annotated, curated), "RNASeq" (calculated from RNASeq data), "ALL" (no filter). |
enforce_refetch | string | No | "0" | "1" = bypass cached results and re-run search |
probability_threshold | float | No | 0.9 | Minimum probability for returned relations (0.0–1.0). Only relations with prob >= threshold are included. |
Example Request#
{
"entity_one_type": "Disease",
"entity_one_name": "Lung Cancer",
"entity_two_type": "Chemical",
"entity_two_name": "",
"relation_type": ["Negative_Correlation"],
"relation_direction": "-1",
"correlation_type": ["-1"],
"novelty": "-1",
"start_date": "",
"end_date": "",
"exclude_sub_type": "0",
"prob_method": "ALL",
"probability_threshold": 0.9
}
Response (HTTP 202)#
Every request — whether cached or new — returns a queued response with a task_id:
{
"status": "success",
"session_id": "1776742838149_92397606-4e17-42be-8dfd-510b2a0c9d91_4288135032",
"message": "Your search request has been queued. Please poll the task_status endpoint with your task_id.",
"credits_consumed": 10,
"remaining_credits": 2870,
"task_id": "2748b9d8-2bb6-4f93-aedf-cbb06c90a879"
}
Next step: Poll
task_statuswith thetask_iduntil the status is"completed", then use the returnedresult_idwith the details endpoints. Cached searches typically complete on the first poll.
2. POST /open_api/iexplore/v3/search_direct_relations_details#
Retrieves saved search results with evidence counts only (no full evidence data). Use this for lightweight result browsing.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
result_id | string | Yes | — | The result_id returned by search_direct_relations or task_status |
requested_relations_count | int | No | 0 | Maximum number of relations to return, ordered by relation probability descending. 0 means return all. The per-relation portion of the bill is returned_count × per-relation price, so lowering this value lowers the credit cost. |
Example Request#
{
"result_id": "68ed2cc3869f71a6f40ebabc",
"requested_relations_count": 20
}
Response — Single Entity (exclude_sub_type: "1")#
{
"relations_count": 5,
"returned_count": 5,
"credits_consumed": 15,
"remaining_credits": 2855,
"relations": [
[
{
"relID": "647681.4744.3.0.12.MP",
"probability": "1.0",
"method": "MP",
"source": null,
"relation_details": {
"node1Id": "4744",
"node1Name": "Lung Cancer",
"node1Type": "Disease",
"node1Subtype": "NA",
"node2Id": "647681",
"node2Name": "Docetaxel",
"node2Type": "Chemical",
"node2Subtype": "Drug",
"score": 43.697,
"relationship_type": "Negative_Correlation",
"correlation_type": "0",
"direction": "21"
},
"evidence_count": 5
},
{
"relID": "433451.10452560.10.1.0.Hetionet",
"probability": "0.9",
"method": "MA",
"source": "Hetionet",
"relation_details": {
"node1Id": "433451",
"node1Name": "MAPK1",
"node1Type": "Gene",
"node1Subtype": "NA",
"node2Id": "10452560",
"node2Name": "Mitotic M-M/G1 Phases",
"node2Type": "Pathway",
"node2Subtype": "NA",
"score": 1.0,
"relationship_type": "participates",
"correlation_type": "1",
"direction": "NA"
},
"evidence_count": 0
}
]
]
}
Note: The first relation above is text-mined (
method: "MP",evidence_count > 0); the second is curated (method: "MA",source: "Hetionet",evidence_count: 0). Curated relations have no PubMed sentence evidence to fetch — calling the evidences endpoint for one returnsevidences: []with themethodandsourcefields populated.
Response — Sub-type (exclude_sub_type: "0")#
Results are grouped by disease sub-type:
{
"relations_count": 42,
"returned_count": 42,
"credits_consumed": 52,
"remaining_credits": 2818,
"relations": [
{
"disease_name": "SCLC",
"relations_count": 7,
"relations": [
{
"relID": "647681.4744.3.0.12.MP",
"probability": "1.0",
"relation_details": {
"node1Id": "4744",
"node1Name": "SCLC",
"node1Type": "Disease",
"node1Subtype": "NA",
"node2Id": "647681",
"node2Name": "Docetaxel",
"node2Type": "Chemical",
"node2Subtype": "Drug",
"score": 43.697,
"relationship_type": "Negative_Correlation",
"correlation_type": "0",
"direction": "21"
},
"evidence_count": 5
}
]
},
{
"disease_name": "NSCLC",
"relations_count": 12,
"relations": [...]
}
]
}
Note: The
evidencesarray is stripped from this endpoint. Onlyevidence_countis included. Usesearch_direct_relations_evidenceswith therelIDto retrieve evidence PMIDs.
3. POST /open_api/iexplore/v3/search_direct_relations_evidences#
Retrieve evidence PMIDs for a single relation in a saved direct search result. This is a synchronous, paginated endpoint — PMIDs are fetched from Neo4j and returned immediately.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
result_id | string | Yes | — | The result_id from search_direct_relations or task_status |
relID | string | Yes | — | The relID of the relation (from the details endpoint response) |
page | int | No | 1 | Page number (1-indexed) |
page_size | int | No | 20 | Number of PMIDs per page (1–500) |
sort | string | No | "probability" | Evidence ordering: probability (default — highest probability first) or date (most recent publication first) |
novelty | string | No | "" (inherit) | Override the search-time novelty filter: "1" (novel), "0" (non-novel), "-1" (all). Blank inherits the saved value. |
start_date | string | No | "" (inherit) | Override the evidence date-range start (YYYY-MM-DD). |
end_date | string | No | "" (inherit) | Override the evidence date-range end (YYYY-MM-DD). |
The same sort / novelty / start_date / end_date overrides apply to
search_indirect_relations_evidences. When omitted, the saved search-time
filters are used, so the result set is unchanged.
Example Request#
{
"result_id": "69b3200a04bac13dfd4f45a5",
"relID": "647681.4744.3.0.12.MP",
"page": 1,
"page_size": 20
}
Response (HTTP 200) — Text-mined relation#
{
"status": "success",
"session_id": "...",
"result_id": "69b3200a04bac13dfd4f45a5",
"relID": "647681.4744.3.0.12.MP",
"method": "MP",
"source": null,
"total_count": 61,
"page": 1,
"page_size": 20,
"total_pages": 4,
"credits_consumed": 40,
"remaining_credits": 2778,
"evidences": [
{"pmid": "15160349", "probability": 0.98, "pub_date": "2004-05-01"},
{"pmid": "18294837", "probability": 0.95, "pub_date": "2008-02-15"},
{"pmid": "20481923", "probability": 0.91, "pub_date": "2010-06-22"}
]
}
Response (HTTP 200) — Curated relation#
For relations with method == "MA", the endpoint short-circuits with an empty evidences list and returns the curated source so the client can render an appropriate message (e.g. "Curated relation — source: Hetionet, no PubMed evidence available") instead of an empty page. Only the base price is charged (no per-evidence charge when no evidences are returned).
{
"status": "success",
"session_id": "...",
"result_id": "69b3200a04bac13dfd4f45a5",
"relID": "433451.10452560.10.1.0.Hetionet",
"method": "MA",
"source": "Hetionet",
"total_count": 0,
"page": 1,
"page_size": 20,
"total_pages": 0,
"credits_consumed": 10,
"remaining_credits": 2768,
"evidences": []
}
Note: For text-mined relations, evidences are deduplicated per PMID and ordered by highest evidence probability. When a PMID appears in multiple sentences for a relation, the highest sentence-level probability is returned. All matching evidences are available (no cap) — use pagination to retrieve them.
Direct Relations — Field Reference#
Relation Object (details endpoint)#
| Field | Type | Description |
|---|---|---|
relID | string | Relation identifier (format: node2Id.node1Id.relType.corrType.direction.method for text-mined, node1Id.node2Id.relType.corrType.direction.<source> for curated). Use this value in evidence requests. |
probability | string | Relation probability score (0-1) |
method | string | "MP" for text-mined (NLP-extracted), "MA" for curated (knowledge-graph imports), "RNASeq" for RNA-Seq derived |
source | string | null | Curated source name when method == "MA": "Hetionet", "primeKG", "PubChem", "GO_annotation", or "TTD". null otherwise. |
relation_details | object | Node and relationship metadata (see below) |
evidence_count | int | Number of PMID-anchored evidences. Always 0 for curated relations (method == "MA") — they have no PubMed sentence anchors. |
relation_seq_id | int | Sequential ID within the result |
Relation Details Object#
| Field | Type | Description |
|---|---|---|
node1Id | string | BioKDE ID for entity one (the searched entity) |
node1Name | string | Display name for entity one |
node1Type | string | Entity type label |
node1Subtype | string | Entity subtype. "Drug" for drug-subtype Chemicals/Genes; "NA" (or empty) otherwise. |
node2Id | string | BioKDE ID for entity two |
node2Name | string | Display name for entity two |
node2Type | string | Entity type label |
node2Subtype | string | Entity subtype. "Drug" for drug-subtype Chemicals/Genes; "NA" (or empty) otherwise. |
score | number | Relation confidence score |
relationship_type | string | e.g., Positive_Correlation, Negative_Correlation, Association |
correlation_type | string | "2" (Positive), "0" (Negative), "1" (Unknown) |
direction | string | "12" (node1->node2), "21" (node2->node1), "0" (no direction), "NA" |
Evidence Response (evidence endpoints)#
| Field | Type | Description |
|---|---|---|
result_id | string | The result this evidence belongs to |
relID | string | The relation these evidences belong to |
method | string | "MP" for text-mined, "MA" for curated, "RNASeq" for RNA-Seq derived |
source | string | null | Curated source name ("Hetionet", "primeKG", "PubChem", "GO_annotation", "TTD") when method == "MA". null for text-mined. |
total_count | int | Total number of unique PMIDs for this relation. Always 0 for curated relations. |
page | int | Current page number |
page_size | int | Number of evidences per page |
total_pages | int | Total number of pages |
evidences | array[object] | Evidences for the current page, ordered by probability descending. Each entry is {"pmid": string, "probability": float, "pub_date": string}. probability is the highest sentence-level probability for that PMID in this relation. pub_date is the PubMed publication date in ISO YYYY-MM-DD format. Always [] for curated relations. |
Relation Type Reference#
The relation_type field accepts either human-readable names or numeric codes. Use "-1" (or omit the filter) to match all types.
| Name | Code | Name | Code | |
|---|---|---|---|---|
Association | 1 | drug_protein | 21 | |
Positive_Correlation | 2 | contraindication | 22 | |
Negative_Correlation | 3 | indication | 23 | |
Bind | 4 | off-label use | 24 | |
Cotreatment | 5 | drug_drug | 25 | |
Comparison | 6 | phenotype_protein | 26 | |
Drug_Interaction | 7 | phenotype_phenotype | 27 | |
Conversion | 8 | disease_phenotype_negative | 28 | |
interacts | 9 | disease_phenotype_positive | 29 | |
participates | 10 | disease_protein | 30 | |
causes | 11 | disease_disease | 31 | |
regulates | 12 | drug_effect | 32 | |
covaries | 13 | bioprocess_bioprocess | 33 | |
localizes | 14 | molfunc_molfunc | 34 | |
resembles | 15 | cellcomp_cellcomp | 35 | |
treats | 16 | molfunc_protein | 36 | |
includes | 17 | cellcomp_protein | 37 | |
presents | 18 | bioprocess_protein | 38 | |
palliates | 19 | exposure_protein | 39 | |
protein_protein | 20 | exposure_disease | 40 | |
exposure_exposure | 41 | |||
Drug_Target | 50 | exposure_bioprocess | 42 | |
Target_Disease | 51 | exposure_molfunc | 43 | |
Biomarker_Disease | 52 | exposure_cellcomp | 44 | |
pathway_pathway | 45 | |||
pathway_protein | 46 | |||
anatomy_anatomy | 47 | |||
anatomy_protein_present | 48 | |||
anatomy_protein_absent | 49 |
Note: Sending an invalid name or code returns HTTP 400 with the full list of valid values.
How It Works#
- Entity resolution: Entity names are resolved to
biokdeidvalues via Neo4j. Whenexclude_sub_type="0", Disease entities are expanded into their MeSH sub-tree via MongoDB (parent + all child diseases). - Graph traversal: A 1-hop Cypher query finds direct
RELATED_TOrelationships in Neo4j. For sub-type searches, separate queries run per sub-entity with independent result limits. - Cypher-level filters:
relation_type,correlation_type,direction,prob_method, andprobability_thresholdare applied in the CypherWHEREclause. When a date filter is set (start_dateandend_datenon-empty), aminDate/maxDatepredicate is appended; this also drops curated relations (which have no per-relation dates) — see Curated vs Text-Mined Relations. - Post-processing filters:
noveltyand date range filters are applied in Python on thelocationsJSON of each relationship. - Curated edge handling: Relations with
method="MA"(no PubMed-anchored locations) are kept withevidence_count=0and thesourcefield populated when no date/novelty filter is active. They are excluded when one of those filters is set. - Pre-validated evidence: Each text-mined evidence entry carries pre-baked LLM validation flags. Entries that were attempted and failed validation are dropped before counting. Entries that were never attempted, plus all curated evidence, are kept.
- Evidence counting: For each text-mined relation, the remaining evidence locations are counted after applying date/novelty filters. Text-mined relations with
evidence_count = 0after filtering are excluded; curated relations withevidence_count = 0are kept. Full evidence PMIDs are retrieved on-demand via the evidence endpoint. - Result storage: Results are saved to MongoDB for retrieval via the details endpoints.
Indirect Relations Search#
Search for indirect relationships between two biological entities using Neo4j graph traversal. Returns multi-hop paths connecting the two entities through intermediate nodes.
4. POST /open_api/iexplore/v3/search_indirect_relations#
Submits an indirect relationship search. Always queues a background task and returns a task_id — regardless of whether the search has been run before. If enforce_refetch is "0" and a cached result exists, the worker resolves the task immediately from history on the first poll; otherwise it runs the full search. The response shape is the same in both cases.
Identical submissions from the same user within the last 5 minutes are served from the gateway response cache and return the same
task_idwithcredits_consumed: 0. See Response Cache.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_one_type | string | Yes | — | Entity type for the first entity |
entity_one_name | string | Cond. | — | Name of the first entity. Required unless entity_one_id is given. |
entity_one_id | string | No | "" | Pre-resolved BioKDE ID for entity one. When set, name resolution is skipped. |
entity_two_type | string | Yes | — | Entity type for the second entity |
entity_two_name | string | Cond. | — | Name of the second entity. Required unless entity_two_id is given. |
entity_two_id | string | No | "" | Pre-resolved BioKDE ID for entity two. When set, name resolution is skipped. |
entity_one_species | string | No | "Homo Sapiens" | Species for entity one |
entity_two_species | string | No | "Homo Sapiens" | Species for entity two |
starting_date | string | No | "" | Start of date range filter (YYYY-MM-DD). Filters text-mined evidence locations. Setting a non-empty value here or in ending_date also drops any path containing a curated hop — see Curated vs Text-Mined Relations. |
ending_date | string | No | "" | End of date range filter (YYYY-MM-DD). Same caveat as starting_date. |
relation_type | string | No | "" | "Positive_Correlation" or "Negative_Correlation". Empty = no filter. |
direction | string | No | "" | "12" or "21". Empty = no filter. |
novelty | string | No | "-1" | "-1" (all), "1" (novel only), "0" (non-novel only). Setting any value other than "-1" also drops any path containing a curated hop — see Curated vs Text-Mined Relations. |
limit | int | No | 100 | Maximum number of paths to return (1-1000) |
enforce_refetch | string | No | "0" | "1" = bypass cached results and re-run search |
intermediate_nodes | int | No | 1 | Number of intermediate nodes in path. 1 = 2-hop, 2 = 3-hop, 3 = 4-hop, and so on. Larger values search deeper but are exponentially more expensive and may exceed the query timeout. |
Example Request#
{
"entity_one_type": "Gene",
"entity_one_name": "GST",
"entity_two_type": "Disease",
"entity_two_name": "Prostatic Neoplasms",
"starting_date": "2020-01-01",
"ending_date": "2025-12-31",
"relation_type": "Positive_Correlation",
"novelty": "-1",
"enforce_refetch": "0",
"intermediate_nodes": 1
}
Response (HTTP 202)#
Every request — whether cached or new — returns a queued response with a task_id:
{
"status": "success",
"session_id": "1776742838149_92397606-4e17-42be-8dfd-510b2a0c9d91_4288135032",
"message": "Your indirect search request has been queued. Please poll the task_status endpoint with your task_id.",
"credits_consumed": 10,
"remaining_credits": 2860,
"task_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
}
Next step: Poll
task_statuswith thetask_iduntil the status is"completed", then use the returnedresult_idwith the details endpoints. Cached searches typically complete on the first poll.
5. POST /open_api/iexplore/v3/search_indirect_relations_details#
Retrieves saved indirect search results with evidence counts only per relationship. Lightweight endpoint for browsing path structure.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
result_id | string | Yes | — | The result_id returned by search_indirect_relations or task_status |
requested_paths_count | int | No | 0 | Maximum number of paths to return, ordered by net_prob (product of hop probabilities) descending. 0 means return all. The per-relation portion of the bill is (total relation edges across returned paths) × per-relation price — a 2-hop path counts as 2 relations, a 3-hop path as 3 — so lowering this value lowers the credit cost of the call. |
Example Request#
{
"result_id": "69c4311b15cbc24ege5f56b6",
"requested_paths_count": 10
}
Response#
{
"status": "success",
"session_id": "...",
"paths_count": 42,
"returned_count": 10,
"credits_consumed": 30,
"remaining_credits": 2738,
"paths": [
{
"path_length": 2,
"net_correlationType": "Positive_Correlation",
"net_prob": 0.85,
"nodes": [
{"biokdeid": "7529", "name": "GSTK1", "officialName": "GSTK1", "commonName": "GST", "type": "Gene"},
{"biokdeid": "12345", "name": "RPS6KB1", "officialName": "RPS6KB1", "commonName": "S6K1", "type": "Gene"},
{"biokdeid": "2438", "name": "Prostatic Neoplasms", "officialName": "Prostatic Neoplasms", "commonName": "Prostatic Carcinoma", "type": "Disease"}
],
"relationships": [
{"relID": "7529.12345.30.1.NA.primeKG", "relationshipType": "Association", "correlationType": "Positive_Correlation", "direction": "NA", "score": 1.0, "prob": 0.95, "method": "MA", "source": "primeKG", "evidence_count": 0},
{"relID": "12345.2438.27.1.NA.MP", "relationshipType": "Association", "correlationType": "Positive_Correlation", "direction": "NA", "score": 1.0, "prob": 0.89, "method": "MP", "source": null, "evidence_count": 8}
]
}
]
}
6. POST /open_api/iexplore/v3/search_indirect_relations_evidences#
Retrieve evidence PMIDs for a single relationship in a saved indirect search result. This is a synchronous, paginated endpoint — PMIDs are fetched from Neo4j and returned immediately.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
result_id | string | Yes | — | The result_id from search_indirect_relations or task_status |
relID | string | Yes | — | The relID of the relationship (from the details endpoint response) |
page | int | No | 1 | Page number (1-indexed) |
page_size | int | No | 20 | Number of PMIDs per page (1–500) |
Example Request#
{
"result_id": "69c4311b15cbc24ege5f56b6",
"relID": "7529.12345.30.1.NA.primeKG",
"page": 1,
"page_size": 20
}
Response (HTTP 200) — Text-mined hop#
{
"status": "success",
"session_id": "...",
"result_id": "69c4311b15cbc24ege5f56b6",
"relID": "12345.2438.27.1.NA.MP",
"method": "MP",
"source": null,
"total_count": 12,
"page": 1,
"page_size": 20,
"total_pages": 1,
"credits_consumed": 30,
"remaining_credits": 2708,
"evidences": [
{"pmid": "28651234", "probability": 0.97, "pub_date": "2017-04-12"},
{"pmid": "31045678", "probability": 0.89, "pub_date": "2019-05-08"},
{"pmid": "33217890", "probability": 0.82, "pub_date": "2020-11-19"}
]
}
Response (HTTP 200) — Curated hop#
For hops with method == "MA", the endpoint short-circuits with an empty evidences list and returns the curated source so the client can render an appropriate message. Only the base price is charged (no per-evidence charge when no evidences are returned).
{
"status": "success",
"session_id": "...",
"result_id": "69c4311b15cbc24ege5f56b6",
"relID": "7529.12345.30.1.NA.primeKG",
"method": "MA",
"source": "primeKG",
"total_count": 0,
"page": 1,
"page_size": 20,
"total_pages": 0,
"credits_consumed": 10,
"remaining_credits": 2698,
"evidences": []
}
Note: For text-mined hops, evidences are deduplicated per PMID and ordered by highest evidence probability. When a PMID supports the hop in multiple sentences, the highest sentence-level probability is returned. All matching evidences are available (no cap) — use pagination to retrieve them. The endpoint returns evidence for one hop in the indirect path, not aggregated across the full path.
Indirect Relations — Field Reference#
Path Object#
| Field | Type | Description |
|---|---|---|
path_length | int | Number of hops in the path |
net_correlationType | string | Net correlation across the path ("Positive_Correlation" or "Negative_Correlation") |
net_prob | float | Net probability across the path |
nodes | array | Ordered list of nodes (start → intermediate(s) → end) |
relationships | array | List of relationships along the path |
Node Object#
| Field | Type | Description |
|---|---|---|
biokdeid | string | IKraph_API entity ID |
name | string | Display name (officialName or commonName) |
officialName | string | Official entity name |
commonName | string | Common entity name |
type | string | Entity type label (e.g., Gene, Disease) |
Relationship Object (details endpoint)#
| Field | Type | Description |
|---|---|---|
relID | string | Relationship identifier. Use this value in evidence requests. |
relationshipType | string | e.g., Association, Positive_Correlation |
correlationType | string | "Positive_Correlation" or "Negative_Correlation" |
direction | string | "12", "21", "NA", or "0" |
score | number | Relationship confidence score |
prob | float | Relationship probability |
method | string | "MP" for text-mined, "MA" for curated, "RNASeq" for RNA-Seq derived |
source | string | null | Curated source name ("Hetionet", "primeKG", "PubChem", "GO_annotation", "TTD") when method == "MA". null for text-mined. |
evidence_count | int | Number of evidence locations (after filters). Always 0 for curated hops (method == "MA"). |
relation_seq_id | int | Sequential ID within the result |
How It Works#
- Entity resolution: Entity names are resolved to
biokdeidvalues via Neo4j - Graph traversal: A multi-hop Cypher query finds intermediate nodes connecting the two entities through
RELATED_TOrelationships in Neo4j - Cypher-level filters:
relation_typeanddirectionfilters are applied in the Cypher WHERE clause for performance. When a date filter is set, a per-hopminDate/maxDatepredicate is appended; this also drops paths where any hop is curated (no per-relation dates) — see Curated vs Text-Mined Relations. - Post-processing filters:
noveltyand date range filters are applied in Python on thelocationsJSON of each hop. If any hop is curated and a date/novelty filter is active, the entire path is dropped. - Curated hop handling: When no date/novelty filter is active, paths containing curated hops are kept; the curated hops carry
evidence_count = 0and asourcefield naming the curated database. - Deduplication: Paths through the same intermediate node are deduplicated
Task Status#
7. POST /open_api/iexplore/v3/task_status#
Poll the status of an async search task. Use this after receiving a task_id from search_direct_relations or search_indirect_relations.
Request Body#
| Field | Type | Required | Description |
|---|---|---|---|
task_id | string | Yes | The task_id returned by the search endpoint |
Example Request#
{
"task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Response (Pending/Processing)#
{
"status": "success",
"session_id": "...",
"task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"search_type": "direct",
"created_at": "2026-03-16T10:30:00"
}
Response (Completed — Direct Search)#
{
"status": "completed",
"task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"search_type": "direct",
"created_at": "2026-03-16T10:30:00",
"result_id": "69b3200a04bac13dfd4f45a5",
"relations_count": 117,
"message": "Search completed. Use the result_id to retrieve details."
}
Response (Completed — Indirect Search)#
{
"status": "completed",
"task_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"search_type": "indirect",
"created_at": "2026-03-16T10:30:00",
"result_id": "69c4311b15cbc24ege5f56b6",
"paths_count": 42,
"message": "Search completed. Use the result_id to retrieve details."
}
Response (Failed)#
{
"status": "failed",
"task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"search_type": "direct",
"created_at": "2026-03-16T10:30:00",
"error": "Error description"
}
Response (Task Not Found — HTTP 404)#
{
"error": "Task not found. It may have expired (tasks expire after 48 hours)."
}
Response (Rate Limited — HTTP 429)#
{
"message": "Polling too frequently. Please wait at least 1 second between polls for the same task.",
"session_id": "..."
}
The gateway enforces a minimum interval of 1 second between polls for the same task_id. Requests that arrive within 1 second of a prior poll on the same task_id are rejected with HTTP 429 and a Retry-After: 1 response header. The reject path is intentionally cheap — it does not call the downstream search service and does not consume credits — but a cooperative client should still:
- Honor the
Retry-Afterheader (value in seconds) before retrying. - Avoid concurrent polls for the same
task_idfrom multiple workers, threads, or async tasks. - Pace polls at ≥ 3 seconds (as shown in the Python example below), comfortably above the 1-second floor.
Note: the rate cap is per task_id, not per API key. Polling multiple distinct task_ids in parallel is unaffected.
Task Lifetime & Recovery After Expiry#
- Task records (
task_id→ status) live in Redis with a 48-hour TTL, starting at submission time. The TTL is not refreshed by polling. After 48 hours,task_statusreturns HTTP 404 as shown above and thetask_idis permanently gone. - Search results (
result_id→ relations/paths) are persisted in MongoDB with no expiry. Once a search has completed, theresult_idremains valid for the details and evidence endpoints indefinitely. - Evidence responses are served from Neo4j + Redis caches. The Redis evidence cache has a 24-hour TTL by default but is transparent — a miss simply re-queries Neo4j.
Recovery recipe when task_status returns 404:
- If you still have the
result_idfrom a priortask_statuscompletion response: proceed directly to the details/evidence endpoints — no need to re-submit. - If you never captured the
result_id: re-submit the original search with the same parameters andenforce_refetch: "0". The backend recognizes the repeated search via its own history cache and resolves the new task immediately on the first poll (see Search Flow), returning the originalresult_id. Note that this re-submission still costs the base price, unless the original submission happened within the last 5 minutes and the gateway's user-level response cache is still warm.
Tip: Capture and persist result_id as soon as task_status reports "completed". As long as you hold either a valid task_id (≤48 h old) or the result_id, recovery is free: task_status will return the result_id again from the task_id, and the result_id remains valid indefinitely. Losing both (or losing the result_id after the 48-hour task TTL) forces a paid re-submission.
Path Search#
Path search finds routes between entities in the knowledge graph. Both endpoints
are async (return a task_id) and store results in the indirect result
store — poll task_status, then fetch with search_indirect_relations_details
and search_indirect_relations_evidences using the returned result_id.
POST /open_api/iexplore/v3/search_path#
Finds the shortest (highest-probability) path — or the k shortest paths —
between two entities using the Neo4j Graph Data Science (GDS) plugin. Edge weight
is derived from relation probability, so the "shortest" path maximizes the product
of per-hop probabilities.
Requires the GDS plugin installed on the graph server.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_one_type / entity_two_type | string | Yes | — | Entity types (Chemical, Disease, Gene) |
entity_one_name / entity_two_name | string | Cond. | — | Entity names. Each is required unless the matching entity_*_id is given. |
entity_one_id / entity_two_id | string | No | "" | Pre-resolved BioKDE IDs (skip name resolution) |
entity_one_species / entity_two_species | string | No | "Homo Sapiens" | Species context |
is_drug_one / is_drug_two | string | No | "0" | Scope endpoint to drug entities |
is_true_name | string | No | "1" | Name-resolution mode (see get_biokde_id) |
k | int | No | 1 | Number of shortest paths (Yen's algorithm when > 1; max 10) |
limit | int | No | 100 | Maximum paths to return |
relation_type | string | No | "" | "Positive_Correlation" / "Negative_Correlation" net-correlation filter |
direction | string | No | "" | "12" / "21" direction filter applied per hop |
novelty | string | No | "-1" | Novelty filter |
starting_date / ending_date | string | No | "" | Date-range filter (YYYY-MM-DD) |
enforce_refetch | string | No | "0" | Bypass cached result |
Example Request#
{
"entity_one_type": "Chemical", "entity_one_name": "aspirin",
"entity_two_type": "Disease", "entity_two_name": "colorectal cancer",
"k": 3
}
Response (HTTP 202)#
{ "task_id": "…", "status": "pending" }
POST /open_api/iexplore/v3/search_specific_path#
Searches for paths that match an explicit hop structure you specify: a root
node and an ordered list of hops. Each hop offers one or more match alternatives
(matched with OR); a path qualifies if every hop matches one of its alternatives.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
root | object | Yes | — | { "id": "<biokde_id>" } — the starting node |
hops | array | Yes | — | Ordered list of hop objects (see below) |
limit | int | No | 100 | Maximum paths to return |
novelty | string | No | "-1" | Novelty filter |
starting_date / ending_date | string | No | "" | Date-range filter |
enforce_refetch | string | No | "0" | Bypass cached result |
Each hop is { "match": [ <alternative>, … ] }. An <alternative> is either:
{ "relID": "<relID>" }— match one specific edge by id; or{ "edge": { "type", "direction", "starting_year", "end_year" }, "node": { "type", "name", "id" } }where all sub-fields are optional:edge.type— relation type name (see Relation Type Reference)edge.direction—"12"(root-side → next),"21"(next → root-side),"0"(either)edge.starting_year/edge.end_year— keep edges within the year rangenode.type— entity type of the hop's target nodenode.name— target node official/common namenode.id— target node BioKDE id
Example Request#
{
"root": { "id": "12345" },
"hops": [
{
"match": [
{ "edge": { "type": "treats", "direction": "12", "starting_year": 2010 }, "node": { "type": "Gene" } },
{ "edge": { "type": "causes", "direction": "21", "end_year": 2020 }, "node": { "type": "Disease", "name": "entity_name2" } },
{ "relID": "relid_1234" }
]
}
]
}
Response (HTTP 202)#
{ "task_id": "…", "status": "pending" }
Functional / Metadata APIs#
These endpoints return metadata and documentation. They are authenticated but free of charge (no credits consumed) and require no async task.
GET /open_api/iexplore/v3/readme#
Returns this API documentation as raw markdown (text/markdown). Pass
?format=json to receive it as { "readme": "<markdown>" }.
GET /open_api/iexplore/v3/entity-type#
Returns a JSON array of all valid entity type names, e.g.
["Anatomy", "Biological Process", "Chemical", "Disease", "Gene", …].
POST /open_api/iexplore/v3/entity-type-detail#
Returns definitions for the requested entity types. Body: { "name": ["Gene", "Chemical"] }
(a single name is allowed). Response:
[
{ "name": "Gene", "definition": "A segment of DNA that encodes a functional product … Examples: TP53, BRCA1, EGFR." },
{ "name": "Chemical", "definition": "A chemical substance or small molecule … Examples: aspirin, glucose, cisplatin." }
]
Unknown names return {"name": "<name>", "definition": null}.
GET /open_api/iexplore/v3/relation-type#
Returns a JSON array of all valid relation type names, e.g.
["Association", "Positive_Correlation", "causes", "treats", …].
POST /open_api/iexplore/v3/relation-type-detail#
Returns definitions for the requested relation types. Body: { "name": ["treats", "causes"] }.
Response mirrors entity-type-detail.
Entity Resolution#
8. POST /open_api/iexplore/v3/get_biokde_id#
Resolves an entity name to its BioKDE ID. Useful for verifying that an entity exists in the knowledge graph before submitting a search, or for looking up internal IDs. Has two modes, selected by is_true_name:
is_true_name="1"(default): treat the input as the entity's canonical/true name and return the single best match — backward-compatible response shape.is_true_name="0": treat the input as a possible synonym and return a paginated, ranked list of every matching entity ("contains anywhere", case-insensitive) so the caller can disambiguate before searching.
Request Body#
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_type | string | Yes | — | Entity type (see Entity Types) |
entity_name | string | Yes | — | Name of the entity to resolve |
entity_species | string | No | "Homo Sapiens" | Species context for the entity (used when entity_type="Gene") |
is_drug | string | No | "0" | "1" scopes the lookup to drug entities — requires entity_type Chemical or Gene (see is_drug) |
is_true_name | string | No | "1" | "1" = exact canonical-name match, single result. "0" = fuzzy synonym search, ranked candidate list |
page | int | No | 1 | Candidate-list page number. Only used when is_true_name="0" |
page_size | int | No | 20 | Candidates per page (1–100). Only used when is_true_name="0" |
sort | string | No | "mentions" | Candidate ordering: "mentions" (mention count desc) or "name" (alphabetical). Only used when is_true_name="0" |
Example Request (is_true_name="1", exact match)#
{
"entity_type": "Gene",
"entity_name": "TP53",
"entity_species": "Homo Sapiens"
}
Response (HTTP 200)#
{
"status": "success",
"session_id": "...",
"biokde_id": "7157",
"entity_type": "Gene",
"entity_name": "TP53",
"entity_species": "Homo Sapiens",
"is_drug": "0",
"credits_consumed": 10,
"remaining_credits": 2688
}
Example Request (is_true_name="0", fuzzy synonym search)#
{
"entity_type": "Chemical",
"entity_name": "aspirin",
"is_true_name": "0",
"page": 1,
"page_size": 20,
"sort": "mentions"
}
Response (HTTP 200, is_true_name="0")#
{
"status": "success",
"session_id": "...",
"entity_type": "Chemical",
"entity_name": "aspirin",
"entity_species": "Homo Sapiens",
"is_drug": "0",
"is_true_name": "0",
"sort": "mentions",
"total_count": 3,
"page": 1,
"page_size": 20,
"total_pages": 1,
"candidates": [
{
"biokde_id": "2244",
"name": "aspirin",
"official_name": "Aspirin",
"type": "Chemical",
"subtype": "Drug",
"species": null,
"cnt": 15234
}
],
"credits_consumed": 10,
"remaining_credits": 2678
}
Each entry in candidates is one distinct entity (biokde_id), collapsed from every matching synonym down to its highest-mention-count name. Pick a biokde_id from this list and pass it as entity_one_id / entity_two_id to a search endpoint to skip name resolution entirely.
Response (Not Found — HTTP 404)#
{
"error": "Entity not found: TP53X (Gene)"
}
Note:
is_true_name="1"uses cascading name matching against MongoDB: first tries exact match on the synonymnamefield, then a capitalized variant, then exact match onofficial name.is_true_name="0"does a case-insensitive substring match across all synonyms of the givenentity_type(andspecies/is_drugscope, if set) — spaces included, so a query can match across word boundaries in a multi-word synonym.
Error Responses#
| Status | Condition |
|---|---|
| 400 | Invalid signature, validation error, or malformed request |
| 401 | Authentication failed (invalid/missing API key) |
| 403 | User does not have permission for this service |
| 404 | Entity not found in the knowledge graph, or task_id expired |
| 500 | Neo4j query error, MongoDB error, or internal server error |
| 503 | Unable to verify user permission (User Center unavailable) |
Sample Script (Python)#
A complete example showing the async search flow and synchronous evidence retrieval.
import requests
import json
import time
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BASE_URL = "https://service.insilicom.com"
API_KEY = "YOUR_API_KEY"
HEADERS = {"Content-Type": "application/json", "X-API-KEY": API_KEY}
def search_direct_relations():
"""Step 1: Submit a direct relation search."""
url = f"{BASE_URL}/open_api/iexplore/v3/search_direct_relations"
payload = {
"entity_one_type": "Disease",
"entity_one_name": "Lung Cancer",
"entity_two_type": "Chemical",
"entity_two_name": "",
"relation_type": ["Negative_Correlation"],
"relation_direction": "21",
"novelty": "-1",
"start_date": "1999-01-01",
"end_date": "2005-01-01",
"exclude_sub_type": "1",
"enforce_refetch": "0",
"probability_threshold": 0.9
}
response = requests.post(url, headers=HEADERS, json=payload, verify=False)
print(f"=== Search Direct Relations (HTTP {response.status_code}) ===")
result = response.json()
print(json.dumps(result, indent=2))
return result
def poll_task_status(task_id):
"""Step 2: Poll task_status until completed or failed."""
url = f"{BASE_URL}/open_api/iexplore/v3/task_status"
print(f"\nPolling task {task_id}...")
while True:
response = requests.post(url, headers=HEADERS, json={"task_id": task_id}, verify=False)
# The gateway caps polls to one per second per task_id. Honor Retry-After.
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", "1"))
print(f" Rate limited; retrying in {retry_after}s")
time.sleep(retry_after)
continue
result = response.json()
status = result.get("status", "")
print(f" Status: {status}")
if status == "completed":
print(f" Result ID: {result.get('result_id')}")
print(f" Relations: {result.get('relations_count', result.get('paths_count', 0))}")
return result
elif status == "failed":
print(f" Error: {result.get('error')}")
return result
else:
time.sleep(3)
def get_details(result_id):
"""Step 3: Retrieve results (evidence counts only)."""
url = f"{BASE_URL}/open_api/iexplore/v3/search_direct_relations_details"
response = requests.post(url, headers=HEADERS, json={"result_id": result_id}, verify=False)
print(f"\n=== Details (HTTP {response.status_code}) ===")
result = response.json()
print(json.dumps(result, indent=2))
return result
def get_evidences(result_id, rel_id, page=1, page_size=20):
"""Step 4: Retrieve evidence PMIDs for a single relation (synchronous, paginated)."""
url = f"{BASE_URL}/open_api/iexplore/v3/search_direct_relations_evidences"
payload = {
"result_id": result_id,
"relID": rel_id,
"page": page,
"page_size": page_size
}
response = requests.post(url, headers=HEADERS, json=payload, verify=False)
print(f"\n=== Evidences (HTTP {response.status_code}) ===")
result = response.json()
print(json.dumps(result, indent=2))
return result
if __name__ == "__main__":
# Step 1: Submit search — always returns a task_id (cached searches complete on first poll)
search_result = search_direct_relations()
task_id = search_result.get("task_id")
if not task_id:
print("No task_id returned. Check errors above.")
exit(1)
# Step 2: Poll task_status until the task resolves
task_result = poll_task_status(task_id)
result_id = task_result.get("result_id")
if not result_id:
print("Search failed. No result_id available.")
exit(1)
# Step 3: Retrieve details (evidence counts only)
details = get_details(result_id)
# Step 4: Pick a relID from the details response and retrieve evidence PMIDs
relations = details.get("relations", [])
if relations:
# Handle both single-entity (list of dicts) and sub-type (grouped) formats
first = relations[0]
if isinstance(first, list) and first:
rel_id = first[0].get("relID")
elif isinstance(first, dict) and "relations" in first:
rel_id = first["relations"][0].get("relID")
else:
rel_id = first.get("relID")
if rel_id:
get_evidences(result_id, rel_id, page=1, page_size=20)
