# Diagnose Materials Project API auth and query failures
Use this when an `MPRester` call from the `mp-api` package fails, returns
unexpected 401/403 errors, or when legacy `pymatgen.ext.matproj` code copied
from old tutorials no longer works. Most failures are one of three distinct
problems with different fixes: wrong key channel (legacy vs new), wrong query
API for the client version in use, or a field-selection problem.
## 1. Know which API era your code targets
There are two APIs and two incompatible clients:
- **New API** (current): `from mp_api.client import MPRester`, endpoint
`https://api.materialsproject.org`. The client reads its key from the
`MP_API_KEY` environment variable when constructed with no arguments.
Key obtained from the API page on next-gen.materialsproject.org.
- **Legacy API** (phased out, retired September 2025 per the MP public docs):
`from pymatgen.ext.matproj import MPRester`, env var `PMG_MAPI_KEY`, and a
`.query(criteria, properties)` method. `pymatgen.ext.matproj` was removed
from modern pymatgen. Old tutorial code using `.query(...)`,
`.get_entry_by_material_id(...)`, or `mpr.query(...)` will fail with
`AttributeError` on the new client — this is expected, not a bug.
Classify first:
- `MPRestError: REST query returned with error status code 401 ... "message":"No API key found in request"` — the key is missing from the
request entirely. The new rester never reads `.pmgrc.yaml`; it only reads
`MP_API_KEY` or an explicitly passed argument.
- 401/403 with a key present — wrong key for this API era (e.g. an old
16-character legacy key against the new endpoint, or a new key against the
legacy client). Regenerate from the current dashboard and use it with the
matching client.
- `AttributeError: 'MPRester' object has no attribute 'query'` — legacy
tutorial code against the new client. Rewrite the query (step 3), do not
downgrade the client.
## 2. Authenticate the supported way
The documented behavior of `MPRester.__init__`: if `api_key` is None, the
code checks for the `MP_API_KEY` setting (environment variable) and uses
that, so calling the constructor with no arguments is the intended pattern.
```python
from mp_api.client import MPRester
# Reads the key from the MP_API_KEY environment variable.
# Obtain the key from the API page on next-gen.materialsproject.org
# and store it in that variable before running.
with MPRester() as mpr:
docs = mpr.materials.summary.search(
material_ids=["mp-149"], fields=["material_id", "formula_pretty"]
)
print(docs[0].material_id, docs[0].formula_pretty)
```
Verification before blaming the network:
1. Confirm the variable is visible to the Python process (`MP_API_KEY`
exported in the same shell, not just set in an IDE run config).
2. Confirm the key matches the current one on the dashboard — keys are
rotated by account changes, and a stale key 401s exactly like a missing one.
3. Confirm you are importing from `mp_api` (check `mp_api.__version__`;
issue #566 confirmed the env-var lookup on mp_api 0.21.5) and not from a
stale `pymatgen.ext.matproj` import path.
## 3. Use the new query API, not legacy `.query()`
New queries go through route objects with `search(...)` and a `fields`
selector. The `fields` argument controls which attributes are returned and
transferred — omit it and you get a small default set, then wonder why an
attribute is missing.
```python
with MPRester() as mpr:
# summary.search with explicit fields: real method names, real kwargs
results = mpr.materials.summary.search(
chemsys="Li-Co-O",
fields=["material_id", "formula_pretty", "energy_above_hull",
"structure"],
)
# chemsys can also be a list of elements
entries = mpr.get_entries_in_chemsys(["Li", "Co", "O"])
```
Gotchas:
- **`get_entries_in_chemsys` accepts a dash-joined string or an element
list** — `"Na-Fe-Nb-O"` and `["Na", "Fe", "Nb", "O"]` both work. A single
element must still be a list, `["Cu"]`, not a bare string.
- **Search results are document models by default**
(`use_document_model=True`); access attributes (`doc.material_id`), not
dict keys. Pass `use_document_model=False` to the constructor for dicts.
- **`fields` typos are silent-ish failures** — an unrecognized field name
is ignored or errors depending on the route; verify a field exists in the
route docs before assuming a query bug.
- **Old MP IDs in the literature** (`mvc-` IDs, very old `mp-` IDs) may not
resolve through `summary.search` even with `deprecated=True`. The public
docs recommend the tasks endpoint for those:
`mpr.materials.tasks.search(task_ids=["mvc-13350"])`, each task carrying
its structure.
## 4. Rate limits and large pulls
The new API enforces request limits. For large downloads:
- Use the progress-muted, cached patterns the client ships with
(`mute_progress_bars=True`); do not hammer the REST endpoint with one
request per material in a loop when a single `search` with a chemsys or
formula filter covers it.
- `get_entries_in_chemsys` pages internally; for very large chemical systems
prefer `summary.search(chemsys=..., fields=[...])` with only the fields
you need.
- Repeated 429s or connection resets mid-pull mean you are over the limit —
shrink the query or add backoff; retrying harder at the same rate keeps
failing.
## Checklist for the failing call
1. Identify the client: `from mp_api.client import MPRester` (new) vs
`pymatgen.ext.matproj` (legacy/removed).
2. Read the status code and message: 401 "No API key found in request" means
the env var is not reaching the process; 401/403 with a key means wrong
key era; `AttributeError` on `.query` means legacy code on the new client.
3. Authenticate with the key from the current dashboard via the `MP_API_KEY`
environment variable and `MPRester()` with no arguments.
4. Rewrite legacy `.query()` calls as route `.search(...)` calls with
explicit `fields`; resolve legacy MP IDs through the tasks endpoint.