# 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.5.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.2"
```

Associates Central hands you a credential id, a secret and a **version** when
you create a credential. Pass that version as the `version` option — it selects
which authentication flow is used:

| Version | Credentials | Auth endpoint | Scope | Catalog auth header |
|---|---|---|---|---|
| `3.1` NA, `3.2` EU, `3.3` FE | `amzn1.application-oa2-client.…` / `amzn1.oa2-cs.v1.…` | `https://api.amazon.com/auth/o2/token`, JSON body | `creatorsapi::default` | `Bearer <token>` |
| `2.1` NA, `2.2` EU, `2.3` FE | Cognito app client | regional Cognito endpoint, form encoded | `creatorsapi/default` | `Bearer <token>, Version <version>` |

Credentials created from v3.0 onwards use Login with Amazon. Older 2.x
credentials keep working, and omitting the option falls back to the 2.x version
for the region, so existing integrations are unaffected.

Pass the version that matches your credential: a 3.x credential sent through the
2.x flow fails with `{:auth_failed, 400, "{\"error\":\"invalid_client\"}"}`.

## 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.
