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:
| Endpoint | Returns | Example for BRCA1 |
|---|---|---|
/lookup/id/:id | metadata — name, biotype, coordinates, canonical transcript | lookup/id/ENSG00000012048 |
/sequence/id/:id | the actual sequence, FASTA or JSON | sequence/id/ENST00000357654 |
/xrefs/id/:id | cross-references to other databases | xrefs/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 againX-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:
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-utilities | Ensembl REST | |
|---|---|---|
| Identity | API key (optional) | none required |
| Default limit | 3 req/s | 55,000 req/hr (~15 req/s) |
| With a key | 10 req/s | not applicable — no key tier |
| Enforcement | caps your rate up front | lets you burst, then signals back-pressure |
| Over-limit response | requests may simply fail or get delayed | explicit 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?