Skill Tracer
Skills › Scientific Agent Skills › ontology-term-resolution › companion-apis.md

companion-apis.md

183 lines · skills/ontology-term-resolution/references/companion-apis.md at 5f2bc03

Open on GitHub
1# Companion identifier services
2
3OLS verifies terms in its loaded ontology releases. The four services below answer
4different questions. Reviewed 2026-10-01 against official schemas/source and public
5read-only requests; endpoint results and registry mappings can change.
6
7| Service | Use it for | Do not use it for |
8| --- | --- | --- |
9| Bioregistry | Is this prefix real? Does the local id match the recorded pattern? What is the preferred prefix? | Whether the term exists or is obsolete |
10| Identifiers.org | Landing-page URLs from Bioregistry `providers.miriam` | Synonym prefixes (`HPO:…`); templating `preferred_prefix`; existence checks |
11| ZOOMA | Mapping lab shorthand OLS cannot lexical-match | Unfiltered annotate; writing an ID without OLS validation |
12| Ontobee | The OBO Foundry HTML/RDF page for a term IRI | Search, validation, or routine resolution — there is no JSON search API |

Companion identifier services

OLS verifies terms in its loaded ontology releases. The four services below answer different questions. Reviewed 2026-10-01 against official schemas/source and public read-only requests; endpoint results and registry mappings can change.

ServiceUse it forDo not use it for
BioregistryIs this prefix real? Does the local id match the recorded pattern? What is the preferred prefix?Whether the term exists or is obsolete
Identifiers.orgLanding-page URLs from Bioregistry providers.miriamSynonym prefixes (HPO:…); templating preferred_prefix; existence checks
ZOOMAMapping lab shorthand OLS cannot lexical-matchUnfiltered annotate; writing an ID without OLS validation
OntobeeThe OBO Foundry HTML/RDF page for a term IRISearch, validation, or routine resolution — there is no JSON search API
Raw lines 1 to 13
14## Bioregistry
16Base URL: `https://bioregistry.io/api`. No API key. Official
17[usage guide](https://bioregistry.io/usage) and [OpenAPI schema](https://bioregistry.io/openapi.json).
18These are individual-resource lookups and an unpaged prefix search, not paginated term searches.
20| Endpoint | Question |
21| --- | --- |
22| `GET /registry/{prefix}` | What is this prefix? Accepts synonyms. |
23| `GET /reference/{CURIE}` | Is the local id well-formed, and which providers resolve it? |
24| `GET /search?q=` | Prefix search. Returns `[[canonical, synonym], …]`. |
26Useful record fields: `prefix` (canonical, usually lowercase), `preferred_prefix`
27(`HP`, `CHEBI`), `pattern` (regex for the **local** id only), `example`,
28`uri_format` (`$1` is the local id), `synonyms`, `mappings.ols`,
29`mappings.ontobee`, `mappings.miriam`.
31`lookup_prefix.py` wraps the first two endpoints.

Bioregistry

Base URL: https://bioregistry.io/api. No API key. Official usage guide and OpenAPI schema. These are individual-resource lookups and an unpaged prefix search, not paginated term searches.

EndpointQuestion
GET /registry/{prefix}What is this prefix? Accepts synonyms.
GET /reference/{CURIE}Is the local id well-formed, and which providers resolve it?
GET /search?q=Prefix search. Returns [[canonical, synonym], …].

Useful record fields: prefix (canonical, usually lowercase), preferred_prefix (HP, CHEBI), pattern (regex for the local id only), example, uri_format ($1 is the local id), synonyms, mappings.ols, mappings.ontobee, mappings.miriam.

lookup_prefix.py wraps the first two endpoints.

Raw lines 14 to 32
33### Trap — synonym prefixes resolve here and fail elsewhere
35```
36GET /registry/HPO -> 200, prefix=hp, preferred_prefix=HP, synonyms=["hpo"]
37GET /reference/HPO:0001250 -> 200, same providers as HP:0001250
38GET https://resolver.api.identifiers.org/HPO:0001250
39 -> 400, "NOT A NAMESPACE"
40```
42If a metadata file writes `HPO:0001250`, Bioregistry will accept the synonym while
43Identifiers.org rejects it. `HP:0001250` works for both OLS and Identifiers.org. Do not
44generalize this to every prefix: Bioregistry prefers `ORPHA`, OLS uses `Orphanet`/`ordo`,
45and Identifiers.org uses `orphanet`. Verify the mapping and IRI, not only capitalization.

Trap — synonym prefixes resolve here and fail elsewhere

GET /registry/HPO          -> 200, prefix=hp, preferred_prefix=HP, synonyms=["hpo"]
GET /reference/HPO:0001250 -> 200, same providers as HP:0001250
GET https://resolver.api.identifiers.org/HPO:0001250
                           -> 400, "NOT A NAMESPACE"

If a metadata file writes HPO:0001250, Bioregistry will accept the synonym while Identifiers.org rejects it. HP:0001250 works for both OLS and Identifiers.org. Do not generalize this to every prefix: Bioregistry prefers ORPHA, OLS uses Orphanet/ordo, and Identifiers.org uses orphanet. Verify the mapping and IRI, not only capitalization.

Raw lines 33 to 46
47### Trap — 404 is two different failures
49`/reference/{CURIE}` returns HTTP 404 with a JSON `detail` for both:
51- unknown prefix: `"Prefix not found: …"`
52- known prefix, bad local id: `"invalid identifier: hp:notanid for pattern ^\\d{7}$"`
54Read `detail`. Treating both as "no such prefix" hides a well-formed-prefix,
55malformed-local-id error. Client-side `re.fullmatch(pattern, local)` is the
56same check and does not need a network call once you have the record.

Trap — 404 is two different failures

/reference/{CURIE} returns HTTP 404 with a JSON detail for both:

  • unknown prefix: "Prefix not found: …"
  • known prefix, bad local id: "invalid identifier: hp:notanid for pattern ^\\d{7}$"

Read detail. Treating both as "no such prefix" hides a well-formed-prefix, malformed-local-id error. Client-side re.fullmatch(pattern, local) is the same check and does not need a network call once you have the record.

Raw lines 47 to 57
58### Trap — `pattern` is the local id, not the CURIE
60`HP` has `pattern: ^\d{7}$`. `0001250` matches; `HP:0001250` does not. Never
61run the regex against the whole CURIE.

Trap — pattern is the local id, not the CURIE

HP has pattern: ^\d{7}$. 0001250 matches; HP:0001250 does not. Never run the regex against the whole CURIE.

Raw lines 58 to 62
63## Identifiers.org
65Resolver: `https://resolver.api.identifiers.org/{CURIE}`. [Resolver API documentation](https://docs.identifiers.org/pages/api.html). No API key.
66This returns provider URLs in one response, not paginated ontology terms. No provider
67URL or successful resolver response proves that the term exists in its underlying database.
69A successful body is `{apiVersion, errorMessage: null, payload: {resolvedResources: […]}}`.
70Each resource has `compactIdentifierResolvedUrl`, `providerCode`, `official`,
71and `recommendation.recommendationIndex`.

Identifiers.org

Resolver: https://resolver.api.identifiers.org/{CURIE}. Resolver API documentation. No API key. This returns provider URLs in one response, not paginated ontology terms. No provider URL or successful resolver response proves that the term exists in its underlying database.

A successful body is {apiVersion, errorMessage: null, payload: {resolvedResources: […]}}. Each resource has compactIdentifierResolvedUrl, providerCode, official, and recommendation.recommendationIndex.

Raw lines 63 to 72
73### Trap — preferred prefix is not the Identifiers.org namespace
75Bioregistry `preferred_prefix`, OLS CURIE spelling, and MIRIAM namespaces can differ,
76and not every prefix has a MIRIAM mapping:
78```
79GET /reference/orphanet:558 -> providers.miriam = https://identifiers.org/orphanet:558
80GET resolver/ORPHA:558 -> 400 NOT A NAMESPACE (preferred_prefix is ORPHA)
81GET resolver/orphanet:558 -> 200
82GET /reference/OBA:0000001 -> no providers.miriam (OBA, XAO, ECTO have none)
83GET resolver/hp:0001250 -> 400 (namespace embeds HP: in the LUI)
84GET resolver/CHEBI:15377 -> 200
85GET resolver/chebi:15377 -> 400
86```
88Always take the landing page from `/api/reference/{CURIE}` `providers.miriam`.
89Leave the column empty when that mapping is missing. Do not template
90`https://identifiers.org/{preferred_prefix}:{local}` — that is how
91`ORPHA:558` and `OBA:0000001` become dead links next to a rejection note.

Trap — preferred prefix is not the Identifiers.org namespace

Bioregistry preferred_prefix, OLS CURIE spelling, and MIRIAM namespaces can differ, and not every prefix has a MIRIAM mapping:

GET /reference/orphanet:558  -> providers.miriam = https://identifiers.org/orphanet:558
GET resolver/ORPHA:558       -> 400 NOT A NAMESPACE   (preferred_prefix is ORPHA)
GET resolver/orphanet:558    -> 200
GET /reference/OBA:0000001   -> no providers.miriam   (OBA, XAO, ECTO have none)
GET resolver/hp:0001250      -> 400                   (namespace embeds HP: in the LUI)
GET resolver/CHEBI:15377     -> 200
GET resolver/chebi:15377     -> 400

Always take the landing page from /api/reference/{CURIE} providers.miriam. Leave the column empty when that mapping is missing. Do not template https://identifiers.org/{preferred_prefix}:{local} — that is how ORPHA:558 and OBA:0000001 become dead links next to a rejection note.

Raw lines 73 to 92
93### Trap — do not encode the colon
95`https://resolver.api.identifiers.org/HP:0001250` works.
96`https://resolver.api.identifiers.org/HP%3A0001250` is HTTP 400
97("NOT A NAMESPACE"). Leave `:` unencoded in the path.
99A 400 body still parses as JSON — `errorMessage` is set and
100`resolvedResources` is null. That is a rejected compact identifier, not a
101transport failure. Timeouts, malformed JSON and 5xx responses are service failures;
102`lookup_prefix.py` retains the registry-supplied URL and labels it unverified rather than
103claiming the identifier was rejected. Only a resolver rejection blanks that URL.

Trap — do not encode the colon

https://resolver.api.identifiers.org/HP:0001250 works. https://resolver.api.identifiers.org/HP%3A0001250 is HTTP 400 ("NOT A NAMESPACE"). Leave : unencoded in the path.

A 400 body still parses as JSON — errorMessage is set and resolvedResources is null. That is a rejected compact identifier, not a transport failure. Timeouts, malformed JSON and 5xx responses are service failures; lookup_prefix.py retains the registry-supplied URL and labels it unverified rather than claiming the identifier was rejected. Only a resolver rejection blanks that URL.

Raw lines 93 to 104
105## ZOOMA
107Bundled compatibility route: `GET https://www.ebi.ac.uk/spot/zooma/v2/api/services/annotate`.
108No API key or pagination. The client uses a 60-second timeout and limits returned rows locally.
109Current [official docs source](https://github.com/EBISPOT/zooma2/blob/dev/frontend/src/pages/docs/api.tsx)
110also describes v3 JSON mapping/streaming routes; this client deliberately retains the supported
111[v2 contract](https://github.com/EBISPOT/zooma2/blob/dev/backend/src/main/java/uk/ac/ebi/zooma2/api/v2/ZoomaApiV2.java).
113| Parameter | Effect |
114| --- | --- |
115| `propertyValue` | Free-text string. |
116| `propertyType` | Optional context such as `organism part` or `cell type`. |
117| `filter` | The client requires `ontologies:[uberon],defining_only:[true]`; the service itself allows omission. |
119Ontology restrictions select targets; `defining_only:[true]` excludes imported namespaces.
120Omitting `required` permits available curated sources. The older `required:[none]` was not a
121requirement of the API and is no longer sent. Do not use `ontologies:[none]` to disable ontology
122matching: the current v2 adapter interprets that legacy sentinel as no ontology restriction.
124Hits contain `confidence` (`HIGH`, `GOOD`, `MEDIUM`, `LOW`), `semanticTags` (IRIs),
125`annotatedProperty`, `provenance`, and often `derivedFrom`. Unknown IRI namespaces remain
126unconverted rather than being turned into plausible CURIEs by splitting a URL.

ZOOMA

Bundled compatibility route: GET https://www.ebi.ac.uk/spot/zooma/v2/api/services/annotate. No API key or pagination. The client uses a 60-second timeout and limits returned rows locally. Current official docs source also describes v3 JSON mapping/streaming routes; this client deliberately retains the supported v2 contract.

ParameterEffect
propertyValueFree-text string.
propertyTypeOptional context such as organism part or cell type.
filterThe client requires ontologies:[uberon],defining_only:[true]; the service itself allows omission.

Ontology restrictions select targets; defining_only:[true] excludes imported namespaces. Omitting required permits available curated sources. The older required:[none] was not a requirement of the API and is no longer sent. Do not use ontologies:[none] to disable ontology matching: the current v2 adapter interprets that legacy sentinel as no ontology restriction.

Hits contain confidence (HIGH, GOOD, MEDIUM, LOW), semanticTags (IRIs), annotatedProperty, provenance, and often derivedFrom. Unknown IRI namespaces remain unconverted rather than being turned into plausible CURIEs by splitting a URL.

Raw lines 105 to 127
128### Confidence and provenance are not curation verdicts
130Current v2 source assigns HIGH to full curated/label/synonym matches; other scores are bucketed.
131These are ranking categories, not calibrated correctness probabilities. In the review's live
132`liver` query, the organ was HIGH while several narrower liver parts were GOOD.
134The v2 outer wrapper always uses `ZOOMA_INFERRED_FROM_CURATED`, including underlying
135`OLS_TEXT_TAGGER` and `OLS_EMBEDDING` matches. `flatten_hit()` reports evidence/source from
136`derivedFrom` and retains the full available provenance chain in JSON. Do not infer human
137curation from the outer evidence label. See the official
138[annotation adapter](https://github.com/EBISPOT/zooma2/blob/dev/backend/src/main/java/uk/ac/ebi/zooma2/api/v2/dto/V2AnnotationDto.java)
139and [confidence logic](https://github.com/EBISPOT/zooma2/blob/dev/backend/src/main/java/uk/ac/ebi/zooma2/api/v2/dto/V2ConfidenceLevel.java).
141`map_terms.py --high-confidence-only` retains HIGH/GOOD candidates. `--exact-only` is a legacy
142alias for that confidence filter, unrelated to OLS lexical exactness. Legacy output names
143`safe` and `zooma_safe` also mean only HIGH/GOOD. Always check each candidate's OLS term detail,
144definition, synonym scope, schema and sample context before writing the annotation.

Confidence and provenance are not curation verdicts

Current v2 source assigns HIGH to full curated/label/synonym matches; other scores are bucketed. These are ranking categories, not calibrated correctness probabilities. In the review's live liver query, the organ was HIGH while several narrower liver parts were GOOD.

The v2 outer wrapper always uses ZOOMA_INFERRED_FROM_CURATED, including underlying OLS_TEXT_TAGGER and OLS_EMBEDDING matches. flatten_hit() reports evidence/source from derivedFrom and retains the full available provenance chain in JSON. Do not infer human curation from the outer evidence label. See the official annotation adapter and confidence logic.

map_terms.py --high-confidence-only retains HIGH/GOOD candidates. --exact-only is a legacy alias for that confidence filter, unrelated to OLS lexical exactness. Legacy output names safe and zooma_safe also mean only HIGH/GOOD. Always check each candidate's OLS term detail, definition, synonym scope, schema and sample context before writing the annotation.

Raw lines 128 to 145
146## Ontobee
148Ontobee is the default linked-data server for most OBO Foundry ontologies.
149It serves an HTML page and RDF for a term IRI. It is not a resolver and it
150has no JSON search API.
152Term page:
154```
155https://ontobee.org/ontology/{PREFIX}?iri={url-encoded IRI}
156```
158`lookup_prefix.py` builds this only when the registry record has
159`mappings.ontobee`, using that value (not `preferred_prefix`) plus
160`uri_format`. Example: `HP:0001250` →
161`https://ontobee.org/ontology/HP?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FHP_0001250`.
162Orphanet has no `mappings.ontobee` — the templated `ORPHA` / `ORDO` page is
163HTTP 500 — so that cell stays empty.
165HTML keyword search (`/search?ontology=UBERON&keywords=liver`) is a browser
166page, not an API — do not scrape it. For text → ID use OLS (or ZOOMA for
167shorthand). For ID → verdict use OLS.
169SPARQL is available at the Hegroup endpoint documented on
170https://ontobee.org/tutorial/sparql, for axiom queries OLS does not expose.
171The graph URI pattern (`http://purl.obolibrary.org/obo/merged/FOO`) is not
172reliable; do not put SPARQL in a routine resolve/validate path.

Ontobee

Ontobee is the default linked-data server for most OBO Foundry ontologies. It serves an HTML page and RDF for a term IRI. It is not a resolver and it has no JSON search API.

Term page:

https://ontobee.org/ontology/{PREFIX}?iri={url-encoded IRI}

lookup_prefix.py builds this only when the registry record has mappings.ontobee, using that value (not preferred_prefix) plus uri_format. Example: HP:0001250 → https://ontobee.org/ontology/HP?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FHP_0001250. Orphanet has no mappings.ontobee — the templated ORPHA / ORDO page is HTTP 500 — so that cell stays empty.

HTML keyword search (/search?ontology=UBERON&keywords=liver) is a browser page, not an API — do not scrape it. For text → ID use OLS (or ZOOMA for shorthand). For ID → verdict use OLS.

SPARQL is available at the Hegroup endpoint documented on https://ontobee.org/tutorial/sparql, for axiom queries OLS does not expose. The graph URI pattern (http://purl.obolibrary.org/obo/merged/FOO) is not reliable; do not put SPARQL in a routine resolve/validate path.

Raw lines 146 to 173
174## Which call to make
1761. Prefix looks wrong, or you need a landing page → `lookup_prefix.py`.
1772. Free text, expected ontology known → `resolve_terms.py` (OLS).
1783. OLS returned unresolved / partial on lab shorthand → `map_terms.py`, then
179 `validate_terms.py` on every CURIE you might keep.
1804. You already have a CURIE → `validate_terms.py` (OLS). Optionally
181 `lookup_prefix.py` first if the prefix itself might be a synonym.
1825. You want the OBO Foundry page for a known IRI → the Ontobee URL from
183 `lookup_prefix.py`, not a new search.

Which call to make

  1. Prefix looks wrong, or you need a landing page → lookup_prefix.py.
  2. Free text, expected ontology known → resolve_terms.py (OLS).
  3. OLS returned unresolved / partial on lab shorthand → map_terms.py, then validate_terms.py on every CURIE you might keep.
  4. You already have a CURIE → validate_terms.py (OLS). Optionally lookup_prefix.py first if the prefix itself might be a synonym.
  5. You want the OBO Foundry page for a known IRI → the Ontobee URL from lookup_prefix.py, not a new search.
Raw lines 174 to 183