Creating an agent fails with 401 "Invalid API key or token" on a fresh self-hosted (local mode) install
Just came across this project and wanted to check it out, but had an issue actually creating an agent when running locally. Fresh self-hosted instance, default local mode, and clicking "Create Agent" in the dashboard returns 401 "Invalid API key or token". It turns out the same thing happens for basically every project-scoped action in the dashboard (listing/adding secrets, etc.), so the self-hosted UI is mostly unusable. See the affected-endpoints list below.
Version: 1.35.0 (also reproduces on a clean DB).
Repro
Fresh install, nothing custom:
git clone https://github.com/onecli/onecli
cd onecli
docker compose -f docker/docker-compose.yml up -d
No .env, no NEXTAUTH_SECRET, so it comes up in local mode. The container's /app/data/runtime-config.json is {"authMode":"local","oauthConfigured":false}, and GET /v1/auth/session returns the local admin fine:
curl -s http://localhost:10254/v1/auth/session
# {"id":"...","email":"admin@localhost","name":"Admin","projectId":"<PID>","organizationId":"..."}
But creating an agent (what the dashboard POSTs) 401s:
curl -s -w '\n%{http_code}\n' -X POST http://localhost:10254/v1/agents \
-H 'Content-Type: application/json' \
--data-raw '{"name":"test","identifier":"test"}'
# {"error":{"message":"Invalid API key or token.","type":"authentication_error"}}
# 401
Add the x-project-id header (value from /v1/auth/session above) and the exact same request works:
curl -s -w '\n%{http_code}\n' -X POST http://localhost:10254/v1/agents \
-H 'Content-Type: application/json' \
-H 'x-project-id: <PID>' \
--data-raw '{"name":"test","identifier":"test"}'
# {"id":"...","name":"test","identifier":"test","createdAt":"..."}
# 201
So the only difference between 401 and 201 is the x-project-id header, which the dashboard never sends in local mode.
Affected endpoints
This is not specific to creating agents. Every route that goes through authMiddleware 401s the same way without x-project-id. Confirmed by hand:
POST /v1/agents (create agent)
GET /v1/agents (list agents)
GET /v1/secrets (list secrets / the LLM connections page)
POST /v1/secrets (add a credential)
Looking at packages/api/src/app.ts, the only routes that do NOT use authMiddleware are /v1/health and /v1/auth/session. Everything else does: agents, secrets, rules, user, apps, gateway, gateway-url, container-config, counts, skill, credential-stubs. So in practice the whole project-scoped dashboard is unusable in local mode, not just agent creation. I only reproduced the four above by hand, but the rest go through the same middleware, so there are likely more.
Cause
The project-scoped routes require x-project-id, but the self-hosted middleware never sets it.
-
packages/api/src/middleware/auth/resolve.ts:40-41 returns null when the header is absent:
const headerProjectId = request.headers.get("x-project-id");
if (!headerProjectId) return null;
-
packages/api/src/middleware/auth/session.ts:26 then bails, so authMiddleware returns 401:
if (!projectId && requireProject) return null;
-
apps/web/src/proxy.ts is what would inject the header, but it only does so for cloud. The non-cloud branch returns early before the injection block:
// proxy.ts
if (!IS_CLOUD) {
// ...scope-strip + redirect...
return NextResponse.next(); // returns here, no x-project-id set
}
// header injection (requestHeaders.set("x-project-id", projectId)) only runs below, cloud-only
The matcher also excludes v1 (apps/web/src/proxy.ts:82), so the API requests are not rewritten by middleware at all.
Net: in local/self-hosted mode nothing supplies x-project-id, but authMiddleware requires it for project-scoped routes, so the dashboard can create nothing.
Notes
GET /v1/auth/session works because it does not go through authMiddleware (it calls the session provider directly), which is why login looks fine but every project-scoped action fails. That makes it look like an auth/login problem when it is actually a missing header.
- The README does not mention the
.env file at all, so the first guess is that .env/AUTH_MODE setup is the problem. It is not: the entrypoint already derives authMode: "local" when NEXTAUTH_SECRET is empty, with or without an .env. Worth a line in the README either way.
- Also, changing Agents in the "Getting Started" model doesn't appear to do anything currently, if you switch to "Autonomous Agents" and then close the modal, next time you open it, it has returned to "Coding Agents".
Workaround
Until fixed, project-scoped calls work if you pass x-project-id from /v1/auth/session. Not usable from the dashboard UI without an extension that injects the header.
Suggested fix
In local mode, resolve the user's default project when x-project-id is absent (the local admin has exactly one), rather than returning null. Either in resolveProjectId (fall back to the default project for the local user) or by having the self-hosted path in proxy.ts inject x-project-id the way the cloud path does.
Creating an agent fails with 401 "Invalid API key or token" on a fresh self-hosted (local mode) install
Just came across this project and wanted to check it out, but had an issue actually creating an agent when running locally. Fresh self-hosted instance, default local mode, and clicking "Create Agent" in the dashboard returns 401 "Invalid API key or token". It turns out the same thing happens for basically every project-scoped action in the dashboard (listing/adding secrets, etc.), so the self-hosted UI is mostly unusable. See the affected-endpoints list below.
Version: 1.35.0 (also reproduces on a clean DB).
Repro
Fresh install, nothing custom:
git clone https://github.com/onecli/onecli cd onecli docker compose -f docker/docker-compose.yml up -dNo
.env, noNEXTAUTH_SECRET, so it comes up in local mode. The container's/app/data/runtime-config.jsonis{"authMode":"local","oauthConfigured":false}, andGET /v1/auth/sessionreturns the local admin fine:curl -s http://localhost:10254/v1/auth/session # {"id":"...","email":"admin@localhost","name":"Admin","projectId":"<PID>","organizationId":"..."}But creating an agent (what the dashboard POSTs) 401s:
Add the
x-project-idheader (value from/v1/auth/sessionabove) and the exact same request works:So the only difference between 401 and 201 is the
x-project-idheader, which the dashboard never sends in local mode.Affected endpoints
This is not specific to creating agents. Every route that goes through
authMiddleware401s the same way withoutx-project-id. Confirmed by hand:POST /v1/agents(create agent)GET /v1/agents(list agents)GET /v1/secrets(list secrets / the LLM connections page)POST /v1/secrets(add a credential)Looking at
packages/api/src/app.ts, the only routes that do NOT useauthMiddlewareare/v1/healthand/v1/auth/session. Everything else does:agents,secrets,rules,user,apps,gateway,gateway-url,container-config,counts,skill,credential-stubs. So in practice the whole project-scoped dashboard is unusable in local mode, not just agent creation. I only reproduced the four above by hand, but the rest go through the same middleware, so there are likely more.Cause
The project-scoped routes require
x-project-id, but the self-hosted middleware never sets it.packages/api/src/middleware/auth/resolve.ts:40-41returns null when the header is absent:packages/api/src/middleware/auth/session.ts:26then bails, soauthMiddlewarereturns 401:apps/web/src/proxy.tsis what would inject the header, but it only does so for cloud. The non-cloud branch returns early before the injection block:The matcher also excludes
v1(apps/web/src/proxy.ts:82), so the API requests are not rewritten by middleware at all.Net: in local/self-hosted mode nothing supplies
x-project-id, butauthMiddlewarerequires it for project-scoped routes, so the dashboard can create nothing.Notes
GET /v1/auth/sessionworks because it does not go throughauthMiddleware(it calls the session provider directly), which is why login looks fine but every project-scoped action fails. That makes it look like an auth/login problem when it is actually a missing header..envfile at all, so the first guess is that.env/AUTH_MODEsetup is the problem. It is not: the entrypoint already derivesauthMode: "local"whenNEXTAUTH_SECRETis empty, with or without an.env. Worth a line in the README either way.Workaround
Until fixed, project-scoped calls work if you pass
x-project-idfrom/v1/auth/session. Not usable from the dashboard UI without an extension that injects the header.Suggested fix
In local mode, resolve the user's default project when
x-project-idis absent (the local admin has exactly one), rather than returning null. Either inresolveProjectId(fall back to the default project for the local user) or by having the self-hosted path inproxy.tsinjectx-project-idthe way the cloud path does.