Skip to content
Open
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
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ alexacli auth

This downloads [alexa-cookie-cli](https://github.com/adn77/alexa-cookie-cli) on first run, opens a browser at http://127.0.0.1:8080 for Amazon login, and automatically captures and saves your refresh token.

For non-US accounts:
For non-US accounts (recommended split-domain login flow):

```bash
alexacli auth --domain amazon.de
alexacli auth --domain amazon.co.uk
# Base auth domain (.com) + local marketplace country page (.it/.de/.co.uk...)
alexacli auth --domain amazon.com --country amazon.it
alexacli auth --domain amazon.com --country amazon.de
alexacli auth --domain amazon.com --country amazon.co.uk
```

### Manual Token
Expand All @@ -69,6 +71,21 @@ alexacli auth logout

Configuration is stored in `~/.alexa-cli/config.json`.

### Config fields

```json
{
"refresh_token": "Atnr|...",
"amazon_domain": "amazon.com",
"amazon_local": "amazon.it",
"default_device": "YOUR_DEVICE_SERIAL"
}
```

- `amazon_domain`: auth/token domain (usually `amazon.com`)
- `amazon_local`: local marketplace/runtime domain (e.g. `amazon.it`, `amazon.de`)
- If `amazon_local` is omitted, the CLI falls back to `amazon_domain`.

## Usage

### List Devices
Expand Down Expand Up @@ -353,6 +370,12 @@ alexacli command "turn on lights" -d Kitchen --verbose

Run `alexacli auth` to configure your refresh token.

For EU/IT accounts, prefer:

```bash
alexacli auth --domain amazon.com --country amazon.it
```

### Device not found

Use `alexacli devices` to see exact device names, then match them in your commands. Partial matching is supported.
Expand All @@ -365,9 +388,12 @@ Try running the same command with `alexacli command` instead - this sends it as

This CLI uses the same unofficial API that the Alexa mobile app uses. It:

1. Exchanges your refresh token for session cookies
2. Obtains a CSRF token from alexa.amazon.com
3. Sends commands to pitangui.amazon.com (US) or layla.amazon.com (EU)
1. Exchanges your refresh token for session cookies via Amazon auth (`api.amazon.com` with your `amazon_domain`)
2. Obtains CSRF tokens from Alexa web endpoints (`alexa.*`, with fallback hosts)
3. Sends command APIs to regional runtime endpoints:
- US/CA/BR/IN: `pitangui.*`
- EU (including IT): `layla.amazon.com`
4. For history/privacy APIs, uses `www.<amazon_local>` with fallback to auth/global domains when needed (to handle temporary marketplace-side blocks)

This approach is used by many popular projects including [alexa-remote-control](https://github.com/thorsten-gehrig/alexa-remote-control) and [Home Assistant's Alexa integration](https://github.com/alandtse/alexa_media_player).

Expand Down
26 changes: 18 additions & 8 deletions cmd/alexa/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const cookieCLIVersion = "v5.0.1"

func newAuthCmd(flags *rootFlags) *cobra.Command {
var domain string
var country string

cmd := &cobra.Command{
Use: "auth [refresh-token]",
Expand All @@ -42,7 +43,7 @@ Or set the ALEXA_REFRESH_TOKEN environment variable.`,
token = args[0]
} else {
// Try browser-based auth flow
t, err := runBrowserAuth(domain)
t, err := runBrowserAuth(domain, country)
if err != nil {
fmt.Fprintf(os.Stderr, "Browser auth failed: %v\n", err)
fmt.Fprintf(os.Stderr, "Falling back to manual token entry.\n\n")
Expand All @@ -64,7 +65,7 @@ Or set the ALEXA_REFRESH_TOKEN environment variable.`,
}

// Validate the token by trying to authenticate
client, err := api.NewClient(token, domain)
client, err := api.NewClientWithLocal(token, domain, country)
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
Expand All @@ -79,6 +80,7 @@ Or set the ALEXA_REFRESH_TOKEN environment variable.`,
cfg := &config.Config{
RefreshToken: token,
AmazonDomain: domain,
AmazonLocal: country,
}

if err := config.Save(cfg); err != nil {
Expand All @@ -90,7 +92,8 @@ Or set the ALEXA_REFRESH_TOKEN environment variable.`,
},
}

cmd.Flags().StringVar(&domain, "domain", "amazon.com", "Amazon domain (amazon.com, amazon.de, amazon.co.uk, etc.)")
cmd.Flags().StringVar(&domain, "domain", "amazon.com", "Base Amazon domain for login/token exchange (usually amazon.com)")
cmd.Flags().StringVar(&country, "country", "amazon.it", "Marketplace country page for login (e.g. amazon.it, amazon.de)")

Comment on lines +95 to 97
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The new --country flag defaults to amazon.it, which means running alexacli auth with no flags will save AmazonLocal=amazon.it and initialize the API client against the IT/EU runtime endpoints even for US accounts. This can break device listing/commands for users who previously relied on the default amazon.com behavior. Consider defaulting --country to an empty string (or amazon.com) and normalizing after flag parsing (e.g., if empty then set to domain) before calling runBrowserAuth/NewClientWithLocal and before saving config.

Copilot uses AI. Check for mistakes.
cmd.AddCommand(newAuthStatusCmd(flags))
cmd.AddCommand(newAuthLogoutCmd(flags))
Expand Down Expand Up @@ -118,10 +121,10 @@ func newAuthStatusCmd(flags *rootFlags) *cobra.Command {
masked = masked[:8] + "..."
}

status := fmt.Sprintf("Domain: %s\nToken: %s", cfg.AmazonDomain, masked)
status := fmt.Sprintf("Domain: %s\nLocal: %s\nToken: %s", cfg.AmazonDomain, cfg.AmazonLocal, masked)

if verify {
client, err := api.NewClient(cfg.RefreshToken, cfg.AmazonDomain)
client, err := api.NewClientWithLocal(cfg.RefreshToken, cfg.AmazonDomain, cfg.AmazonLocal)
if err != nil {
status += "\nStatus: invalid (authentication failed)"
return out.Success(status)
Expand Down Expand Up @@ -268,14 +271,21 @@ func localeFlags(domain string) []string {

// runBrowserAuth launches alexa-cookie-cli to perform browser-based Amazon login.
// Returns the captured refresh token.
func runBrowserAuth(domain string) (string, error) {
func runBrowserAuth(domain, country string) (string, error) {
if domain == "" {
domain = "amazon.com"
}
if country == "" {
country = domain
}

binPath, err := ensureCookieCLI()
if err != nil {
return "", err
}

args := []string{"-b", domain, "-p", domain}
args = append(args, localeFlags(domain)...)
args := []string{"-b", domain, "-p", country}
args = append(args, localeFlags(country)...)
args = append(args, "-q")

fmt.Fprintf(os.Stderr, "Opening browser for Amazon login at http://127.0.0.1:8080 ...\n")
Expand Down
2 changes: 1 addition & 1 deletion cmd/alexa/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func getClientWithFlags(flags *rootFlags) (*api.Client, error) {
return nil, err
}

client, err := api.NewClient(cfg.RefreshToken, cfg.AmazonDomain)
client, err := api.NewClientWithLocal(cfg.RefreshToken, cfg.AmazonDomain, cfg.AmazonLocal)
if err != nil {
return nil, err
}
Expand Down
Loading