From Clicking to Scripting

From Clicking to Scripting · Subtopic 4 of 7 · 12 min

The Ensembl REST API

By the end of this lesson you will be able to pull BRCA1's gene record, sequence, and cross-references straight from Ensembl's REST API, and build a small Python client that backs off on its own when it hits the shared rate limit instead of getting blocked.

No key, but a quota

The previous lesson showed ESearch and EFetch — NCBI's E-utilities, which want an API key, meaning a registered string you attach to every request to prove who you are and unlock a faster tier. Ensembl's REST API skips that step entirely. There is no signup, no key, no header to attach. You can GET a URL from a fresh terminal and get JSON back immediately.

That openness comes with a different kind of guardrail: a quota, meaning a hard cap on how much of the shared service any one client may use in a window of time. Ensembl's is 55,000 requests/hour per client, which works out to roughly 15 req/s if you spread it evenly — generous for exploring a handful of genes, tight if you loop over every gene in the genome without pacing yourself.

Three endpoints worth knowing

Ensembl's REST API mirrors the same gene → transcript → protein model from Ensembl and the Genome Browser. Three endpoints cover most of what you will reach for:

EndpointReturnsExample for BRCA1
/lookup/id/:idmetadata — name, biotype, coordinates, canonical transcriptlookup/id/ENSG00000012048
/sequence/id/:idthe actual sequence, FASTA or JSONsequence/id/ENST00000357654
/xrefs/id/:idcross-references to other databasesxrefs/id/ENSG00000012048

That last one is worth dwelling on. xrefs/id is Ensembl's programmatic version of the db_xref links you met in db_xref: How Records Point at Each Other — call it on ENSG00000012048 and, among the results, you get back the HGNC symbol BRCA1, the UniProt accession P38398, and RefSeq's NM_007294. One HTTP call reconstructs a chunk of the accession thread you have been following since the start of this course.

What crossing the line looks like

Send requests too fast and Ensembl does not silently drop them — it tells you exactly what happened. The response comes back with HTTP status 429 Too Many Requests, plus two headers worth reading:

  • Retry-After — how many seconds to wait before trying again
  • X-RateLimit-Remaining: 0 — confirming the quota, not a bug, caused the rejection

That Retry-After header is the whole point. It is not a suggestion buried in documentation; it is a number the server hands you in the response itself, telling your script precisely how long to back off.

Every successful Ensembl response also carries X-RateLimit-Remaining, counting down from your hourly allowance. Check it after a batch of calls and you can slow down before you ever see a 429, rather than reacting to one after the fact.

Building a self-throttling client

A well-behaved client reads Retry-After and sleeps for exactly that long, then retries. Here it is wrapped around all three endpoints:

python
import time
import requests

SERVER = "https://rest.ensembl.org"


def ensembl_get(path, params=None):
    params = dict(params or {})
    params["content-type"] = "application/json"
    while True:
        r = requests.get(SERVER + path, params=params)
        if r.status_code == 429:
            wait = int(r.headers.get("Retry-After", 1))
            print(f"Rate limited — sleeping {wait}s")
            time.sleep(wait)
            continue
        r.raise_for_status()
        return r.json()


gene = ensembl_get("/lookup/id/ENSG00000012048")
print(gene["display_name"], gene["biotype"])          # BRCA1 protein_coding

seq = ensembl_get("/sequence/id/ENST00000357654")
print(len(seq["seq"]), "bp transcript sequence")

xrefs = ensembl_get("/xrefs/id/ENSG00000012048")
uniprot_hits = [x["primary_id"] for x in xrefs if x.get("dbname") == "Uniprot/SWISSPROT"]
print(uniprot_hits)                                    # ['P38398']

Nothing here calls time.sleep on a fixed schedule. The client only pauses when the server tells it to, then resumes at full speed — the loop retries the exact same call rather than skipping it.

Two philosophies for the same problem

NCBI and Ensembl both throttle, but they enforce it in opposite ways:

NCBI E-utilitiesEnsembl REST
IdentityAPI key (optional)none required
Default limit3 req/s55,000 req/hr (~15 req/s)
With a key10 req/snot applicable — no key tier
Enforcementcaps your rate up frontlets you burst, then signals back-pressure
Over-limit responserequests may simply fail or get delayedexplicit 429 with Retry-After

NCBI's model is a speed limit set before you start driving. Ensembl's is cooperative: it trusts you to read the X-RateLimit-Remaining header and the 429 response, and to slow down when told. Both amount to the same courtesy — treating a free public API as a shared resource rather than your own server — but Ensembl's version puts the negotiation directly in the HTTP response instead of a key you registered weeks earlier.

That difference in philosophy is worth carrying forward: whichever REST API you script against next, the underlying question is always the same — does it hand you a number and a header, or does it simply expect you to already know the rules?