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