# Amazon Creators API - Elixir Client

An Elixir client library for the Amazon Creators API with automatic token
caching and management.

## Features

- ✅ Support for all Amazon regions (NA, EU, FE)
- ✅ Automatic OAuth token management with caching
- ✅ Token auto-refresh before expiration
- ✅ GenServer-based token cache for performance
- ✅ Comprehensive error handling
- ✅ Full test coverage
- ✅ Easy-to-use API

## Installation

Add `amazon_creators_api` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:amazon_creators_api, "~> 0.3.0"}
  ]
end
```

## Configuration

Set up your Amazon Creators API credentials as environment variables:

```bash
export CREATORS_API_CLIENT_ID="your_client_id"
export CREATORS_API_CLIENT_SECRET="your_client_secret"
export CREATORS_API_PARTNER_TAG="yourpartner-20"
export CREATORS_API_VERSION="3.0"
```

Associates Central hands you a credential id, a secret and a **version** when
you create a credential. Pass that version as the `version` option. Credentials
issued before Login with Amazon v3.0 carried a fixed version per region (`2.1`
for NA, `2.2` for EU, `2.3` for FE); those are used as the fallback when the
option is omitted, so existing code keeps working unchanged.

## Usage

### Simple Usage (Recommended)

The easiest way to use the API is with `fetch_items/2`, which handles token management automatically:

```elixir
# Fetch a single item
opts = [
  region: :na,  # or :eu, :fe
  marketplace: "www.amazon.com",
  partner_tag: System.get_env("CREATORS_API_PARTNER_TAG"),
  client_id: System.get_env("CREATORS_API_CLIENT_ID"),
  client_secret: System.get_env("CREATORS_API_CLIENT_SECRET"),
  version: System.get_env("CREATORS_API_VERSION")
]

case AmazonCreatorsAPI.fetch_items("B09B2SBHQK", opts) do
  {:ok, %{"itemsResult" => %{"items" => items}}} ->
    Enum.each(items, fn item ->
      IO.puts(item["itemInfo"]["title"]["displayValue"])
      IO.puts(item["detailPageURL"])
    end)

  {:error, reason} ->
    IO.puts("Error: #{inspect(reason)}")
end
```

### Fetch Multiple Items

```elixir
asins = ["B09B2SBHQK", "B09B8V1LZ3", "B0BZTW3TCH"]

{:ok, %{"itemsResult" => %{"items" => items}}} =
  AmazonCreatorsAPI.fetch_items(asins, opts)

IO.puts("Fetched #{length(items)} items")
```

### European Marketplaces

```elixir
opts = [
  region: :eu,
  marketplace: "www.amazon.de",
  partner_tag: "yourpartner-21",
  client_id: System.get_env("CREATORS_API_CLIENT_ID"),
  client_secret: System.get_env("CREATORS_API_CLIENT_SECRET")
]

AmazonCreatorsAPI.fetch_items("B09B2SBHQK", opts)
```

### Custom Resources

By default, the API fetches common product information. You can specify custom resources:

```elixir
opts = [
  region: :na,
  marketplace: "www.amazon.com",
  partner_tag: "yourpartner-20",
  client_id: System.get_env("CREATORS_API_CLIENT_ID"),
  client_secret: System.get_env("CREATORS_API_CLIENT_SECRET"),
  resources: [
    "itemInfo.title",
    "itemInfo.features",
    "offersV2.listings.price",
    "images.primary.large"
  ]
]

AmazonCreatorsAPI.fetch_items("B09B2SBHQK", opts)
```

#### Available Resources

**Item Information:**

- `itemInfo.title`
- `itemInfo.features`
- `itemInfo.byLineInfo`
- `itemInfo.contentInfo`
- `itemInfo.contentRating`
- `itemInfo.classifications`
- `itemInfo.externalIds`
- `itemInfo.manufactureInfo`
- `itemInfo.productInfo`
- `itemInfo.technicalInfo`
- `itemInfo.tradeInInfo`

**Images:**

- `images.primary.small`
- `images.primary.medium`
- `images.primary.large`
- `images.primary.highRes`
- `images.variants.small`
- `images.variants.medium`
- `images.variants.large`
- `images.variants.highRes`

**Offers (Version 2):**

- `offersV2.listings.price`
- `offersV2.listings.availability`
- `offersV2.listings.condition`
- `offersV2.listings.dealDetails`
- `offersV2.listings.isBuyBoxWinner`
- `offersV2.listings.loyaltyPoints`
- `offersV2.listings.merchantInfo`
- `offersV2.listings.type`

**Browse Nodes:**

- `browseNodeInfo.browseNodes`
- `browseNodeInfo.browseNodes.ancestor`
- `browseNodeInfo.browseNodes.salesRank`
- `browseNodeInfo.websiteSalesRank`

**Customer Reviews:**

- `customerReviews.count`
- `customerReviews.starRating`

**Other:**

- `parentASIN`

### Manual Token Management

For advanced use cases where you want to manage tokens yourself:

```elixir
# Get a token (cached automatically)
{:ok, token_data} = AmazonCreatorsAPI.get_token(:na, client_id, client_secret, "3.0")

# Use the token for multiple requests
{:ok, items1} = AmazonCreatorsAPI.get_items(
  "B09B2SBHQK",
  "www.amazon.com",
  "yourpartner-20",
  token_data["access_token"],
  token_data["version"]
)

{:ok, items2} = AmazonCreatorsAPI.get_items(
  "B09B8V1LZ3",
  "www.amazon.com",
  "yourpartner-20",
  token_data["access_token"],
  token_data["version"]
)
```

### Monitoring Token Cache

```elixir
# Get cache statistics
stats = AmazonCreatorsAPI.token_stats()
# => %{"na:your_client_id:3.0" => %{ttl_seconds: 3540, expires_at: 1735123456}}

# Clear cache (useful for testing or forcing refresh)
AmazonCreatorsAPI.clear_token_cache()
```

## Regions and Marketplaces

### North America (NA) - Version 2.1

- United States: `www.amazon.com`
- Canada: `www.amazon.ca`
- Mexico: `www.amazon.com.mx`
- Brazil: `www.amazon.com.br`

### Europe (EU) - Version 2.2

- United Kingdom: `www.amazon.co.uk`
- Germany: `www.amazon.de`
- France: `www.amazon.fr`
- Italy: `www.amazon.it`
- Spain: `www.amazon.es`
- Netherlands: `www.amazon.nl`
- Belgium: `www.amazon.com.be`
- Egypt: `www.amazon.eg`
- India: `www.amazon.in`
- Ireland: `www.amazon.ie`
- Poland: `www.amazon.pl`
- Saudi Arabia: `www.amazon.sa`
- Sweden: `www.amazon.se`
- Turkey: `www.amazon.com.tr`
- UAE: `www.amazon.ae`

### Far East (FE) - Version 2.3

- Japan: `www.amazon.co.jp`
- Singapore: `www.amazon.sg`
- Australia: `www.amazon.com.au`

## Error Handling

The API returns standard Elixir `{:ok, result}` or `{:error, reason}` tuples:

```elixir
case AmazonCreatorsAPI.fetch_items("INVALID_ASIN", opts) do
  {:ok, items} ->
    IO.inspect(items)

  {:error, :not_found} ->
    IO.puts("Item not found")

  {:error, :unauthorized} ->
    IO.puts("Invalid credentials")

  {:error, {:auth_failed, status, body}} ->
    IO.puts("Authentication failed: #{status}")

  {:error, {:http_error, status, body}} ->
    IO.puts("HTTP error: #{status}")

  {:error, {:request_failed, reason}} ->
    IO.puts("Request failed: #{inspect(reason)}")
end
```

## Token Caching

The library automatically caches authentication tokens using a GenServer.
Tokens are:

- Cached per region and client ID combination
- Automatically refreshed 60 seconds before expiration
- Reused across multiple API calls for better performance
- Thread-safe for concurrent applications

## Testing

Run the test suite:

```bash
mix test
```

Run tests with coverage:

```bash
mix coveralls
```

Run tests with detailed coverage:

```bash
mix coveralls.detail
```

## Testing with Mocks

The library includes a built-in mocking framework that makes it easy to test your application code without making real API calls.

### Configuration

Configure your test environment to use the mock HTTP client:

```elixir
# config/test.exs
import Config

config :amazon_creators_api,
  http_client: AmazonCreatorsAPI.HTTPClientMock
```

### Basic Usage

In your tests, set up expectations for HTTP requests:

```elixir
defmodule MyAppTest do
  use ExUnit.Case

  setup do
    AmazonCreatorsAPI.HTTPClientMock.reset()
    :ok
  end

  test "fetches product information" do
    # Set up mock response
    AmazonCreatorsAPI.HTTPClientMock.expect_post(
      {:ok, %{status_code: 200, body: ~s({"access_token": "test_token", "expires_in": 3600})}}
    )

    AmazonCreatorsAPI.HTTPClientMock.expect_post(
      {:ok, %{status_code: 200, body: ~s({"itemsResult": {"items": [{"asin": "B09B2SBHQK"}]}})}}
    )

    # Call your function that uses the API
    {:ok, result} = AmazonCreatorsAPI.fetch_items("B09B2SBHQK",
      region: :na,
      marketplace: "www.amazon.com",
      partner_tag: "test-20",
      client_id: "test_id",
      client_secret: "test_secret"
    )

    # Verify the result
    assert result["itemsResult"]["items"] == [%{"asin" => "B09B2SBHQK"}]

    # Optionally verify the requests made
    requests = AmazonCreatorsAPI.HTTPClientMock.get_requests()
    assert length(requests) == 2
  end
end
```

### Multiple Sequential Calls

Queue multiple responses for sequential API calls:

```elixir
# First call returns success
AmazonCreatorsAPI.HTTPClientMock.expect_post(
  {:ok, %{status_code: 200, body: ~s({"access_token": "token1"})}}
)

# Second call returns success
AmazonCreatorsAPI.HTTPClientMock.expect_post(
  {:ok, %{status_code: 200, body: ~s({"access_token": "token2"})}}
)
```

### Error Responses

Test error handling by mocking error responses:

```elixir
AmazonCreatorsAPI.HTTPClientMock.expect_post(
  {:error, %{reason: :timeout}}
)

# Or mock HTTP errors
AmazonCreatorsAPI.HTTPClientMock.expect_post(
  {:ok, %{status_code: 401, body: ~s({"error": "unauthorized"})}}
)
```

### Custom HTTP Client

You can also implement your own HTTP client for testing or production use:

```elixir
defmodule MyApp.CustomHTTPClient do
  @behaviour AmazonCreatorsAPI.HTTPClient

  @impl true
  def post(url, body, headers) do
    # Your custom implementation
    {:ok, %{status_code: 200, body: "{}"}}
  end
end

# config/config.exs
config :amazon_creators_api,
  http_client: MyApp.CustomHTTPClient
```

## Architecture

The library consists of two main modules:

1. **AmazonCreatorsAPI**: Main API module with public functions
2. **AmazonCreatorsAPI.TokenManager**: GenServer that manages token caching

The TokenManager is automatically started as part of your application's
supervision tree.

## License

MIT License - see LICENSE file for details.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Support

For issues related to the Amazon Creators API itself, please refer to the official
[Amazon documentation](https://partnernet.amazon.de/creatorsapi/docs/en-us/concepts/common-request-headers-and-parameters).

For issues with this library, please open an issue on Codeberg.
