The Materials Project API, explained from zero
What an API actually is, what an endpoint is, and how to pull real materials data with Python, assuming you know nothing going in.
The Materials Project is a free public database holding computed properties for more than 150,000 materials: band gaps, densities, elastic moduli, formation energies. If you work anywhere near materials and data, you will eventually need to pull from it with code. This is the tutorial I wish I had when I started, written for someone who has never touched an API.
What an API actually is
API stands for Application Programming Interface, which explains nothing. Here is what it means in practice: it is a way for your program to ask another computer for data and get an answer back.
The Materials Project runs a server, a computer that stores all those material records. Your Python script is the client. The client sends a request, the server sends back a response. That is the entire relationship. You are not scraping a website or downloading a file; you are asking a specific question and getting a specific answer.
What an endpoint is
An endpoint is one specific URL that returns one specific kind of data. Think of a large office building: the building is the API, and each labelled door, Accounting, HR, IT, is an endpoint. You pick the door based on what you need.
Some real Materials Project endpoints:
https://api.materialsproject.org/materials/summary/ <- general properties
https://api.materialsproject.org/materials/thermo/ <- stability data
https://api.materialsproject.org/materials/elasticity/ <- elastic tensors
https://api.materialsproject.org/materials/synthesis/ <- synthesis recipes
Every one is a different door into the same building. For most work, summary is the door you want.
Setup: your key
An API key is a private password identifying you to the server. Get one free: log in at materialsproject.org and find your key in the account dashboard. Two rules: never paste it directly into code you might share, and never commit it to GitHub. Store it as an environment variable instead:
# Windows PowerShell
$env:MP_API_KEY="your_key_here"
# Mac/Linux
export MP_API_KEY="your_key_here"
The request, by hand
You could use the official client library (more on that below), but doing one request manually first teaches you what is actually happening. You need Python’s requests library (pip install requests) and three pieces: a URL, headers, and error handling.
import os
import requests
# 1. The URL: the endpoint, plus query parameters after the "?"
# material_ids says WHICH material, _fields says WHICH properties
url = (
"https://api.materialsproject.org/materials/summary/"
"?material_ids=mp-149"
"&_fields=material_id,formula_pretty,band_gap,density"
)
# 2. The headers: extra information attached to the request
headers = {
"X-API-KEY": os.environ.get("MP_API_KEY"), # your identity
"Accept": "application/json", # answer format
"User-Agent": "Mozilla/5.0", # see the gotcha below
}
# 3. The request itself, with a timeout so it cannot hang forever
response = requests.get(url, headers=headers, timeout=25)
data = response.json() # the reply, converted from JSON text to a Python dict
print(data["data"][0]["formula_pretty"]) # Si
print(data["data"][0]["band_gap"]) # 0.61 (hold that thought)
mp-149 is a material ID, the unique name the database gives every entry; mp-149 is silicon. The reply comes back as JSON, which converts directly into Python dictionaries and lists, and you navigate into it with keys: data["data"][0] means “the first result in the list called data.”
The gotcha nobody documents: the Materials Project sits behind Cloudflare, which blocks Python’s default identification string with a 403 error. That is why the User-Agent header is there. Without it, your first request fails and the error message will not tell you why. I lost an evening to this one.
The same request, the official way
The mp-api library wraps all of the above in a friendlier interface:
from mp_api.client import MPRester # pip install mp-api
with MPRester(os.environ.get("MP_API_KEY")) as mpr:
docs = mpr.materials.summary.search(
material_ids=["mp-149"],
fields=["material_id", "formula_pretty", "band_gap", "density"],
)
print(docs[0].band_gap)
Same server, same data. The library already knows the endpoint URLs, handles the Cloudflare issue, and hands you objects (docs[0].band_gap) instead of nested dictionaries. The with block just guarantees the connection closes properly when you are done.
Which should you use? Learn with requests so you understand the machinery; build with MPRester so you benefit from everyone else’s debugging. Knowing both means that when something breaks, you can reason about what is underneath.
Searching, not just looking up
The real power is querying by properties rather than by ID:
with MPRester(os.environ.get("MP_API_KEY")) as mpr:
docs = mpr.materials.summary.search(
elements=["Si", "O"], # must contain silicon and oxygen
band_gap=(1.0, 3.0), # gap between 1 and 3 eV
is_stable=True, # only thermodynamically stable entries
fields=["material_id", "formula_pretty", "band_gap"],
)
print(len(docs), "materials found")
Three habits that make you a polite and efficient user: always restrict fields to what you need, batch many material IDs into one call rather than looping, and while testing, add num_chunks=1, chunk_size=10 so a mistyped query returns ten results instead of ten thousand.
The part most tutorials skip: should you trust the numbers?
Notice silicon’s band gap came back as 0.61 eV. The experimental value, measured countless times, is 1.12 eV. That is not a bug in your code or the database: the values are computed with DFT, a simulation method whose standard flavour systematically underestimates band gaps, sometimes by half, and for some oxides reports semiconductors as metals outright.
Three rules for using what you fetch:
- A property belongs to an entry, not a formula. One formula can have a dozen entries (polymorphs) with wildly different properties. Always note the
material_idyou used. - Prefer the stable entry (
is_stable=True, or the lowestenergy_above_hull) unless you have a reason not to. - Treat computed band gaps as lower bounds, and check against experimental values for anything that matters.
The database is an extraordinary resource. It is also computed, not measured, and knowing the difference is what separates using it from being used by it.
That last problem, knowing which fetched values deserve trust, matters enough that I built an agent that audits Materials Project records automatically. But the manual habits above will carry you a long way.
Found a mistake? Good, tell me. This publication flags its own suspect values. Reach me on LinkedIn.