Summary (Co-Authored by Claude code)
The dashboard never sends an API key/auth header on any of its data requests to the
backend. Once OM_API_KEY is set — which in practice is required, since the server
rejects protected endpoints entirely when no key is configured at all (separate
issue, filing that too) — every single data-fetching page in the dashboard breaks:
Memories, Timeline/Activity, Decay, dashboard stats, etc. all show generic
"failed to fetch ..." errors, because the backend correctly 401s requests with no
credentials.
Root Cause
dashboard/lib/api.ts:
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api/openmemory'
export const getHeaders = (): { 'Content-Type': string } => {
return {
'Content-Type': 'application/json',
}
}
getHeaders() is used by every page (memories/page.tsx, timeline/page.tsx,
decay/page.tsx, etc.) as the headers object for fetch() calls to the backend.
It never includes an Authorization or x-api-key header.
docker-compose.yml defines a NEXT_PUBLIC_API_KEY build arg and env var for the
dashboard service — presumably intended for exactly this purpose — but it's dead:
$ grep -rn "NEXT_PUBLIC_API_KEY" dashboard/ --include="*.ts" --include="*.tsx"
(no output — not referenced anywhere in dashboard source)
So there's a build arg wired all the way through docker-compose.yml →
Dockerfile → the Next.js build, that gets baked into the client bundle... and then
is never actually read by any client code. getHeaders() should be sending it but
doesn't.
Separately: when NEXT_PUBLIC_API_URL isn't set, API_BASE_URL falls back to
/api/openmemory, which routes through dashboard/app/api/openmemory/[...path]/route.ts.
That proxy route reads a different env var (OPENMEMORY_API_URL, default
http://127.0.0.1:9432 — a port that matches neither the documented default
(8080) nor anything else in the repo) and does correctly attach the key server-side
via OPENMEMORY_API_KEY/OM_API_KEY — but docker-compose.yml never sets
OPENMEMORY_API_URL for the dashboard service either, so that path is also broken
out of the box unless a user manually adds it.
Reproducer
- Set a real
OM_API_KEY (as recommended/required — see linked auth issue).
docker compose --profile ui up per the repo's own compose file.
- Open dashboard → Memories (or Timeline, or the main dashboard stats view).
- Browser network tab: request to
/memory/all (or wherever NEXT_PUBLIC_API_URL
points) has no Authorization/x-api-key header.
- Backend responds
401 {"error":"authentication_required","message":"API key required"}.
- UI shows a generic "failed to fetch memories" / "failed to fetch activity" error
with no indication it's an auth problem.
Suggested Fix
- Make
getHeaders() actually read and send NEXT_PUBLIC_API_KEY (e.g. as
Authorization: Bearer ... or x-api-key, matching whatever the backend expects).
- Wire
OPENMEMORY_API_URL (pointing at the openmemory service's internal address,
e.g. http://openmemory:8080 in the compose network) into docker-compose.yml for
the dashboard service, so the /api/openmemory proxy path works without manual
config too.
- Fix the proxy route's fallback default (
127.0.0.1:9432) to match the documented
default port (8080), or remove the fallback and fail loudly instead of silently
pointing somewhere wrong.
Happy to open a PR if a preferred direction (client-side header vs. server-side
proxy-only) is confirmed — right now there are two half-wired paths and neither
works unedited.
Related docs mismatch
.env.example says of OM_API_KEY: "Leave empty to disable authentication
(development only)". In practice, leaving it empty doesn't disable auth — the server
returns {"error":"auth_not_configured","message":"Server has no OM_API_KEY configured. Protected endpoints are unavailable."} on protected endpoints instead,
which is a different failure mode than "auth disabled." A real key is required
either way, so the docs comment is misleading as written.
Environment
openmemory-js / dashboard, current main
- Docker deployment via the repo's own
docker-compose.yml --profile ui
- Confirmed via direct
curl against the backend (with vs. without the key) that
the backend's auth behavior itself is correct — this is purely a dashboard-side
gap.
Summary (Co-Authored by Claude code)
The dashboard never sends an API key/auth header on any of its data requests to the
backend. Once
OM_API_KEYis set — which in practice is required, since the serverrejects protected endpoints entirely when no key is configured at all (separate
issue, filing that too) — every single data-fetching page in the dashboard breaks:
Memories, Timeline/Activity, Decay, dashboard stats, etc. all show generic
"failed to fetch ..." errors, because the backend correctly 401s requests with no
credentials.
Root Cause
dashboard/lib/api.ts:getHeaders()is used by every page (memories/page.tsx,timeline/page.tsx,decay/page.tsx, etc.) as the headers object forfetch()calls to the backend.It never includes an
Authorizationorx-api-keyheader.docker-compose.ymldefines aNEXT_PUBLIC_API_KEYbuild arg and env var for thedashboard service — presumably intended for exactly this purpose — but it's dead:
So there's a build arg wired all the way through
docker-compose.yml→Dockerfile→ the Next.js build, that gets baked into the client bundle... and thenis never actually read by any client code.
getHeaders()should be sending it butdoesn't.
Separately: when
NEXT_PUBLIC_API_URLisn't set,API_BASE_URLfalls back to/api/openmemory, which routes throughdashboard/app/api/openmemory/[...path]/route.ts.That proxy route reads a different env var (
OPENMEMORY_API_URL, defaulthttp://127.0.0.1:9432— a port that matches neither the documented default(
8080) nor anything else in the repo) and does correctly attach the key server-sidevia
OPENMEMORY_API_KEY/OM_API_KEY— butdocker-compose.ymlnever setsOPENMEMORY_API_URLfor the dashboard service either, so that path is also brokenout of the box unless a user manually adds it.
Reproducer
OM_API_KEY(as recommended/required — see linked auth issue).docker compose --profile ui upper the repo's own compose file./memory/all(or whereverNEXT_PUBLIC_API_URLpoints) has no
Authorization/x-api-keyheader.401 {"error":"authentication_required","message":"API key required"}.with no indication it's an auth problem.
Suggested Fix
getHeaders()actually read and sendNEXT_PUBLIC_API_KEY(e.g. asAuthorization: Bearer ...orx-api-key, matching whatever the backend expects).OPENMEMORY_API_URL(pointing at theopenmemoryservice's internal address,e.g.
http://openmemory:8080in the compose network) intodocker-compose.ymlforthe dashboard service, so the
/api/openmemoryproxy path works without manualconfig too.
127.0.0.1:9432) to match the documenteddefault port (
8080), or remove the fallback and fail loudly instead of silentlypointing somewhere wrong.
Happy to open a PR if a preferred direction (client-side header vs. server-side
proxy-only) is confirmed — right now there are two half-wired paths and neither
works unedited.
Related docs mismatch
.env.examplesays ofOM_API_KEY: "Leave empty to disable authentication(development only)". In practice, leaving it empty doesn't disable auth — the server
returns
{"error":"auth_not_configured","message":"Server has no OM_API_KEY configured. Protected endpoints are unavailable."}on protected endpoints instead,which is a different failure mode than "auth disabled." A real key is required
either way, so the docs comment is misleading as written.
Environment
openmemory-js/ dashboard, currentmaindocker-compose.yml --profile uicurlagainst the backend (with vs. without the key) thatthe backend's auth behavior itself is correct — this is purely a dashboard-side
gap.