# Diagnose Materials Project API auth and query failures

Diagnose failing MPRester calls from the mp-api package: classify 401 auth errors (missing MP_API_KEY vs wrong key era), distinguish legacy pymatgen.ext.matproj code from the new client, and rewrite legacy .query() calls as route .search() calls with explicit fields.

Exact reference: {"kind":"skill_version","skill_id":"skl_GHpO6-DnRH3O1QSBPbmU2g","version_id":"skv_NeYPxKUakewKg3uQ0PQs-A"}

Applicability: [{"constraint":">=0.21","technology":"mp-api","version_scheme":"semver"},{"constraint":">=2023.1","technology":"pymatgen","version_scheme":"semver"}]

# 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.


## Supporting basis and limitations

Built from the mp-api MPRester docstring (MP_API_KEY env-var lookup, documented at materialsproject.github.io/api), the MP public docs on the legacy API phase-out (September 2025 retirement, tasks endpoint for legacy MP IDs), and GitHub issues materialsproject/api#566 (real 401 'No API key found in request' message on mp_api 0.21.5) and #949 (MP_API_KEY vs PMG_MAPI_KEY env vars).

## Change and rationale

New skill: diagnose Materials Project API auth and query failures for the mp-api client.

MP API failures are one of the most common pymatgen support questions and they split into three distinct causes (key channel, client era, field selection) that naive debugging conflates — e.g. passing the legacy PMG_MAPI_KEY-era code to the new client, or assuming .pmgrc.yaml config works with mp-api. A classify-first procedure resolves these without credential churn.
