Skip to content

omnilab/gwas

Omnilab GWAS Catalog: a study by its accession (GCST000854), a variant by its rs id, a trait by its EFO, Mondo or HPO id - reported and mapped traits, samples, ancestries, platforms, functional class, location, mapped genes, studies - each with its strongest associations and the studies, variants and traits they tie it to as related things, a search of traits, and the associations paged with their p-values, effect sizes and risk alleles, through the NHGRI-EBI GWAS Catalog's REST API v2, no key needed.

Floor
Provider · Omnilab
Version
1.x
Conformance suite
not wired
Verified for real
Its README does not say
Licence
MIT
composer require omnilab/gwas:1.x-dev

The NHGRI-EBI GWAS Catalog's REST API v2 (https://www.ebi.ac.uk/gwas/rest/api/v2/) for omnisense/omnilab: studies (GCST000854), variants (rs334) and traits (EFO, Mondo, HPO, Orphanet) with their strongest associations - p-values, effect sizes, risk alleles - as entities, the studies, variants and traits they tie as related things; a search of traits; outside the contract, the associations paged. No key.

Installation

composer require omnilab/gwas
use Omnilab\Gwas\GwasSourceFactory;

$gwas = (new GwasSourceFactory($httpClient))->create([
    'mailto' => 'support@glitchr.io',  // optional
    'associations' => 10,              // optional: associations (or a trait's studies) an entity holds; 0: one call
]);

Credentials

None: the GWAS Catalog's REST API v2 takes no key and no account.

Options

The factory's options, with the defaults it applies (GwasSourceFactory):

Option Type Default Required Effect
associations int 10 no how many associations (for a trait, how many studies) an entity holds, the strongest first; 0: none, one call an entity
mailto ?string null no a contact address, sent in the User-Agent
user_agent ?string null no the whole User-Agent, in place of the one made from mailto
throttle float 0 no seconds left between two calls: the catalogue publishes no rate
base_uri string https://www.ebi.ac.uk/gwas/rest/api/v2/ no the API the source calls

Capabilities

capabilities() answers entity, search; an operation it does not answer throws NotSupportedException before any call.

Capability Supported
entity yes a study (GCST000854), a variant (rs334) or a trait (EFO_..., MONDO:..., HP:..., ORPHA:...); another base refused before any call; two calls with associations above 0
search yes the catalogue's traits by name (efo-traits?trait=), 100 a page at most; a study or a variant is not searched by text
series no
products no
occurrences no
datasets no
jobs no
open no
structure no
cross_references no none of the same thing: the studies, variants and traits an entity is tied to are its related (trait, variant, study, associated_trait)
forecast no
historical no
area no
station no
cloud_cover no

Outside the contract, on Omnilab\Gwas\GwasSource: associations(Identifier|string $id, int $limit = 25, ?string $cursor = null): Page - a study's, a variant's or a trait's associations, the smallest p-value first, as Omnilab\Gwas\Association objects (below).

The schemes

Thing Scheme Read from
a study gwas GCST000854, its page www.ebi.ac.uk/gwas/studies/
a variant dbsnp rs334, its dbSNP page
a trait efo, mondo, hp, orphanet EFO_0004321, MONDO:0011382... (the catalogue writes MONDO_0011382)

Any other base's identifier is refused before any call.

Which API

https://www.ebi.ac.uk/gwas/rest/api/v2/ (the v1 API answers 410 since its retirement: "This legacy GWAS Catalog REST API is no longer available"):

Method Calls
entity(), a study studies/GCST000854, then associations?accession_id=GCST000854&page=0&size=10&sort=p_value&direction=asc
entity(), a variant single-nucleotide-polymorphisms/rs334, then associations?rs_id=rs334&...
entity(), a trait efo-traits/MONDO_0011382, then studies?efo_id=MONDO_0011382&page=0&size=10
search() efo-traits?trait=...&page=...&size=...
associations() associations?{accession_id,rs_id,efo_id}=...&page=...&size=...&sort=p_value&direction=asc

With associations 0, an entity takes one call. Unknown is a 404: null. The catalogue's studies?disease_trait= and ?efo_trait= matched nothing for "sickle" or "sickle cell anemia" on 2026-10-15: a search goes through the traits (efo-traits?trait=), whose studies entity() gives.

What an entity holds

A study: id, label (its reported trait), kind GWAS study, url, and reported_trait, trait and background_trait (one each, mapped), initial_sample_size, replication_sample_size, discovery_ancestry, replication_ancestry, cohort, genotyping_technology, array_manufacturer (one each), platforms, snp_count, imputed, pooled, gxe, gxg, full_summary_stats_available, summary_statistics (its address when there is one), pubmed, associations (how many), association (the strongest, as text). related: trait, background_trait (EFO, Mondo, HPO), variant (the strongest associations' rs ids).

A variant: id (dbsnp:rs334), label, kind variant, url (its dbSNP page), and functional_class, most_severe_consequence, alleles (T/A/C/G (forward)), genomic_location (11:5227002), map_location, gene (one per mapped gene), merged, associations, association (labelled with its study: Association (GCST90475778), valued rs334-T (HBB): p = 1e-323, Hereditary hemolytic anemias (PheCode 282)). related: study, associated_trait.

A trait: id, label, kind trait, url (its page in the catalogue), studies (how many), study (one each: Sickle cell anemia (PheCode 282.5) (GCST90481652)). related: study.

A p-value is written as the catalogue keeps it, mantissa and exponent (1e-323): a float underflows past about 1e-308, and the catalogue holds smaller ones.

Outside the contract: associations()

associations($id, $limit, $cursor) gives a Page of Association objects, the smallest p-value first - id, study, variants, riskAlleles (rs334-T), pValue (a float), pValueText (mantissa and exponent), traits, reported, oddsRatio, beta (2.241 unit decrease), range (the confidence interval: [2.16-2.32]), riskFrequency, genes, locations, pubmed, firstAuthor - its next the next page's number, its total the catalogue's count (134 for rs334 on 2026-10-15).

The Collector: the sickle cell variant from ClinVar

ClinVar gives a variation's rs id; the catalogue reads it:

$collector->collect('clinvar', '15333', 1, ['gwas']);

Against the real services on 2026-10-15: three calls in 1.7 s, sources = clinvar, gwas; ClinVar's classification and the catalogue's associations side by side. An rs id names a site (rs334 holds four alleles): the variation and the site are joined as ClinVar names them.

What the API does not give here

The loci and genomic contexts of an association (associations/{id}/loci), a study's ancestries in detail, the summary statistics themselves (files), publications as entities, genes as the catalogue maps them (genes/{symbol}), and the background traits' own studies.

Example

<?php

require __DIR__.'/vendor/autoload.php';

use Omnilab\Gwas\GwasSourceFactory;
use Omnilab\Source\Query;
use Symfony\Component\HttpClient\HttpClient;

$gwas = (new GwasSourceFactory(HttpClient::create()))->create(['mailto' => 'you@example.org', 'associations' => 3]);

$variant = $gwas->entity('rs334');
echo $variant->label, ': ', $variant->value('most_severe_consequence'), ', ', $variant->value('genomic_location'), ', ', $variant->value('associations'), " associations; the strongest:\n";
foreach ($variant->properties('association') as $association) {
    echo '  ', $association, "\n";
}

$page = $gwas->associations('rs334', 2);
foreach ($page as $a) {
    printf("  %s in %s: p = %s, %s\n", implode(',', $a->riskAlleles), $a->study, $a->pValueText, $a->oddsRatio ?? $a->beta ?? '-');
}

foreach ($gwas->search(new Query('sickle', limit: 5)) as $trait) {
    echo 'trait ', $trait->id->display(), ' ', $trait->label, "\n";
}
rs334: missense variant, 11:5227002, 134 associations; the strongest:
  rs334-T (HBB): p = 1e-323, Hereditary hemolytic anemias (PheCode 282)
  rs334-T (HBB): p = 9e-131, hematocrit (maximum, inv-norm transformed)
  rs334-T (HBB): p = 3e-118, hematocrit (maximum, inv-norm transformed)
  rs334-T in gwas:GCST90475778: p = 1e-323, 2.241 unit decrease
  rs334-T in gwas:GCST90475337: p = 9e-131, 0.2578 unit increase
trait EFO:0700066 sickle cell disease and related diseases
trait MONDO:0011382 sickle cell disease

(as answered on 2026-10-27: four calls)

# config/packages/omnilab.yaml
omnilab:
    sources:
        gwas: { factory: gwas, options: { mailto: '%env(OMNILAB_MAILTO)%', associations: 10 } }

associations() is a method of GwasSource, not of the interface the bundle injects: check the instance before calling it.

Errors

Every call goes through HttpSource: a 404 or a 410 (and a 204) is null - an empty page for a list -; a 429 is a RateLimitedException (retryAfter from Retry-After); a 5xx, a time-out or a host that does not answer an UnavailableException; any other 4xx a ProviderException with its status and what the service said; an answer that is not JSON a ProviderException, a web page answered for JSON an UnavailableException.

Before any call: an identifier of another base given to entity() or associations() (NotSupportedException). An unknown study, variant or trait is the catalogue's 404, null; the retired v1 API answers 410 (null too, were base_uri pointed at it).

Verified, and not

Against the real service on 2026-10-15: the study GCST000854 (four associations, two traits), rs334 (134 associations, the smallest p-value 1e-323), sickle cell disease as a trait (eight studies), an rs id the catalogue does not hold (404), the search for "sickle" (two traits), rs334's associations in pages of five, the Collector's walk above. The fixtures in Tests/Fixtures/ are its answers. Each call took 1 to 2.7 s.

Again on 2026-10-27: rs334 and its three strongest associations, two pages of its associations, the search for "sickle" (two traits; the example above).

Not verified: an Orphanet id as a trait (the catalogue's traits met were EFO, Mondo and HPO terms); a rate limit (none published, none met).

Responsibility

This package is an independent, unofficial client: it is not affiliated with, endorsed by or sponsored by the NHGRI, the EMBL-EBI or the GWAS Catalog. The catalogue's answers name the EMBL-EBI's terms of use as their licence, and it asks to be cited (Cerezo M. et al., Nucleic Acids Res. 2025, 10.1093/nar/gkae1070), as the README's Terms of use says. Whoever runs the package is responsible for complying with them and for any use made of what it returns: see the README's Responsibility section.

Propose a change docs/index.md