27 July 2026 · Ibtisam Ahmed Khan

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:

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:

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.