Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Send the auth header as `api-access-token` so proxies that drop underscore headers (e.g. Caddy 2.6.4+) no longer cause `401`s. ([#41](https://github.com/chatwoot/cli/issues/41))

## [0.6.1] - 2026-06-03

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func TestApiCmdCallsAccountScopedEndpoint(t *testing.T) {
http.Error(w, "unexpected path: "+r.URL.Path, http.StatusNotFound)
return
}
if r.Header.Get("api_access_token") != "test-token" {
t.Errorf("api_access_token = %q, want test-token", r.Header.Get("api_access_token"))
if r.Header.Get("api-access-token") != "test-token" {
t.Errorf("api-access-token = %q, want test-token", r.Header.Get("api-access-token"))
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":123,"status":"open"}`))
Expand Down
2 changes: 1 addition & 1 deletion internal/sdk/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Authenticated user profile.
## Authentication

- Token resolved by callers from `CHATWOOT_API_KEY` or the OS keyring
- Injected as `api_access_token` header on all requests
- Injected as `api-access-token` header on all requests (hyphens, not underscores: proxies like Caddy 2.6.4+ and nginx drop underscore headers by default; Rack maps both to `HTTP_API_ACCESS_TOKEN`)
- Token must have `conversation:read`, `message:read`, `message:write` scopes

## File Organization
Expand Down
4 changes: 2 additions & 2 deletions internal/sdk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (c *Client) request(method, path string, body io.Reader) (*http.Request, er
return nil, err
}

req.Header.Set("api_access_token", c.APIKey)
req.Header.Set("api-access-token", c.APIKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor underscore-form token overrides in raw API calls

When an account-scoped raw call supplies Chatwoot's documented -H 'api_access_token: ...' header, RequestRaw deletes only that underscore-form key (internal/sdk/client.go:254-258), not the new Api-Access-Token, because Go treats the two names as distinct. Both values are therefore sent, and Rack/Puma's collision handling can prefer the configured hyphenated token instead of the caller's override. This regresses the previous override behavior for the spelling still advertised by the pinned Swagger; normalize this alias or delete both forms before applying custom headers.

Useful? React with 👍 / 👎.

req.Header.Set("Content-Type", "application/json")

return req, nil
Expand All @@ -81,7 +81,7 @@ func (c *Client) rawRequest(method, path string, body io.Reader) (*http.Request,
return nil, err
}

req.Header.Set("api_access_token", c.APIKey)
req.Header.Set("api-access-token", c.APIKey)
req.Header.Set("Content-Type", "application/json")

return req, nil
Expand Down
4 changes: 2 additions & 2 deletions internal/sdk/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,8 @@ func newContractClient(t *testing.T, handler func(*testing.T, *http.Request, *op
func validateContractRequest(t *testing.T, r *http.Request) *openapi3filter.RequestValidationInput {
t.Helper()

if got := r.Header.Get("api_access_token"); got != contractAPIKey {
t.Fatalf("api_access_token header = %q, want %q", got, contractAPIKey)
if got := r.Header.Get("api-access-token"); got != contractAPIKey {
t.Fatalf("api-access-token header = %q, want %q", got, contractAPIKey)
}

route, pathParams, err := getContractRouter(t).FindRoute(r)
Expand Down
4 changes: 2 additions & 2 deletions internal/sdk/help_center_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ func TestHelpCenterListPortalsCallsAccountEndpoint(t *testing.T) {
http.Error(w, "unexpected path: "+r.URL.Path, http.StatusNotFound)
return
}
if r.Header.Get("api_access_token") != "test-token" {
t.Errorf("api_access_token = %q, want test-token", r.Header.Get("api_access_token"))
if r.Header.Get("api-access-token") != "test-token" {
t.Errorf("api-access-token = %q, want test-token", r.Header.Get("api-access-token"))
}

w.Header().Set("Content-Type", "application/json")
Expand Down
Loading