Description
Management routes (/api/threads/*, /api/user/*, /api/auth/*, /api/internal/*, etc.) fail with authentication errors (401 Unauthorized or 402 Out of Credits) when proxied through CLIProxyAPI to ampcode.com.
The root cause is that all routes—both provider routes AND management routes—share the same ReverseProxy instance created by createReverseProxy(), which unconditionally:
- Strips the client's
Authorization header
- Injects the
upstream-api-key from config
This works correctly for provider routes (LLM API calls like /api/provider/anthropic/v1/messages) because those routes authenticate using the Amp subscription token.
However, management routes require a user-specific Amp session token for identity-based operations (reading threads, user info, etc.). The current implementation strips this token and replaces it with the provider subscription key, which lacks the necessary permissions.
Steps to Reproduce
- Configure CLIProxyAPI with a valid
ampcode.upstream-api-key
- Connect Amp CLI to CLIProxyAPI (e.g.,
http://localhost:1337)
- Use a feature that calls management routes, such as:
read_thread tool
find_thread tool
- Web search (which may call management APIs internally)
- Observe 401 or 402 error responses
Expected Behavior
Management routes should successfully proxy to ampcode.com with appropriate authentication that allows user-identity operations.
Actual Behavior
Management routes return authentication errors:
{"error":{"code":402,"message":"Out of credits"}}
{"error":"unauthorized"}
Root Cause Analysis
File: internal/api/modules/amp/proxy.go (lines 40-65)
proxy.Director = func(req *http.Request) {
originalDirector(req)
req.Host = parsed.Host
// Remove client's Authorization header
req.Header.Del("Authorization") // <-- PROBLEM: Strips user's Amp session token
req.Header.Del("X-Api-Key")
// Inject API key from secret source
if key, err := secretSource.Get(req.Context()); err == nil && key != "" {
req.Header.Set("X-Api-Key", key)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", key)) // <-- Wrong token type for management routes
}
}
File: internal/api/modules/amp/routes.go (lines 117-134)
proxyHandler := func(c *gin.Context) {
proxy := m.getProxy() // <-- Uses same proxy for ALL routes
proxy.ServeHTTP(c.Writer, c.Request)
}
// Management routes use provider proxy
ampAPI.Any("/threads/*path", proxyHandler) // Uses provider proxy!
ampAPI.Any("/user/*path", proxyHandler) // Uses provider proxy!
ampAPI.Any("/auth/*path", proxyHandler) // Uses provider proxy!
Affected Routes
| Route Pattern |
Purpose |
Status |
/api/threads/* |
Read/list threads |
❌ Fails |
/api/user/* |
User info/preferences |
❌ Fails |
/api/auth/* |
OAuth flow |
❌ Fails |
/api/internal/* |
Internal APIs |
❌ Fails |
/api/telemetry/* |
Telemetry |
❌ Fails |
/api/provider/* |
LLM API calls |
✅ Works |
Proposed Solution
Create a separate createManagementProxy() that preserves the client's Authorization header instead of replacing it:
func createManagementProxy(upstreamURL string) (*httputil.ReverseProxy, error) {
parsed, err := url.Parse(upstreamURL)
if err != nil {
return nil, fmt.Errorf("invalid amp upstream url: %w", err)
}
proxy := httputil.NewSingleHostReverseProxy(parsed)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
req.Host = parsed.Host
// PRESERVE client's Authorization header - it contains their Amp session token
// Do NOT strip or replace it
}
return proxy, nil
}
Then use this proxy for management routes in registerManagementRoutes().
Environment
- CLIProxyAPI version: v6.x
- Go version: 1.21+
- Upstream: ampcode.com
Description
Management routes (
/api/threads/*,/api/user/*,/api/auth/*,/api/internal/*, etc.) fail with authentication errors (401 Unauthorized or 402 Out of Credits) when proxied through CLIProxyAPI to ampcode.com.The root cause is that all routes—both provider routes AND management routes—share the same
ReverseProxyinstance created bycreateReverseProxy(), which unconditionally:Authorizationheaderupstream-api-keyfrom configThis works correctly for provider routes (LLM API calls like
/api/provider/anthropic/v1/messages) because those routes authenticate using the Amp subscription token.However, management routes require a user-specific Amp session token for identity-based operations (reading threads, user info, etc.). The current implementation strips this token and replaces it with the provider subscription key, which lacks the necessary permissions.
Steps to Reproduce
ampcode.upstream-api-keyhttp://localhost:1337)read_threadtoolfind_threadtoolExpected Behavior
Management routes should successfully proxy to ampcode.com with appropriate authentication that allows user-identity operations.
Actual Behavior
Management routes return authentication errors:
{"error":{"code":402,"message":"Out of credits"}}{"error":"unauthorized"}Root Cause Analysis
File:
internal/api/modules/amp/proxy.go(lines 40-65)File:
internal/api/modules/amp/routes.go(lines 117-134)Affected Routes
/api/threads/*/api/user/*/api/auth/*/api/internal/*/api/telemetry/*/api/provider/*Proposed Solution
Create a separate
createManagementProxy()that preserves the client'sAuthorizationheader instead of replacing it:Then use this proxy for management routes in
registerManagementRoutes().Environment