Materials Project API: common mp-api errors and fixes
Fixes for the errors people hit on the Materials Project API: 403s, API keys, legacy vs new mp-api client, material_ids, and get_dos_by_material_id.
Most problems with the Materials Project API are not really bugs. They are small mismatches: the wrong client, a key from the wrong place, a singular where the code wants a plural. Here are the ones that come up again and again, and the fix for each. If you are completely new to the API, start with the Materials Project API tutorial first, then come back here when something breaks.
403 Forbidden on your first request
If a raw request returns a 403 Forbidden before you have done anything wrong, the cause is almost always Cloudflare, which sits in front of the Materials Project and blocks Python’s default user agent.
The fix is to send a normal-looking User-Agent header:
headers = {
"X-API-KEY": os.environ.get("MP_API_KEY"),
"Accept": "application/json",
"User-Agent": "Mozilla/5.0", # without this, Cloudflare returns 403
}
The official mp-api client already does this for you, which is one good reason to use it rather than hand-rolled requests for real work.
Invalid or missing API key (mp api key)
Two things go wrong here. First, the key has to come from the new dashboard: log in at materialsproject.org and copy the key from your account page. An older key from the retired legacy site will not authenticate against the current API. Second, do not paste the key into your code. Set it as an environment variable and read it back:
# Windows PowerShell
$env:MP_API_KEY="your_key_here"
# Mac / Linux
export MP_API_KEY="your_key_here"
import os
key = os.environ.get("MP_API_KEY") # never hard-code the key
A missing environment variable returns None, which then fails as an authentication error, so if the key looks “empty”, check the variable is actually set in the same shell you are running from.
Legacy API vs new mp-api (the biggest source of confusion)
This is the one that wastes the most time. There are two different MPRester classes:
- Legacy:
from pymatgen.ext.matproj import MPRester. This talked to the old REST API, which has been retired. Old tutorials and Stack Overflow answers use it, and their code no longer works. - New:
from mp_api.client import MPRester, installed withpip install mp-api. This is the current, supported client.
If you copied code that imports MPRester from pymatgen and it fails, switching to the new import is usually the whole fix:
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"],
)
material_ids, not material_id
The search methods take a list under the plural name material_ids, not a single material_id. Passing one bare string, or the singular keyword, is a common cause of empty results or a type error:
# wrong
docs = mpr.materials.summary.search(material_id="mp-149")
# right
docs = mpr.materials.summary.search(material_ids=["mp-149"])
get_dos_by_material_id returns a CompleteDos object
This one is not an error at all, which is exactly why it confuses people. The convenience method
with MPRester(os.environ.get("MP_API_KEY")) as mpr:
dos = mpr.get_dos_by_material_id("mp-149")
returns a pymatgen CompleteDos object (that is the “completedos” people search for). It is not a dictionary and not a plain number: it is a full density-of-states object you analyse or plot with pymatgen’s own tools:
from pymatgen.electronic_structure.plotter import DosPlotter
plotter = DosPlotter()
plotter.add_dos("Total DOS", dos)
plotter.add_dos_dict(dos.get_element_dos()) # per-element contributions
plotter.get_plot()
Two things to know. First, the sibling method get_bandstructure_by_material_id works the same way and returns a band-structure object, not raw numbers. Second, not every material has electronic-structure data computed, so for some IDs these methods return nothing. If you get an empty or None result, check that the material actually has DOS or band-structure data before assuming your code is broken.
Empty results or missing fields
If a query returns fewer results than expected, or a field comes back missing:
- Only request fields that exist. Restrict
fieldsto the properties you need, and make sure they belong to the endpoint you are querying (summary,thermo,elasticity, and so on are different doors). - A property belongs to an entry, not a formula. One formula can have several entries (polymorphs). Filter with
is_stable=Trueor the lowestenergy_above_hullwhen you want the representative one. - Test small. Add
num_chunks=1, chunk_size=10while developing so a mistyped query returns ten rows, not ten thousand.
And the error that is not in your code
The subtlest “error” is trusting a value that is computed, not measured. Silicon’s band gap comes back around 0.61 eV against an experimental 1.12 eV, because standard DFT underestimates gaps. That is not a bug to fix, it is a property of the data. Which values to trust is a real question, enough that I built an agent that audits Materials Project records and wrote about why this data needs auditing at all.
For the full walk-through from nothing, see the Materials Project API tutorial.
Found a mistake? Good, tell me. This publication flags its own suspect values. Reach me on LinkedIn.