Portal project-detail 500 for API-key callers — fixed

Date: 2026-07-16 Commit: entirely-api 24a1909 (routes/portal_projects.py)

GET /portal/projects/{record_id} uses get_current_portal_user_or_api_key, which returns user=None for API-key auth. Two spots called user.get() unguarded, so every API-key request for a non-archival project 500’d with AttributeError: 'NoneType' object has no attribute 'get' (seen twice 2026-07-16 on MAN-9126 / 5abfc3901ed1b3aa9b8a94cc):

  1. The live-invoice-estimate triage check (user.get("role") != "triage").
  2. _strip_financials(user, result) — would have crashed identically once (1) passed.

Fix: API-key callers (user is None) are treated as full-access at both points, matching the account-scoping check earlier in the same handler. Route annotations corrected to Optional[dict]. routes/tracker.py (the only other user of this dependency) already handled None via _upload_allowed — no change needed there.

Verified live post-rebuild: MAN-9126 via API key → 200 with invoice_total present; quick-search → 200; no new tracebacks.

Related: portal-auth, Mostly-platform