defmodule AmazonCreatorsAPI do @moduledoc """ Client for Amazon Creators API to fetch product details by ASIN. Supports all regions (NA, EU, FE) with automatic token caching. """ # Region configurations @regions %{ na: %{ version: "2.1", auth_endpoint: "https://creatorsapi.auth.us-east-1.amazoncognito.com/oauth2/token", marketplaces: ["www.amazon.com", "www.amazon.ca", "www.amazon.com.mx", "www.amazon.com.br"] }, eu: %{ version: "2.2", auth_endpoint: "https://creatorsapi.auth.eu-south-2.amazoncognito.com/oauth2/token", marketplaces: [ "www.amazon.co.uk", "www.amazon.de", "www.amazon.fr", "www.amazon.it", "www.amazon.es", "www.amazon.nl", "www.amazon.com.be", "www.amazon.eg", "www.amazon.in", "www.amazon.ie", "www.amazon.pl", "www.amazon.sa", "www.amazon.se", "www.amazon.com.tr", "www.amazon.ae" ] }, fe: %{ version: "2.3", auth_endpoint: "https://creatorsapi.auth.us-west-2.amazoncognito.com/oauth2/token", marketplaces: ["www.amazon.co.jp", "www.amazon.sg", "www.amazon.com.au"] } } @api_base_url "https://creatorsapi.amazon" @doc """ Internal function to fetch authentication token (used by TokenManager). For direct use, prefer get_token/3 which uses caching. """ def fetch_auth_token(region, client_id, client_secret) do region_config = Map.get(@regions, region) if !region_config do {:error, :invalid_region} else headers = [ {"Content-Type", "application/x-www-form-urlencoded"} ] body = URI.encode_query(%{ "grant_type" => "client_credentials", "client_id" => client_id, "client_secret" => client_secret, "scope" => "creatorsapi/default" }) case AmazonCreatorsAPI.HTTPClient.post(region_config.auth_endpoint, body, headers) do {:ok, %{status_code: 200, body: response_body}} -> token_data = Jason.decode!(response_body) {:ok, Map.put(token_data, "version", region_config.version)} {:ok, %{status_code: status_code, body: response_body}} -> {:error, {:auth_failed, status_code, response_body}} {:error, %{reason: reason}} -> {:error, {:request_failed, reason}} end end end @doc """ Get authentication token for the specified region. Uses cached token if available and valid. ## Parameters - region: :na, :eu, or :fe - client_id: Your Creators API client ID - client_secret: Your Creators API client secret ## Examples iex> AmazonCreatorsAPI.get_token(:na, "your_client_id", "your_client_secret") {:ok, %{"access_token" => "eyJraWQiOi...", "expires_in" => 3600, "token_type" => "Bearer", "version" => "2.1"}} Returns `{:ok, token_data}` on success or `{:error, reason}` on failure. """ def get_token(region, client_id, client_secret) do AmazonCreatorsAPI.TokenManager.get_token(region, client_id, client_secret) end @doc """ Fetch item details by ASIN(s). ## Parameters - asins: Single ASIN string or list of ASINs - marketplace: Marketplace domain (e.g., "www.amazon.com") - partner_tag: Your Amazon Associates tracking ID - access_token: Access token from get_token/3 - credential_version: Version from get_token/3 response - resources: List of resources to retrieve (optional) ## Examples iex> {:ok, token_data} = AmazonCreatorsAPI.get_token(:na, client_id, client_secret) iex> AmazonCreatorsAPI.get_items( ...> "B09B2SBHQK", ...> "www.amazon.com", ...> "yourpartner-20", ...> token_data["access_token"], ...> token_data["version"] ...> ) {:ok, %{"itemsResult" => %{"items" => [...]}}} Returns `{:ok, items_data}` on success or `{:error, reason}` on failure. """ def get_items( asins, marketplace, partner_tag, access_token, credential_version, resources \\ nil ) do item_ids = if is_list(asins), do: asins, else: [asins] default_resources = [ "images.primary.small", "images.primary.medium", "images.primary.large", "itemInfo.title", "itemInfo.features", "itemInfo.byLineInfo", "offersV2.listings.price", "parentASIN" ] headers = [ {"Authorization", "Bearer #{access_token}, Version #{credential_version}"}, {"Content-Type", "application/json"}, {"x-marketplace", marketplace} ] body = Jason.encode!(%{ "itemIds" => item_ids, "itemIdType" => "ASIN", "marketplace" => marketplace, "partnerTag" => partner_tag, "resources" => resources || default_resources }) url = "#{@api_base_url}/catalog/v1/getItems" case AmazonCreatorsAPI.HTTPClient.post(url, body, headers) do {:ok, %{status_code: 200, body: response_body}} -> {:ok, Jason.decode!(response_body)} {:ok, %{status_code: 404}} -> {:error, :not_found} {:ok, %{status_code: 401}} -> {:error, :unauthorized} {:ok, %{status_code: status_code, body: response_body}} -> {:error, {:http_error, status_code, response_body}} {:error, %{reason: reason}} -> {:error, {:request_failed, reason}} end end @doc """ Convenience function to get items with automatic token management. Recommended for most use cases - handles token caching automatically. ## Parameters (as keyword list) - region: :na, :eu, or :fe (default: :na) - marketplace: Marketplace domain (default: "www.amazon.com") - partner_tag: Your Amazon Associates tracking ID (required) - client_id: Your Creators API client ID (required) - client_secret: Your Creators API client secret (required) - resources: List of resources to retrieve (optional) ## Examples iex> opts = [ ...> region: :na, ...> marketplace: "www.amazon.com", ...> partner_tag: "yourpartner-20", ...> client_id: "your_client_id", ...> client_secret: "your_client_secret" ...> ] iex> AmazonCreatorsAPI.fetch_items("B09B2SBHQK", opts) {:ok, %{"itemsResult" => %{"items" => [...]}}} Returns `{:ok, items_data}` on success or `{:error, reason}` on failure. """ def fetch_items(asins, opts \\ []) do region = Keyword.get(opts, :region, :na) marketplace = Keyword.get(opts, :marketplace, "www.amazon.com") partner_tag = Keyword.fetch!(opts, :partner_tag) client_id = Keyword.fetch!(opts, :client_id) client_secret = Keyword.fetch!(opts, :client_secret) resources = Keyword.get(opts, :resources) with {:ok, token_data} <- get_token(region, client_id, client_secret), {:ok, items} <- get_items( asins, marketplace, partner_tag, token_data["access_token"], token_data["version"], resources ) do {:ok, items} end end @doc """ Get token cache statistics (for monitoring/debugging). """ def token_stats do AmazonCreatorsAPI.TokenManager.stats() end @doc """ Clear the token cache (useful for testing or forced refresh). """ def clear_token_cache do AmazonCreatorsAPI.TokenManager.clear_cache() end end