├── src └── main │ └── java │ └── com │ └── keepa │ └── api │ └── backend │ ├── structs │ ├── AmazonLocale.java │ ├── RequestError.java │ ├── BestSellers.java │ ├── DealResponse.java │ ├── Notification.java │ ├── TrackingRequest.java │ ├── LightningDeal.java │ ├── Response.java │ ├── Category.java │ ├── Deal.java │ ├── Tracking.java │ ├── Offer.java │ ├── Seller.java │ ├── DealRequest.java │ ├── Stats.java │ └── Request.java │ ├── exceptions │ └── KeepaAPIException.java │ ├── helper │ ├── BasicNameFactory.java │ ├── Utility.java │ ├── KeepaTime.java │ └── ProductAnalyzer.java │ └── KeepaAPI.java ├── .gitignore ├── pom.xml ├── README.md └── LICENSE /src/main/java/com/keepa/api/backend/structs/AmazonLocale.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | /** 4 | * Amazon Locale Domain Enum 5 | */ 6 | public enum AmazonLocale { 7 | RESERVED, US, GB, DE, FR, JP, CA, RESERVED2, IT, ES, IN, MX, BR 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/exceptions/KeepaAPIException.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.exceptions; 2 | 3 | /** 4 | * API-Exception thrown on different negative API-Events. 5 | */ 6 | public class KeepaAPIException extends Exception { 7 | public KeepaAPIException(String s) { 8 | super(s); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | *.txt 12 | 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | 16 | target/* 17 | *.iml 18 | 19 | out/* 20 | .idea 21 | composer.json -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/RequestError.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import static com.keepa.api.backend.helper.Utility.gsonPretty; 4 | 5 | /** 6 | * Contains information about an API error. 7 | */ 8 | public class RequestError { 9 | String type, message, details; 10 | 11 | @Override 12 | public String toString() { 13 | return gsonPretty.toJson(this); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/helper/BasicNameFactory.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.helper; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * HelperFactory to name ThreadPool-Threads for debugging purpose. 8 | */ 9 | public class BasicNameFactory implements ThreadFactory { 10 | private final String namingPattern; 11 | private final AtomicInteger count = new AtomicInteger(0); 12 | 13 | public BasicNameFactory(String s) { 14 | this.namingPattern = s; 15 | } 16 | 17 | @Override 18 | public Thread newThread(Runnable r) { 19 | final Thread t = new Thread(r); 20 | if(this.namingPattern != null) { 21 | t.setName(String.format(namingPattern, count.getAndIncrement())); 22 | } 23 | 24 | return t; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/BestSellers.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | /** 6 | * About: 7 | * A best sellers ASIN list of a specific category. 8 | *

9 | * Returned by: 10 | * Request Best Sellers 11 | */ 12 | public class BestSellers { 13 | 14 | /** 15 | * Integer value for the Amazon locale this category belongs to. 16 | * {@link AmazonLocale} 17 | */ 18 | public byte domainId; 19 | 20 | /** 21 | * States the last time we have updated the list, in Keepa Time minutes.
22 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 23 | */ 24 | public int lastUpdate; 25 | 26 | /** 27 | * The category node id used by Amazon. Represents the identifier of the category. Also part of the Product object's categories and rootCategory fields. Always a positive Long value or 0 if a product group was used. 28 | */ 29 | public long categoryId; 30 | 31 | /** 32 | * An ASIN list. The list starts with the best selling product (lowest sales rank). 33 | */ 34 | public String[] asinList; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/helper/Utility.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.helper; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.lang.reflect.Modifier; 8 | import java.net.URLEncoder; 9 | 10 | /** 11 | * Static helper methods and reused objects. 12 | */ 13 | public class Utility { 14 | public static final Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.PRIVATE, Modifier.PROTECTED).create(); 15 | 16 | public static final Gson gsonPretty = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.PRIVATE, Modifier.PROTECTED).setPrettyPrinting().create(); 17 | 18 | public static String arrayToCsv(String array[]) { 19 | StringBuilder buff = new StringBuilder(); 20 | String sep = ""; 21 | for (String s : array) { 22 | buff.append(sep); 23 | buff.append(s); 24 | sep = ","; 25 | } 26 | return buff.toString(); 27 | } 28 | 29 | public static String urlEncodeUTF8(String s) { 30 | try { 31 | return URLEncoder.encode(s, "UTF-8"); 32 | } catch (UnsupportedEncodingException e) { 33 | throw new UnsupportedOperationException(e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/helper/KeepaTime.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.helper; 2 | 3 | /** 4 | * Keepa Time - Unix Time Converter Helper Class 5 | */ 6 | public class KeepaTime { 7 | public static long keepaStartHour = 359400; 8 | public static long keepaStartMinute = 21564000; 9 | 10 | public static int nowHours() { 11 | return unixInMillisToKeepaHour(System.currentTimeMillis()); 12 | } 13 | 14 | public static int nowMinutes() { 15 | return unixInMillisToKeepaMinutes(System.currentTimeMillis()); 16 | } 17 | 18 | public static int unixInMillisToKeepaMinutes(long unix) { 19 | return (int)((Math.floor(unix / (60 * 1000))) - keepaStartMinute); 20 | } 21 | 22 | public static int unixInMillisToKeepaHour(long unix) { 23 | return (int)((Math.floor(unix / (60 * 60 * 1000))) - keepaStartHour); 24 | } 25 | 26 | public static long keepaHourToUnixInMillis(int hour) { 27 | return hour * 60 * 60 * 1000L + keepaStartHour * 60 * 60 * 1000L; 28 | } 29 | 30 | public static long keepaMinuteToUnixInMillis(int minute) { 31 | return minute * 60 * 1000L + keepaStartMinute * 60 * 1000L; 32 | } 33 | 34 | public static long keepaMinuteToUnixInMillis(String minute) { 35 | return keepaMinuteToUnixInMillis(Integer.parseInt(minute)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/DealResponse.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import static com.keepa.api.backend.helper.Utility.gson; 4 | 5 | /** 6 | * The response of a browse deals request. 7 | *

Each deal product is listed in one root category. The three fields categoryIds, categoryNames and categoryCount contain information about those categories. The values of the same index in those arrays belong together, so the first category entry would have the id categoryIds[0], the name categoryNames[0] and the deals count categoryCount[0]. If the root category of a product could not be determined it will be listed in the category with the name "?" with the id 9223372036854775807.

8 | */ 9 | public class DealResponse { 10 | 11 | /** 12 | * Ordered array of all deal objects matching your query. 13 | */ 14 | public Deal[] dr = null; 15 | 16 | /** 17 | * Not yet used / placeholder 18 | */ 19 | public byte[] drDateIndex = null; 20 | 21 | /** 22 | * Contains all root categoryIds of the matched deal products. 23 | */ 24 | public long[] categoryIds = null; 25 | 26 | /** 27 | * Contains all root category names of the matched deal products. 28 | */ 29 | public String[] categoryNames = null; 30 | 31 | /** 32 | * Contains how many deal products in the respective root category are found. 33 | */ 34 | public int[] categoryCount = null; 35 | 36 | @Override 37 | public String toString() { 38 | return gson.toJson(this); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Notification.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | /** 6 | * Represents a price alert 7 | */ 8 | public class Notification { 9 | 10 | /** 11 | * The notified product ASIN 12 | */ 13 | public String asin = null; 14 | 15 | /** 16 | * Title of the product. Caution: may contain HTML markup in rare cases. 17 | */ 18 | public String title; 19 | 20 | /** 21 | * The main image name of the product. Full Amazon image path:
22 | * https://m.media-amazon.com/images/I/_image name_ 23 | */ 24 | public String image; 25 | 26 | /** 27 | * Creation date of the notification in {@link KeepaTime} minutes 28 | */ 29 | public int createDate; 30 | 31 | /** 32 | * The main Amazon locale of the tracking which determines the currency used for all prices of this notification.
33 | * Integer value for the Amazon locale {@link AmazonLocale} 34 | */ 35 | public byte domainId; 36 | 37 | /** 38 | * The Amazon locale which triggered the notification.
39 | * Integer value for the Amazon locale {@link AmazonLocale} 40 | */ 41 | public byte notificationDomainId; 42 | 43 | /** 44 | * The {@link Product.CsvType} which triggered the notification. 45 | */ 46 | public int csvType; 47 | 48 | /** 49 | * The {@link Tracking.TrackingNotificationCause} of the notification. 50 | */ 51 | public int trackingNotificationCause; 52 | 53 | /** 54 | * Contains the prices / values of the product of the time this notification was created. 55 | *

Uses {@link Product.CsvType} indexing.

56 | * The price is an integer of the respective Amazon locale's smallest currency unit (e.g. euro cents or yen). 57 | * If no offer was available in the given interval (e.g. out of stock) the price has the value -1. 58 | */ 59 | public int[] currentPrices; 60 | 61 | /** 62 | * States through which notification channels ({@link Tracking.NotificationType}) this notification was delivered. 63 | */ 64 | public boolean[] sentNotificationVia; 65 | 66 | /** 67 | * The meta data of the tracking. 68 | */ 69 | public String metaData; 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/TrackingRequest.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | /** 4 | * Required by the Add Tracking request. 5 | */ 6 | public class TrackingRequest { 7 | 8 | public TrackingRequest(String asin, AmazonLocale mainDomainId, int updateInterval){ 9 | this.asin = asin; 10 | this.mainDomainId = (byte) mainDomainId.ordinal(); 11 | this.updateInterval = updateInterval < 1 ? 1 : updateInterval; 12 | } 13 | 14 | /** 15 | * The product ASIN to track 16 | */ 17 | public String asin; 18 | 19 | /** 20 | * The time to live in hours until the tracking expires and is deleted. 21 | * When setting the value through the _Add Tracking_ request it is in relation to the time of request. Possible values:
22 | *
any positive integer: time to live in hours
23 | * 0: never expires
24 | * any negative integer:
25 | *
tracking already exists: keep the original `ttl`
tracking is new: use the absolute value as `ttl`
26 | *
27 | */ 28 | public int ttl = 24 * 365 * 2; 29 | 30 | /** 31 | * Trigger a notification if tracking expires or is removed by the system (e.g. product deprecated) 32 | */ 33 | public boolean expireNotify = false; 34 | 35 | /** 36 | * Whether or not all desired prices are in the currency of the mainDomainId. If false they will be converted. 37 | */ 38 | public boolean desiredPricesInMainCurrency = true; 39 | 40 | /** 41 | * The main Amazon locale of this tracking determines the currency used for all desired prices.
42 | * Integer value for the Amazon locale {@link AmazonLocale} 43 | */ 44 | public byte mainDomainId; 45 | 46 | /** 47 | * Contains all settings for price or value related tracking criteria 48 | */ 49 | public Tracking.TrackingThresholdValue[] thresholdValues = null; 50 | 51 | /** 52 | * Contains specific, meta tracking criteria, like out of stock. 53 | */ 54 | public Tracking.TrackingNotifyIf[] notifyIf = null; 55 | 56 | /** 57 | * Determines through which channels we will send notifications.
58 | * Uses NotificationType indexing {@link Tracking.NotificationType}. True means the channel will be used. 59 | */ 60 | public boolean[] notificationType = null; 61 | 62 | /** 63 | * A tracking specific rearm timer.
64 | * -1 = use default notification timer of the user account (changeable via the website settings) 65 | * 0 = never notify a desired price more than once 66 | * larger than 0 = rearm the desired price after x minutes. 67 | */ 68 | public int individualNotificationInterval = -1; 69 | 70 | /** 71 | * The update interval, in hours. Determines how often our system will trigger a product update. A setting of 1 72 | * hour will not trigger an update exactly every 60 minutes, but as close to that as it is efficient for our system. 73 | * Throughout a day it will be updated 24 times, but the updates are not perfectly distributed.
74 | * Possible values: Any integer between 0 and 25. Default is 1. 75 | */ 76 | public int updateInterval = 1; 77 | 78 | /** 79 | * Meta data of this tracking (max length is 500 characters). You can use this to store any string with this tracking. 80 | */ 81 | public String metaData = null; 82 | } 83 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | UTF-8 8 | UTF-8 9 | UTF-8 10 | 1.8 11 | 1.8 12 | github 13 | 14 | 15 | com.keepa.api 16 | backend 17 | 2.02 18 | jar 19 | 20 | Keepa Java Framework 21 | A Java Framework which helps you to deploy your own application, based on the Keepa API. 22 | https://keepa.com/ 23 | 24 | 25 | 26 | The Apache License, Version 2.0 27 | http://www.apache.org/licenses/LICENSE-2.0.txt 28 | 29 | 30 | 31 | 32 | 33 | Keepa.com 34 | info@keepa.com 35 | Keepa 36 | https://keepa.com 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-source-plugin 45 | 3.2.1 46 | 47 | 48 | attach-sources 49 | 50 | jar 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-javadoc-plugin 59 | 3.4.1 60 | 61 | none 62 | false 63 | 64 | 65 | 66 | attach-javadocs 67 | 68 | jar 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | com.google.code.gson 79 | gson 80 | 2.10 81 | 82 | 83 | org.jdeferred 84 | jdeferred-core 85 | 1.2.6 86 | 87 | 88 | org.slf4j 89 | slf4j-simple 90 | 1.7.21 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/LightningDeal.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | public class LightningDeal { 4 | /** 5 | * The domainId of the products Amazon locale
6 | * {@link AmazonLocale} 7 | */ 8 | public byte domainId; 9 | 10 | /** 11 | * States the time of our last data collection of this lighting deal, in Keepa Time minutes.
12 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 13 | */ 14 | public int lastUpdate; 15 | 16 | /** 17 | * The ASIN of the product 18 | */ 19 | public String asin; 20 | 21 | /** 22 | * Title of the product. Caution: may contain HTML markup in rare cases. 23 | */ 24 | public String title; 25 | 26 | /** 27 | * The seller id of the merchant offering this deal. 28 | */ 29 | public String sellerName; 30 | 31 | /** 32 | * The name of seller offering this deal. 33 | */ 34 | public String sellerId; 35 | 36 | /** 37 | * A unique ID for this deal. 38 | */ 39 | public String dealId; 40 | 41 | /** 42 | * The discounted price of this deal. Available once the deal has started. -1 if the deal’s state is upcoming. The price is an integer of the respective Amazon locale’s smallest currency unit (e.g. euro cents or yen). 43 | */ 44 | public int dealPrice; 45 | 46 | /** 47 | * The regular price of this product. Available once the deal has started. -1 if the deal’s state is upcoming. The price is an integer of the respective Amazon locale’s smallest currency unit (e.g. euro cents or yen). 48 | */ 49 | public int currentPrice; 50 | 51 | /** 52 | * The name of the primary image of the product. null if not available. 53 | */ 54 | public String image; 55 | 56 | /** 57 | * Whether or not the deal is Prime eligible. 58 | */ 59 | public boolean isPrimeEligible; 60 | 61 | /** 62 | * Whether or not the deal is fulfilled by Amazon. 63 | */ 64 | public boolean isFulfilledByAmazon; 65 | 66 | /** 67 | * Whether or not the price is restricted by MAP (Minimum Advertised Price). 68 | */ 69 | public boolean isMAP; 70 | 71 | /** 72 | * The rating of the product. A rating is an integer from 0 to 50 (e.g. 45 = 4.5 stars). 73 | */ 74 | public int rating; 75 | 76 | /** 77 | * The product’s review count. 78 | */ 79 | public int totalReviews; 80 | 81 | /** 82 | * The state of the deal. 83 | */ 84 | public DealState dealState; 85 | 86 | /** 87 | * The start time of this lighting deal, in Keepa Time minutes. Note that due to the delay in our data collection the deal price might not be available immediately once the deal has started on Amazon.
88 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 89 | */ 90 | public int startTime; 91 | 92 | /** 93 | * The end time of this lighting deal, in Keepa Time minutes.
94 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 95 | */ 96 | public int endTime; 97 | 98 | /** 99 | * The percentage claimed of the lighting deal. Since lightning deals have limited stock, this number may change fast on Amazon, but due to the delay of our data collection the provided value may be outdated. 100 | */ 101 | public int percentClaimed; 102 | 103 | /** 104 | * The provided discount of this deal, according to Amazon. May be in reference to the list price, not the current price. 105 | */ 106 | public int percentOff; 107 | 108 | /** 109 | * The dimension attributes of this deal. 110 | */ 111 | public Product.VariationAttributeObject[] variation; 112 | 113 | 114 | public enum DealState { 115 | AVAILABLE, UPCOMING, WAITLIST, SOLDOUT, WAITLISTFULL, EXPIRED, SUPPRESSED 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | Keepa API Framework 18 | ============================== 19 | 20 | This framework is intended for users of the Keepa API. 21 | 22 | Features 23 | -------- 24 | * Retrieves content from the API asynchronously and in parallel via Deferred callbacks (Java 8 Lambda friendly) 25 | * Parses API response to easy to use Java objects 26 | * Provides methods that facilitate the work with price history data 27 | 28 | Maven 29 | ----- 30 | ```xml 31 | 32 | 33 | Keepa 34 | Keepa Repository 35 | https://keepa.com/maven/ 36 | 37 | ... 38 | 39 | 40 | 41 | 42 | com.keepa.api 43 | backend 44 | LATEST 45 | 46 | ... 47 | 48 | ``` 49 | 50 | Gradle 51 | ----- 52 | ```xml 53 | repositories { 54 | ... 55 | maven { url 'https://keepa.com/maven/' } 56 | } 57 | dependencies { 58 | ... 59 | compile 'com.keepa.api:backend:latest.release' 60 | } 61 | 62 | Also consider to add 63 | 64 | configurations.all { 65 | resolutionStrategy { 66 | cacheDynamicVersionsFor 0, 'seconds' 67 | cacheChangingModulesFor 0, 'seconds' 68 | } 69 | } 70 | 71 | Which makes sure that the newest version from our servers is pulled during build. 72 | ``` 73 | 74 | 75 | Quick Example 76 | ============== 77 | 78 | Make an API request 79 | --------------------------- 80 | 81 | ```java 82 | KeepaAPI api = new KeepaAPI("YOUR_API_KEY"); // the KeepaAPI object is reuseable 83 | Request r = Request.getProductRequest(AmazonLocale.US, 90, null, "B001GZ6QEC"); 84 | 85 | api.sendRequest(r) 86 | .done(result -> { 87 | switch (result.status) { 88 | case OK: 89 | // iterate over received product information 90 | for (Product product : result.products){ 91 | // System.out.println(product); 92 | if (product.productType == Product.ProductType.STANDARD.code || product.productType == Product.ProductType.DOWNLOADABLE.code) { 93 | 94 | //get basic data of product and print to stdout 95 | int currentAmazonPrice = ProductAnalyzer.getLast(product.csv[Product.CsvType.AMAZON.index], Product.CsvType.AMAZON); 96 | 97 | //check if the product is in stock -1 -> out of stock 98 | if (currentAmazonPrice == -1) { 99 | System.out.println(product.asin + " " + product.title + " is currently out of stock!"); 100 | } else { 101 | System.out.println(product.asin + " " + product.title + " Current Amazon Price: " + currentAmazonPrice); 102 | } 103 | 104 | // get weighted mean of the last 90 days for Amazon 105 | int weightedMean90days = ProductAnalyzer.calcWeightedMean(product.csv[Product.CsvType.AMAZON.index], KeepaTime.nowMinutes(), 90, Product.CsvType.AMAZON); 106 | 107 | ... 108 | } else { 109 | ... 110 | } 111 | } 112 | break; 113 | default: 114 | System.out.println(result); 115 | } 116 | }) 117 | .fail(failure -> System.out.println(failure)); 118 | 119 | ``` 120 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Response.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.KeepaAPI; 4 | 5 | import java.util.HashMap; 6 | 7 | import static com.keepa.api.backend.helper.Utility.gson; 8 | import static com.keepa.api.backend.helper.Utility.gsonPretty; 9 | 10 | /** 11 | * Common Keepa API Response 12 | */ 13 | public class Response { 14 | /** 15 | * Server time when response was sent. 16 | */ 17 | public long timestamp = System.currentTimeMillis(); 18 | 19 | /** 20 | * States how many ASINs may be requested before the assigned API contingent is depleted. 21 | * If the contigent is depleted, HTTP status code 503 will be delivered with the message: 22 | * "You are submitting requests too quickly and your requests are being throttled." 23 | */ 24 | public int tokensLeft = 0; 25 | 26 | /** 27 | * Milliseconds till new tokens are generated. Use this if your contigent is depleted to wait before you try a new request. Tokens are generated every 5 minutes. 28 | */ 29 | public int refillIn = 0; 30 | 31 | /** 32 | * Token refill rate per minute. 33 | */ 34 | public int refillRate = 0; 35 | 36 | /** 37 | * total time the request took (local, including latencies and connection establishment), in milliseconds 38 | */ 39 | public long requestTime = 0; 40 | 41 | /** 42 | * time the request's processing took (remote), in milliseconds 43 | */ 44 | public int processingTimeInMs = 0; 45 | 46 | /** 47 | * Token flow reduction 48 | */ 49 | public double tokenFlowReduction = 0; 50 | 51 | /** 52 | * Tokens used for call 53 | */ 54 | public int tokensConsumed = 0; 55 | 56 | /** 57 | * Status of the response. 58 | */ 59 | public KeepaAPI.ResponseStatus status = KeepaAPI.ResponseStatus.PENDING; 60 | 61 | /** 62 | * HTTP Status code of the response. 63 | */ 64 | public int statusCode = 0; 65 | 66 | /** 67 | * Results of the product request 68 | */ 69 | public Product[] products = null; 70 | 71 | /** 72 | * Results of the category lookup and search 73 | */ 74 | public HashMap categories = null; 75 | 76 | /** 77 | * Results of the category lookup and search includeParents parameter 78 | */ 79 | public HashMap categoryParents = null; 80 | 81 | /** 82 | * Results of the deals request 83 | */ 84 | public DealResponse deals = null; 85 | 86 | /** 87 | * Results of the best sellers request 88 | */ 89 | public BestSellers bestSellersList = null; 90 | 91 | /** 92 | * Results of the deals request 93 | */ 94 | public HashMap sellers = null; 95 | 96 | /** 97 | * Results of get and add tracking operations 98 | */ 99 | public Tracking[] trackings = null; 100 | 101 | /** 102 | * Results of get and add tracking operations 103 | */ 104 | public Notification[] notifications = null; 105 | 106 | /** 107 | * A list of ASINs. Result of, but not limited to, the get tracking list operation 108 | */ 109 | public String[] asinList = null; 110 | 111 | /** 112 | * Estimated count of all matched products. 113 | */ 114 | public Integer totalResults = null; 115 | 116 | /** 117 | * A list of sellerIds. 118 | */ 119 | public String[] sellerIdList = null; 120 | 121 | /** 122 | * A list of lightning deals. 123 | */ 124 | public LightningDeal[] lightningDeals = null; 125 | 126 | /** 127 | * Contains information about any error that might have occurred. 128 | */ 129 | public RequestError error = null; 130 | 131 | /** 132 | * Contains request specific additional output. 133 | */ 134 | public String additional = null; 135 | 136 | /** 137 | * If the reqeust failed due to an Java exception (e.g. network error), this contains the exception object. 138 | */ 139 | public transient Exception exception; 140 | 141 | @Override 142 | public String toString() { 143 | if(status == KeepaAPI.ResponseStatus.OK) 144 | return gson.toJson(this); 145 | else 146 | return gsonPretty.toJson(this); 147 | } 148 | 149 | } 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Category.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | 4 | import static com.keepa.api.backend.helper.Utility.gsonPretty; 5 | 6 | public final class Category { 7 | 8 | /** 9 | * Integer value for the Amazon locale this category belongs to. 10 | * {@link AmazonLocale} 11 | */ 12 | public byte domainId; 13 | 14 | /** 15 | * The category node id used by Amazon. Represents the identifier of the category. Also part of the Product object's categories and rootCategory fields. Always a positive Long value. 16 | */ 17 | public long catId; 18 | 19 | /** 20 | * The name of the category. 21 | */ 22 | public String name; 23 | 24 | /** 25 | * The context free category name. 26 | */ 27 | public String contextFreeName; 28 | 29 | /** 30 | * The websiteDisplayGroup - available for most root categories. 31 | */ 32 | public String websiteDisplayGroup; 33 | 34 | /** 35 | * List of all sub categories. null or [] (empty array) if the category has no sub categories. 36 | */ 37 | public long[] children; 38 | 39 | /** 40 | * The parent category's Id. Always a positive Long value. If it is 0 the category is a root category and has no parent category. 41 | */ 42 | public long parent; 43 | 44 | /** 45 | * The highest (root category) sales rank we have observed of a product that is listed in this category. Note: Estimate, as the value is from the Keepa product database and not retrieved from Amazon. 46 | */ 47 | public int highestRank; 48 | 49 | /** 50 | * The lowest (root category) sales rank we have observed of a product that is listed in this category. Note: Estimate, as the value is from the Keepa product database and not retrieved from Amazon. 51 | */ 52 | public int lowestRank; 53 | 54 | /** 55 | * Number of products that are listed in this category. Note: Estimate, as the value is from the Keepa product database and not retrieved from Amazon. 56 | */ 57 | public int productCount; 58 | 59 | /** 60 | * Determines if this category functions as a standard browse node, rather than serving promotional purposes (for example, 'Specialty Stores'). 61 | */ 62 | public boolean isBrowseNode; 63 | 64 | /** 65 | * Average current buy box price of all products in this category. 66 | * Value is in the currency's smallest unit (e.g., cents for USD/EUR). May be null. 67 | */ 68 | public Integer avgBuyBox; 69 | 70 | /** 71 | * Average 90 day buy box price of all products in this category. 72 | * Value is in the currency's smallest unit (e.g., cents for USD/EUR). May be null. 73 | */ 74 | public Integer avgBuyBox90; 75 | 76 | /** 77 | * Average 365 day buy box price of all products in this category. 78 | * Value is in the currency's smallest unit (e.g., cents for USD/EUR). May be null. 79 | */ 80 | public Integer avgBuyBox365; 81 | 82 | /** 83 | * Average 30 day buy box deviation (standard deviation) of all products in this category. 84 | * Value is in the currency's smallest unit (e.g., cents for USD/EUR). May be null. 85 | */ 86 | public Integer avgBuyBoxDeviation; 87 | 88 | /** 89 | * Average number of reviews of all products in this category. May be null. 90 | */ 91 | public Integer avgReviewCount; 92 | 93 | /** 94 | * Average rating of all products in this category. 95 | * Value is multiplied by 10 (e.g., 45 means 4.5 stars). May be null. 96 | */ 97 | public Integer avgRating; 98 | 99 | /** 100 | * Percentage of products fulfilled by Amazon (FBA) in this category. May be null. 101 | * Represents the distribution of FBA vs. third-party sellers. 102 | */ 103 | public Float isFBAPercent; 104 | 105 | /** 106 | * Percentage of products sold directly by Amazon in this category. May be null. 107 | */ 108 | public Float soldByAmazonPercent; 109 | 110 | /** 111 | * Percentage of products that have an active coupon in this category. May be null. 112 | */ 113 | public Float hasCouponPercent; 114 | 115 | /** 116 | * Average number of new offers of all products in this category. May be null. 117 | */ 118 | public Float avgOfferCountNew; 119 | 120 | /** 121 | * Average number of used offers of all products in this category. May be null. 122 | */ 123 | public Float avgOfferCountUsed; 124 | 125 | /** 126 | * Number of distinct sellers with at least one active offer in this category. May be null. 127 | */ 128 | public Integer sellerCount; 129 | 130 | /** 131 | * Number of distinct brands present in this category. May be null. 132 | */ 133 | public Integer brandCount; 134 | 135 | 136 | @Override 137 | public String toString() { 138 | return gsonPretty.toJson(this); 139 | } 140 | } -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Deal.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | import static com.keepa.api.backend.helper.Utility.gson; 6 | 7 | /** 8 | * About: 9 | * A Deal object represents a product that has recently changed (usually in price or sales rank). It contains a summary of the product and information about the changes. 10 | *

11 | * Returned by: 12 | * The Deal object is returned by the Browsing Deals request. 13 | */ 14 | public class Deal { 15 | 16 | /** 17 | * The ASIN of the product 18 | */ 19 | public String asin = null; 20 | 21 | /** 22 | * The parent ASIN of the product 23 | */ 24 | public String parentAsin = null; 25 | 26 | /** 27 | * Title of the product. Caution: may contain HTML markup in rare cases. 28 | */ 29 | public String title = null; 30 | 31 | /** 32 | * Contains the absolute difference between the current value and the average value of the respective date range interval. 33 | * The value 0 means it did not change or could not be calculated. First dimension uses the Date Range indexing, second the Price Type indexing. 34 | *

First dimension uses {@link Product.CsvType}, second domension {@link DealInterval}

35 | */ 36 | public int[][] delta = null; 37 | 38 | /** 39 | * Same as {@link #delta}, but given in percent instead of absolute values. 40 | *

First dimension uses {@link Product.CsvType}, second domension {@link DealInterval}

41 | */ 42 | public short[][] deltaPercent = null; 43 | 44 | /** 45 | * Contains the absolute difference of the current and the previous price / rank. The value 0 means it did not change or could not be calculated. 46 | *

Uses {@link Product.CsvType} indexing

47 | */ 48 | public int[] deltaLast = null; 49 | 50 | /** 51 | * Contains the weighted averages in the respective date range and price type.
52 | * Note: The day interval (index 0) is actually the average of the last 48 hours, not 24 hours. This is due to the way our deals work. 53 | *

First dimension uses {@link Product.CsvType}, second domension {@link DealInterval}

54 | */ 55 | public int[][] avg = null; 56 | 57 | /** 58 | * Contains the prices / ranks of the product of the time we last updated it. Uses the Price Type indexing. 59 | * The price is an integer of the respective Amazon locale's smallest currency unit (e.g. euro cents or yen). 60 | * If no offer was available in the given interval (e.g. out of stock) the price has the value -1. 61 | * Shipping and Handling costs are not included. Amazon is considered to be part of the marketplace, so if 62 | * Amazon has the overall lowest new price, the marketplace new price in the corresponding time interval will 63 | * be identical to the Amazon price (except if there is only one marketplace offer). 64 | *

Uses {@link Product.CsvType} indexing

65 | */ 66 | public int[] current = null; 67 | 68 | /** 69 | * Category node id {@link Category#catId} of the product's root category. 0 or 9223372036854775807 if no root category known. 70 | */ 71 | public long rootCat = 0L; 72 | 73 | /** 74 | * States the time this deal was found, in Keepa Time minutes.
75 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 76 | */ 77 | public int creationDate = 0; 78 | 79 | /** 80 | * The name of the main product image of the product. Make sure you own the rights to use the image.
81 | * Each entry represents the integer of a US-ASCII (ISO646-US) coded character. Easiest way to convert it to a String in Javascript would be var imageName = String.fromCharCode.apply("", productObject.image);.
82 | * Example: [54,49,107,51,76,97,121,55,74,85,76,46,106,112,103], which equals "61k3Lay7JUL.jpg".
83 | * Full Amazon image path: https://m.media-amazon.com/images/I/_image name_ 84 | */ 85 | public byte[] image = null; 86 | 87 | /** 88 | * Array of Amazon category node ids {@link Category#catId} this product is listed in. Can be empty.
89 | * Example: [569604] 90 | */ 91 | public long[] categories = null; 92 | 93 | /** 94 | * States the last time we have updated the information for this deal, in Keepa Time minutes.
95 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 96 | */ 97 | public int lastUpdate = 0; 98 | 99 | /** 100 | * States the time this lightning deal is scheduled to end, in Keepa Time minutes. Only applicable to lightning deals. 0 otherwise.
101 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 102 | */ 103 | public int lightningEnd = 0; 104 | 105 | /** 106 | * Limit to products with a minimum rating (A rating is an integer from 0 to 50 (e.g. 45 = 4.5 stars)). 107 | * If -1 the filter is inactive. 108 | * Example: 20 (= min. rating of 2 stars) 109 | */ 110 | public int minRating = -1; 111 | 112 | /** 113 | * The {@link OfferCondition} condition of the cheapest warehouse deal of this product. Integer value: 114 | *
0 - Unknown: We were unable to determine the condition or this is not a warehouse deal in our data 115 | *
2 - Used - Like New 116 | *
3 - Used - Very Good 117 | *
4 - Used - Good 118 | *
5 - Used - Acceptable 119 | */ 120 | public byte warehouseCondition; 121 | 122 | /** 123 | * The offer comment of the cheapest warehouse deal of this product. null if no warehouse deal found in our data. 124 | */ 125 | public String warehouseConditionComment; 126 | 127 | /** 128 | * The timestamp indicating the starting point from which the current value has been in effect, in Keepa Time minutes. 129 | *

Uses {@link Product.CsvType} indexing.

130 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 131 | */ 132 | public int[] currentSince; 133 | 134 | 135 | /** 136 | * Available deal ranges. 137 | */ 138 | public enum DealInterval { 139 | DAY, 140 | WEEK, 141 | MONTH, 142 | _90_DAYS; 143 | public static final DealInterval[] values = DealInterval.values(); 144 | } 145 | 146 | @Override 147 | public String toString() { 148 | return gson.toJson(this); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Tracking.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | /** 6 | * Represents a Tracking Object 7 | 8 | "metaData": String, 9 | "thresholdValues": TrackingThresholdValue array, 10 | "notifyIf": TrackingNotifyIf array, 11 | "notificationType": Boolean array, 12 | "notificationCSV": Integer array, 13 | "individualNotificationInterval": Integer 14 | */ 15 | public class Tracking { 16 | 17 | /** 18 | * The tracked product ASIN 19 | */ 20 | public String asin = null; 21 | 22 | /** 23 | * Creation date of the tracking in {@link KeepaTime} minutes 24 | */ 25 | public int createDate; 26 | 27 | /** 28 | * The time to live in hours until the tracking expires and is deleted. When setting the value through the _Add Tracking_ request it is in relation to the time of request, 29 | * when retrieving the tracking object it is relative to the `createDate`. Possible values:
30 | *
any positive integer: time to live in hours
31 | * 0: never expires
32 | * any negative integer (only when setting the value):
33 | *
tracking already exists: keep the original `ttl`
tracking is new: use the absolute value as `ttl`
34 | *
35 | */ 36 | public int ttl; 37 | 38 | /** 39 | * Trigger a notification if tracking expires or is removed by the system (e.g. product deprecated) 40 | */ 41 | public boolean expireNotify; 42 | 43 | /** 44 | * The main Amazon locale of this tracking determines the currency used for all desired prices.
45 | * Integer value for the Amazon locale {@link AmazonLocale} 46 | */ 47 | public byte mainDomainId; 48 | 49 | /** 50 | * Contains all settings for price or value related tracking criteria 51 | */ 52 | public TrackingThresholdValue[] thresholdValues; 53 | 54 | /** 55 | * Contains specific, meta tracking criteria, like out of stock. 56 | */ 57 | public TrackingNotifyIf[] notifyIf; 58 | 59 | /** 60 | * Determines through which channels we will send notifications.
Must be a boolean array with the length of the NotificationType enum. 61 | * Uses NotificationType indexing {@link NotificationType}. True means the channel will be used.
62 | * Our Tracking API currently only supports notifications through push webhooks or API pull request. Other channels will follow soon.

63 | * Example: Only notify via API: [ false, false, false, false, false, true, false ]
64 | * 65 | * boolean[] notificationType = new boolean[Tracking.NotificationType.values.length];
66 | * notificationType[Tracking.NotificationType.API.ordinal()] = true; 67 | *
68 | */ 69 | public boolean[] notificationType; 70 | 71 | /** 72 | * A history of past notifications of this tracking. Each past notification consists of 5 entries, in the format:
73 | * [{@link AmazonLocale}, {@link Product.CsvType}, {@link NotificationType}, {@link TrackingNotificationCause}, {@link KeepaTime}] 74 | */ 75 | public int[] notificationCSV; 76 | 77 | /** 78 | * A tracking specific rearm timer.
79 | * -1 = use default notification timer of the user account (changeable via the website settings) 80 | * 0 = never notify a desired price more than once 81 | * larger than 0 = rearm the desired price after x minutes. 82 | */ 83 | public int individualNotificationInterval; 84 | 85 | /** 86 | * Whether or not the tracking is active. A tracking is automatically deactivated if the corresponding API account is no longer sufficiently paid for. 87 | */ 88 | public boolean isActive; 89 | 90 | /** 91 | * The update interval, in hours. Determines how often our system will trigger a product update. A setting of 1 hour will not trigger an update exactly every 60 minutes, but as close to that as it is efficient for our system. Throughout a day it will be updated 24 times, but the updates are not perfectly distributed. 92 | */ 93 | public int updateInterval; 94 | 95 | /** 96 | * The meta data of this tracking (max length is 500 characters). Used to assign some text to this tracking, like a user reference or a memo. 97 | */ 98 | public String metaData; 99 | 100 | /** 101 | * Available notification channels 102 | */ 103 | public enum NotificationType { 104 | EMAIL, TWITTER, FACEBOOK_NOTIFICATION, BROWSER, FACEBOOK_MESSENGER_BOT, API, MOBILE_APP, DUMMY; 105 | public static final NotificationType[] values = NotificationType.values(); 106 | 107 | } 108 | 109 | /** 110 | * The cause that triggered a notification 111 | */ 112 | public enum TrackingNotificationCause { 113 | EXPIRED, DESIRED_PRICE, PRICE_CHANGE, PRICE_CHANGE_AFTER_DESIRED_PRICE, OUT_STOCK, IN_STOCK, DESIRED_PRICE_AGAIN; 114 | public static final TrackingNotificationCause[] values = TrackingNotificationCause.values(); 115 | } 116 | 117 | 118 | /** 119 | * Available notification meta trigger types 120 | */ 121 | public enum NotifyIfType { 122 | OUT_OF_STOCK, BACK_IN_STOCK; 123 | public static final NotifyIfType[] values = NotifyIfType.values(); 124 | } 125 | 126 | /** 127 | * Represents a desired price - a {@link Product.CsvType} of a specific {@link AmazonLocale} to be monitored 128 | */ 129 | public static class TrackingThresholdValue { 130 | 131 | public TrackingThresholdValue(AmazonLocale domainId, Product.CsvType csvType, int thresholdValue, boolean isDrop, Integer minDeltaAbsolute, Integer minDeltaPercentage, Boolean deltasAreBetweenNotifications){ 132 | this.thresholdValue = thresholdValue; 133 | this.isDrop = isDrop; 134 | this.domain = (byte) domainId.ordinal(); 135 | this.csvType = csvType.index; 136 | this.minDeltaAbsolute = minDeltaAbsolute; 137 | this.minDeltaPercentage = minDeltaPercentage; 138 | this.deltasAreBetweenNotifications = deltasAreBetweenNotifications == null ? false : deltasAreBetweenNotifications; 139 | } 140 | /** 141 | * The history of threshold values (or desired prices). Only for existing tracking!
142 | * Format: [{@link KeepaTime}, value] 143 | */ 144 | public int[] thresholdValueCSV; 145 | 146 | /** 147 | * The threshold value (or desired price). Only for creating a tracking! 148 | */ 149 | public int thresholdValue; 150 | 151 | /** 152 | * Integer value of the {@link AmazonLocale} this threshold value belongs to. Regardless of the locale, the threshold value is always in the currency of the mainDomainId. 153 | */ 154 | public byte domain; 155 | 156 | /** 157 | * Integer value of the {@link Product.CsvType} for this threshold value 158 | */ 159 | public int csvType; 160 | 161 | /** 162 | * Whether or not this tracking threshold value tracks value drops (true) or value increases (false) 163 | */ 164 | public boolean isDrop; 165 | 166 | /** 167 | * not yet available. 168 | */ 169 | public Integer minDeltaAbsolute; 170 | 171 | /** 172 | * not yet available. 173 | */ 174 | public Integer minDeltaPercentage; 175 | 176 | /** 177 | * not yet available. 178 | */ 179 | public boolean deltasAreBetweenNotifications; 180 | } 181 | 182 | public static class TrackingNotifyIf { 183 | 184 | public TrackingNotifyIf(AmazonLocale domainId, Product.CsvType csvType, NotifyIfType notifyIfType){ 185 | this.domain = (byte) domainId.ordinal(); 186 | this.csvType = csvType.index; 187 | this.notifyIfType = notifyIfType.ordinal(); 188 | } 189 | 190 | /** 191 | * Integer value of the {@link AmazonLocale} for this NotifyIf 192 | */ 193 | public byte domain; 194 | 195 | /** 196 | * The {@link Product.CsvType} for this threshold value 197 | */ 198 | public int csvType; 199 | 200 | /** 201 | * The {@link NotifyIfType} 202 | */ 203 | public int notifyIfType; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Offer.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import static com.keepa.api.backend.helper.Utility.gson; 4 | 5 | /** 6 | * About: 7 | * The offer object represents a marketplace offer. 8 | *

9 | * Returned by: 10 | * The offer object is returned by the Product Request using the optional offers parameter and is part of the Product Object. 11 | *

12 | * Important to know: 13 | * It is impossible to update billions of marketplace offers on a regular basis. The product request's offers parameter determines how many offers we retrieve / update. We always fetch the best offers, as sorted by Amazon, in all conditions. If a product has more offers than requested, those will not be retrieved. 14 | * The order of offers constantly changes and we can retrieve a different amount of offers with each data retrieval. Because of this as well as the fact that we do keep a history of offers you will almost certainly encounter outdated offers. So the following is very important: 15 | *

16 | * Evaluate the lastSeen field - only process fresh and active offers if you are not interested in past offers. 17 | * The history of an offer (its past prices and shipping costs) is often not without gaps. Evaluate the EXTRA_INFO_UPDATES csv-type of the product object to find out when we updated the offers. If you need complete coverage of (all) offers of a product you have to request it on a regular basis. 18 | * If there are almost identical offers - same seller, same condition, same shipping type and same condition text - we only provide access to the one with the cheapest price. We do not list duplicates. 19 | */ 20 | public class Offer { 21 | 22 | /** 23 | * Unique id of this offer (in the scope of the product). 24 | * Not related to the offerIds used by Amazon, as those are user specific and only valid for a short time. 25 | * The offerId can be used to identify the same offers throughout requests. 26 | *

27 | * Example: 4 28 | */ 29 | public int offerId; 30 | 31 | /** 32 | * States the last time we have seen (and updated) this offer, in Keepa Time minutes. 33 | *

34 | * Example: 2700145 35 | */ 36 | public int lastSeen = 0; 37 | 38 | /** 39 | * The seller id of the merchant. 40 | *

41 | * Example: A2L77EE7U53NWQ (Amazon.com Warehouse Deals) 42 | */ 43 | public String sellerId = null; 44 | 45 | /** 46 | * Contains the current price and shipping costs of the offer as well as, if available, the offer's history. 47 | * It has the format Keepa time minutes, price, shipping cost, [...]. 48 | *

49 | * The price and shipping cost are integers of the respective Amazon locale's smallest currency unit (e.g. euro cents or yen). 50 | * If we were unable to determine the price or shipping cost they have the value -2. 51 | * Free shipping has the shipping cost of 0. 52 | * If an offer is not shippable or has unspecified shipping costs the shipping cost will be -1. 53 | * To get the newest price and shipping cost access the last two entries of the array.
54 | * Most recent price: offerCSV[offerCSV.length - 2]
55 | * Most recent shipping cost: offerCSV[offerCSV.length - 1] 56 | */ 57 | public int[] offerCSV = null; 58 | 59 | /** 60 | * The {@link OfferCondition} condition of the offered product. Integer value: 61 | *
0 - Unknown: We were unable to determine the condition. 62 | *
1 - New 63 | *
2 - Used - Like New 64 | *
3 - Used - Very Good 65 | *
4 - Used - Good 66 | *
5 - Used - Acceptable 67 | *
6 - Refurbished 68 | *
7 - Collectible - Like New 69 | *
8 - Collectible - Very Good 70 | *
9 - Collectible - Good 71 | *
10 - Collectible - Acceptable 72 | *
Note: Open Box conditions will be coded as Used conditions. 73 | */ 74 | public byte condition = 0; 75 | 76 | /** 77 | * The describing text of the condition. 78 | *

79 | * Example: The item may come repackaged. Small cosmetic imperfection on top, [...] 80 | */ 81 | public String conditionComment = null; 82 | 83 | /** 84 | * Whether this offer is available via Prime shipping. Can be used as a FBA ("Fulfillment by Amazon") indicator as well. 85 | */ 86 | public boolean isPrime; 87 | 88 | /** 89 | * If the price of this offer is hidden on Amazon due to a MAP ("minimum advertised price") restriction. 90 | * Even if so, the offer object will contain the price and shipping costs. 91 | */ 92 | public boolean isMAP; 93 | 94 | /** 95 | * Indicating whether the offer is currently shippable. 96 | * If not this could mean for example that it is temporarily out of stock or a pre-order. 97 | */ 98 | public boolean isShippable; 99 | 100 | /** 101 | * Indicating whether the offer is an Add-on item. 102 | */ 103 | public boolean isAddonItem; 104 | 105 | /** 106 | * Indicating whether the offer is a pre-order. 107 | */ 108 | public boolean isPreorder; 109 | 110 | /** 111 | * Indicating whether the offer is a Warehouse Deal. 112 | */ 113 | public boolean isWarehouseDeal; 114 | 115 | /** 116 | * Indicating whether our system identified that the offering merchant attempts to scam users. 117 | */ 118 | public boolean isScam; 119 | 120 | /** 121 | * Indicating whether the offer ships from China. 122 | */ 123 | public boolean shipsFromChina; 124 | 125 | /** 126 | * True if the seller is Amazon (e.g. "Amazon.com"). 127 | *

128 | * Note: Amazon's Warehouse Deals seller account or other accounts Amazon is maintaining under a different name are not considered to be Amazon. 129 | */ 130 | public boolean isAmazon; 131 | 132 | /** 133 | * Whether this offer is fulfilled by Amazon. 134 | */ 135 | public boolean isFBA; 136 | 137 | /** 138 | * This offer has a discounted Prime exclusive price. A Prime exclusive offer can only be ordered if the buyer has an active Prime subscription. 139 | */ 140 | public boolean isPrimeExcl; 141 | 142 | 143 | /** 144 | * Contains the Prime exclusive price history of this offer, if available. A Prime exclusive offer can only be ordered if the buyer has an active Prime subscription. 145 | * It has the format Keepa time minutes, price, [...]. 146 | *

147 | * Most recent Prime exclusive price: primeExclCSV[primeExclCSV.length - 1] 148 | */ 149 | public int[] primeExclCSV; 150 | 151 | /** 152 | * Contains the available stock of this offer as well as, if available, the stock's history. 153 | * It has the format Keepa time minutes, stock, [...]. 154 | *

155 | * Most recent stock: stockCSV[stockCSV.length - 1] 156 | */ 157 | public int[] stockCSV; 158 | 159 | /** 160 | * Minimum order quantity. 0 if unknown. 161 | */ 162 | public int minOrderQty; 163 | 164 | 165 | /** 166 | * Contains one-time coupon details of this offer. Undefined if none is available. 167 | * Positive integer for an absolute discount or negative for a percentage discount. 168 | * Example: 169 | * 500 - Coupon with a $5 discount. 170 | * -15 - Coupon with a 15% discount. 171 | */ 172 | public int coupon; 173 | 174 | /** 175 | * Contains the coupon history of this offer, if available. 176 | * It has the format Keepa time minutes, coupon, [...]. 177 | */ 178 | public int[] couponHistory; 179 | 180 | /** 181 | * An array that lists identical offers we detected for the same seller, condition, and shipping type that were excluded from the main offers list because they were not the cheapest. 182 | */ 183 | public OfferDuplicate[] offerDuplicates; 184 | 185 | 186 | public class OfferDuplicate { 187 | public int price; 188 | public String conditionComment; 189 | public int shipping; 190 | } 191 | 192 | public enum OfferCondition { 193 | UNKNOWN(0), 194 | NEW(1), 195 | USED_NEW(2), USED_VERY_GOOD(3), USED_GOOD(4), USED_ACCEPTABLE(5), 196 | REFURBISHED(6), 197 | COLLECTIBLE_NEW(7), COLLECTIBLE_VERY_GOOD(8), COLLECTIBLE_GOOD(9), COLLECTIBLE_ACCEPTABLE(10); 198 | 199 | public final byte code; 200 | public static final OfferCondition[] values = OfferCondition.values(); 201 | 202 | public static Product.CsvType getCorrespondingCsvType(OfferCondition oc) { 203 | Product.CsvType type = null; 204 | switch (oc) { 205 | case UNKNOWN: 206 | case NEW: 207 | break; 208 | case USED_NEW: 209 | type = Product.CsvType.USED_NEW_SHIPPING; 210 | break; 211 | case USED_VERY_GOOD: 212 | type = Product.CsvType.USED_VERY_GOOD_SHIPPING; 213 | break; 214 | case USED_GOOD: 215 | type = Product.CsvType.USED_GOOD_SHIPPING; 216 | break; 217 | case USED_ACCEPTABLE: 218 | type = Product.CsvType.USED_ACCEPTABLE_SHIPPING; 219 | break; 220 | case REFURBISHED: 221 | type = Product.CsvType.REFURBISHED_SHIPPING; 222 | break; 223 | case COLLECTIBLE_NEW: 224 | type = Product.CsvType.COLLECTIBLE_NEW_SHIPPING; 225 | break; 226 | case COLLECTIBLE_VERY_GOOD: 227 | type = Product.CsvType.COLLECTIBLE_VERY_GOOD_SHIPPING; 228 | break; 229 | case COLLECTIBLE_GOOD: 230 | type = Product.CsvType.COLLECTIBLE_GOOD_SHIPPING; 231 | break; 232 | case COLLECTIBLE_ACCEPTABLE: 233 | type = Product.CsvType.COLLECTIBLE_ACCEPTABLE_SHIPPING; 234 | break; 235 | } 236 | 237 | return type; 238 | } 239 | 240 | OfferCondition(int index) { 241 | code = (byte) index; 242 | } 243 | } 244 | 245 | @Override 246 | public String toString() { 247 | return gson.toJson(this); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/KeepaAPI.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend; 2 | 3 | import com.google.gson.stream.JsonReader; 4 | import com.keepa.api.backend.helper.BasicNameFactory; 5 | import com.keepa.api.backend.structs.Request; 6 | import com.keepa.api.backend.structs.Response; 7 | import org.jdeferred.Deferred; 8 | import org.jdeferred.Promise; 9 | import org.jdeferred.impl.DeferredObject; 10 | 11 | import javax.net.ssl.HttpsURLConnection; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.io.OutputStream; 16 | import java.net.URL; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | import java.util.zip.GZIPInputStream; 22 | 23 | import static com.keepa.api.backend.helper.Utility.gson; 24 | import static com.keepa.api.backend.helper.Utility.urlEncodeUTF8; 25 | 26 | public final class KeepaAPI { 27 | /** 28 | * Thread pool size determines degree of asynchronization. 29 | */ 30 | final private ExecutorService executorDeferred; 31 | final private ExecutorService executorRetry; 32 | 33 | final private String accessKey; 34 | final private String userAgent; 35 | final private int maxDelay = 60000; 36 | 37 | public enum ResponseStatus { 38 | PENDING, OK, FAIL, NOT_ENOUGH_TOKEN, REQUEST_REJECTED, NOT_FOUND, PAYMENT_REQUIRED, METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR 39 | } 40 | 41 | /** 42 | * @param key Your private API Access Token 43 | * @param threads Thread pool size determines degree of asynchronization. Higher thread count allows more requests in parallel to be made. Default 4 44 | */ 45 | public KeepaAPI(String key, int threads) { 46 | this(key, Executors.newFixedThreadPool(threads, new BasicNameFactory("KeepaAPI-%d")), Executors.newFixedThreadPool(threads, new BasicNameFactory("KeepaAPI-RetryScheduler"))); 47 | } 48 | 49 | /** 50 | * @param key Your private API Access Token 51 | * @param executorRetry provide a custom executor service for request retry attempts 52 | * @param executorDeferred provide a custom executor service for deferred request processing 53 | */ 54 | public KeepaAPI(String key, ExecutorService executorRetry, ExecutorService executorDeferred) { 55 | this.accessKey = key; 56 | String apiVersion = getClass().getPackage().getImplementationVersion(); 57 | if (apiVersion != null) { 58 | userAgent = "KEEPA-JAVA Framework-" + apiVersion; 59 | } else { 60 | userAgent = "KEEPA-JAVA Framework-"; 61 | } 62 | 63 | this.executorDeferred = executorDeferred; 64 | this.executorRetry = executorRetry; 65 | } 66 | 67 | /** 68 | * @param key Your private API Access Token 69 | */ 70 | public KeepaAPI(String key) { 71 | this(key, 4); 72 | } 73 | 74 | /** 75 | * Shutdown internal executor services (thread pools) 76 | * @param shutdownNow if true issue a shutdownNow() 77 | */ 78 | public void shutdown(boolean shutdownNow) { 79 | if(shutdownNow){ 80 | executorDeferred.shutdownNow(); 81 | executorRetry.shutdownNow(); 82 | } else { 83 | executorDeferred.shutdown(); 84 | executorRetry.shutdown(); 85 | } 86 | } 87 | 88 | /** 89 | * Issue a request to the Keepa Price Data API. 90 | * If your tokens are depleted, this method will fail. 91 | * 92 | * @param r the API Request {@link Request} 93 | * @return Promise for {@link Response} 94 | */ 95 | public Promise sendRequest(Request r) { 96 | return sendRequest(r, 30000, 120000); 97 | } 98 | 99 | /** 100 | * Issue a request to the Keepa Price Data API. 101 | * If your tokens are depleted, this method will fail. 102 | * 103 | * @param r the API Request {@link Request} 104 | * @param connectTimeout the timeout value, in milliseconds, to be used when opening a connection to the API 105 | * @param readTimeout the read timeout value, in milliseconds, for receiving an API response 106 | * @return Promise for {@link Response} 107 | */ 108 | public Promise sendRequest(Request r, int connectTimeout, int readTimeout) { 109 | Deferred d = new DeferredObject<>(); 110 | 111 | if(r == null){ 112 | d.reject(null); 113 | return d.promise(); 114 | } 115 | 116 | executorDeferred.execute(() -> { 117 | long responseTime = System.nanoTime(); 118 | Response response; 119 | 120 | String query = r.parameter.entrySet().stream() 121 | .map(p -> urlEncodeUTF8(p.getKey()) + "=" + urlEncodeUTF8(p.getValue())) 122 | .reduce((p1, p2) -> p1 + "&" + p2) 123 | .orElse(""); 124 | 125 | String url = "https://api.keepa.com/" + r.path + "?key=" + accessKey + "&" + query; 126 | 127 | try { 128 | URL obj = new URL(url); 129 | HttpsURLConnection con = (HttpsURLConnection) obj.openConnection(); 130 | con.setUseCaches(false); 131 | con.setRequestProperty("User-Agent", this.userAgent); 132 | con.setRequestProperty("Connection", "keep-alive"); 133 | con.setRequestProperty("Accept-Encoding", "gzip"); 134 | con.setConnectTimeout(connectTimeout); 135 | con.setReadTimeout(readTimeout); 136 | if (r.postData != null) { 137 | con.setRequestMethod("POST"); 138 | con.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); 139 | con.setDoOutput(true); 140 | try (OutputStream os = con.getOutputStream()) { 141 | os.write(r.postData.getBytes(StandardCharsets.UTF_8)); 142 | } 143 | } else 144 | con.setRequestMethod("GET"); 145 | 146 | int responseCode = con.getResponseCode(); 147 | 148 | if (responseCode == 200) { 149 | try (InputStream is = con.getInputStream(); 150 | GZIPInputStream gis = new GZIPInputStream(is)) { 151 | JsonReader reader = new JsonReader(new InputStreamReader(gis, "UTF-8")); 152 | response = gson.fromJson(reader, Response.class); 153 | response.status = ResponseStatus.OK; 154 | } catch (Exception e) { 155 | response = new Response(); 156 | response.status = ResponseStatus.FAIL; 157 | response.exception = e; 158 | } 159 | } else { 160 | try (InputStream is = con.getErrorStream(); 161 | GZIPInputStream gis = new GZIPInputStream(is)) { 162 | JsonReader reader = new JsonReader(new InputStreamReader(gis, "UTF-8")); 163 | response = gson.fromJson(reader, Response.class); 164 | } catch (Exception e) { 165 | response = new Response(); 166 | response.status = ResponseStatus.FAIL; 167 | response.exception = e; 168 | } 169 | 170 | response.statusCode = responseCode; 171 | 172 | switch (responseCode) { 173 | case 400: 174 | response.status = ResponseStatus.REQUEST_REJECTED; 175 | break; 176 | case 402: 177 | response.status = ResponseStatus.PAYMENT_REQUIRED; 178 | break; 179 | case 404: 180 | response.status = ResponseStatus.NOT_FOUND; 181 | break; 182 | case 405: 183 | response.status = ResponseStatus.METHOD_NOT_ALLOWED; 184 | break; 185 | case 429: 186 | response.status = ResponseStatus.NOT_ENOUGH_TOKEN; 187 | break; 188 | case 500: 189 | response.status = ResponseStatus.INTERNAL_SERVER_ERROR; 190 | break; 191 | default: 192 | if (response.status != ResponseStatus.FAIL) 193 | response.status = ResponseStatus.FAIL; 194 | break; 195 | } 196 | } 197 | } catch (IOException e) { 198 | response = new Response(); 199 | response.status = ResponseStatus.FAIL; 200 | response.exception = e; 201 | } 202 | 203 | response.requestTime = (System.nanoTime() - responseTime) / 1000000; 204 | if (response.status == ResponseStatus.OK) 205 | d.resolve(response); 206 | else 207 | d.reject(response); 208 | }); 209 | return d.promise(); 210 | } 211 | 212 | /** 213 | * Issue a request to the Keepa Price Data API. 214 | * If your API contingent is depleted, this method will retry the request as soon as there are new tokens available. May take minutes. 215 | * Will fail it the request failed too many times. 216 | * 217 | * @param r the API Request {@link Request} 218 | * @param connectTimeout the timeout value, in milliseconds, to be used when opening a connection to the API 219 | * @param readTimeout the read timeout value, in milliseconds, for receiving an API response 220 | * @return Promise for {@link Response} 221 | */ 222 | public Promise sendRequestWithRetry(Request r, int connectTimeout, int readTimeout) { 223 | Deferred deferred = new DeferredObject<>(); 224 | AtomicInteger expoDelay = new AtomicInteger(0); 225 | 226 | executorRetry.execute(() -> { 227 | int retry = 0; 228 | Response[] lastResponse = {null}; 229 | final boolean[] solved = {false}; 230 | 231 | try { 232 | int delay = 0; 233 | 234 | while (!solved[0]) { 235 | if (lastResponse[0] != null && lastResponse[0].status == ResponseStatus.NOT_ENOUGH_TOKEN && lastResponse[0].refillIn > 0) 236 | delay = lastResponse[0].refillIn + 100; 237 | 238 | if (retry > 0) 239 | Thread.sleep(delay); 240 | 241 | Promise p1 = sendRequest(r, connectTimeout, readTimeout); 242 | p1 243 | .done(result -> { 244 | deferred.resolve(result); 245 | solved[0] = true; 246 | expoDelay.set(0); 247 | }) 248 | .fail(result -> { 249 | lastResponse[0] = result; 250 | switch (result.status) { 251 | case FAIL: 252 | case NOT_ENOUGH_TOKEN: // retry 253 | break; 254 | default: 255 | deferred.reject(result); 256 | solved[0] = true; 257 | } 258 | }) 259 | .waitSafely(); 260 | 261 | if (p1.isRejected()) { 262 | retry++; 263 | delay = expoDelay.getAndUpdate(operand -> Math.min(2 * operand + 100, maxDelay)); 264 | } 265 | } 266 | } catch (InterruptedException e) { 267 | Thread.currentThread().interrupt(); 268 | deferred.reject(null); 269 | } 270 | }); 271 | 272 | return deferred.promise(); 273 | } 274 | 275 | /** 276 | * Issue a request to the Keepa Price Data API. 277 | * If your API contingent is depleted, this method will retry the request as soon as there are new tokens available. May take minutes. 278 | * Will fail it the request failed too many times. 279 | * 280 | * @param r the API Request {@link Request} 281 | * @return Promise for {@link Response} 282 | */ 283 | public Promise sendRequestWithRetry(Request r) { 284 | return sendRequestWithRetry(r, 30000, 120000); 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Seller.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | import static com.keepa.api.backend.helper.Utility.gson; 6 | 7 | /** 8 | * About: 9 | * The seller object provides information about a Amazon marketplace seller. 10 | * Returned by: 11 | * The seller object is returned by the following request: 12 | * Request Seller Information 13 | */ 14 | public class Seller { 15 | 16 | /** 17 | * States the time we have started tracking this seller, in Keepa Time minutes. 18 | *

Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time).

19 | */ 20 | public int trackedSince; 21 | 22 | /** 23 | * The domainId of the products Amazon locale 24 | * {@link AmazonLocale} 25 | */ 26 | public byte domainId; 27 | 28 | /** 29 | * The seller id of the merchant. 30 | *

31 | * Example: A2L77EE7U53NWQ (Amazon.com Warehouse Deals) 32 | */ 33 | public String sellerId; 34 | 35 | /** 36 | * The name of seller. 37 | *

38 | * Example: Amazon Warehouse Deals 39 | */ 40 | public String sellerName; 41 | 42 | /** 43 | * Two dimensional history array that contains history data for this seller. First dimension index: 44 | *

{@link MerchantCsvType}

45 | * 0 - RATING: The merchant's rating in percent, Integer from 0 to 100. 46 | * 1 - RATING_COUNT: The merchant's total rating count, Integer. 47 | */ 48 | public int[][] csv; 49 | 50 | /** 51 | * States the time of our last update of this seller, in Keepa Time minutes. 52 | *

Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time).

53 | */ 54 | public int lastUpdate; 55 | 56 | /** 57 | * Indicating whether or not our system identified that this seller attempts to scam users. 58 | */ 59 | public boolean isScammer; 60 | 61 | /** 62 | * Indicating whether or not this seller ships from China. 63 | */ 64 | public boolean shipsFromChina; 65 | 66 | /** 67 | * Boolean value indicating whether or not the seller currently has FBA listings.
68 | * This value is usually correct, but could be set to false even if the seller has FBA listings, since we are not always aware of all
69 | * seller listings. This can especially be the case with sellers with only a few listings consisting of slow-selling products. 70 | */ 71 | public boolean hasFBA; 72 | 73 | /** 74 | * Contains the number of storefront ASINs if available and the last update of that metric.
75 | * Is null if not available (no storefront was ever retrieved). This field is available in the
76 | * default Request Seller Information (storefront parameter is not required).
77 | *

Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time).


78 | * Has the format: [ last update of the storefront in Keepa Time minutes, the count of storefront ASINs ]

79 | * Example: [2711319, 1200] 80 | */ 81 | public int[] totalStorefrontAsins = null; 82 | 83 | /** 84 | * Only available if the storefront parameter was used and only updated if the update parameter was utilized.

85 | * String array containing up to 100,000 storefront ASINs, sorted by freshest first. The corresponding
86 | * time stamps can be found in the asinListLastSeen field.
87 | * Example: ["B00M0QVG3W", "B00M4KCH2A"] 88 | */ 89 | public String[] asinList; 90 | 91 | /** 92 | * Only available if the storefront parameter was used and only updated if the update parameter was utilized.

93 | * Contains the last time (in Keepa Time minutes) we were able to verify each ASIN in the _asinList_ field.
94 | * asinList and asinListLastSeen share the same indexation, so the corresponding time stamp
95 | * for `asinList[10]` would be `asinListLastSeen[10]`. 96 | *

Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time).

97 | *
98 | * Example: [2711319, 2711311] 99 | */ 100 | public int[] asinListLastSeen; 101 | 102 | /** 103 | * Only available if the storefront parameter was used and only updated if the update parameter was utilized.

104 | * Contains the total amount of listings of this seller. Includes historical data
105 | * asinList and asinListLastSeen share the same indexation, so the corresponding time stamp
106 | * for `asinList[10]` would be `asinListLastSeen[10]`. Has the format: Keepa Time minutes, count, ... 107 | *

Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time).

108 | *
109 | * Example: [2711319, 1200, 2711719, 1187] 110 | */ 111 | public int[] totalStorefrontAsinsCSV; 112 | 113 | /** 114 | * Statistics about the primary categories of this seller. Based on our often incomplete and outdated product offers data. 115 | */ 116 | public MerchantCategoryStatistics[] sellerCategoryStatistics = null; 117 | 118 | /** 119 | * Statistics about the primary brands of this seller. Based on our often incomplete and outdated product offers data. 120 | */ 121 | public MerchantBrandStatistics[] sellerBrandStatistics = null; 122 | 123 | /** 124 | * Average number of sellers competing for the Buy Box of this seller's products (this seller included). 125 | */ 126 | public float avgBuyBoxCompetitors; 127 | 128 | /** 129 | * Average New Buy Box ownership percentage 130 | */ 131 | public int buyBoxNewOwnershipRate; 132 | 133 | /** 134 | * Average Used Buy Box ownership percentage 135 | */ 136 | public int buyBoxUsedOwnershipRate; 137 | 138 | /** 139 | * The top five sellers most commonly offering the same products as this seller. 140 | */ 141 | public Competitors[] competitors; 142 | 143 | /** 144 | * The business address. Each entry of the array contains one address line. 145 | * The last entry contains the 2 letter country code. null if not available. 146 | * Example: [123 Main Street, New York, NY, 10001, US] 147 | */ 148 | public String[] address; 149 | 150 | /** 151 | * Contains up to 5 of the most recent customer feedbacks. 152 | * Each feedback object in the array contains the following fields 153 | */ 154 | public FeedbackObject[] recentFeedback; 155 | 156 | /** 157 | * States the time of our last rating data update of this seller, in Keepa Time minutes. 158 | *

Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time).

159 | */ 160 | public int lastRatingUpdate; 161 | 162 | /** 163 | * Contains the neutral percentage ratings for the last 30 days, 90 days, 365 days and lifetime, in that order. 164 | * A neutral rating is a 3 star rating. 165 | * Example: [1, 1, 1, 2] 166 | */ 167 | public int[] neutralRating = null; 168 | 169 | /** 170 | * Contains the negative percentage ratings for the last 30 days, 90 days, 365 days and lifetime, in that order. 171 | * A negative rating is a 1 or 2 star rating. 172 | * Example: [3, 1, 1, 3] 173 | */ 174 | public int[] negativeRating = null; 175 | 176 | /** 177 | * Contains the positive percentage ratings for the last 30 days, 90 days, 365 days and lifetime, in that order. 178 | * A positive rating is a 4 or 5 star rating. 179 | * Example: [96, 98, 98, 95] 180 | */ 181 | public int[] positiveRating = null; 182 | 183 | /** 184 | * Contains the rating counts for the last 30 days, 90 days, 365 days and lifetime, in that order. 185 | * Example: [3, 10, 98, 321] 186 | */ 187 | public int[] ratingCount = null; 188 | 189 | /** 190 | * The customer services address. Each entry of the array contains one address line. 191 | * The last entry contains the 2 letter country code. null if not available. 192 | * Example: [123 Main Street, New York, NY, 10001, US] 193 | */ 194 | public String[] customerServicesAddress; 195 | 196 | /** 197 | * The Trade Register Number. null if not available. 198 | * Example: HRB 123 456 199 | */ 200 | public String tradeNumber; 201 | 202 | /** 203 | * The business name. null if not available. 204 | * Example: Keepa GmbH 205 | */ 206 | public String businessName; 207 | 208 | /** 209 | * The VAT number. null if not available. 210 | * Example: DE123456789 211 | */ 212 | public String vatID; 213 | 214 | /** 215 | * The phone number. null if not available. 216 | * Example: 800 1234 567 217 | */ 218 | public String phoneNumber; 219 | 220 | /** 221 | * The business type. null if not available. 222 | * Example: Unternehmen in Privatbesitz 223 | */ 224 | public String businessType; 225 | 226 | /** 227 | * The share capital. null if not available. 228 | * Example: 25000 229 | */ 230 | public String shareCapital; 231 | 232 | /** 233 | * The name of the business representative. null if not available. 234 | * Example: Max Mustermann 235 | */ 236 | public String representative; 237 | 238 | /** 239 | * The email address of the business. null if not available. 240 | * Example: info@keepa.com 241 | */ 242 | public String email; 243 | 244 | 245 | public int currentRating; 246 | public int currentRatingCount; 247 | public int ratingsLast30Days; 248 | 249 | public enum MerchantCsvType { 250 | RATING(0, false), 251 | RATING_COUNT(1, false); 252 | 253 | /** 254 | * If the values are prices. 255 | */ 256 | public final boolean isPrice; 257 | 258 | public final int index; 259 | public static final MerchantCsvType[] values = MerchantCsvType.values(); 260 | 261 | MerchantCsvType(int i, boolean price) { 262 | index = i; 263 | isPrice = price; 264 | } 265 | 266 | public static MerchantCsvType getCSVTypeFromIndex(int index) { 267 | for (MerchantCsvType type : MerchantCsvType.values) { 268 | if (type.index == index) return type; 269 | } 270 | return RATING; 271 | } 272 | } 273 | 274 | public static class MerchantBrandStatistics { 275 | 276 | /** 277 | * the brand (in all lower-case) 278 | */ 279 | public String brand; 280 | 281 | /** 282 | * the number of products this merchant sells with this brand 283 | */ 284 | public int productCount; 285 | 286 | /** 287 | * the 30 day average sales rank of these products 288 | */ 289 | public int avg30SalesRank; 290 | 291 | /** 292 | * how many of these products have an Amazon offer 293 | */ 294 | public int productCountWithAmazonOffer; 295 | } 296 | 297 | public static class Competitors { 298 | /** 299 | * The sellerId of the competitor, in lowercase. 300 | */ 301 | public String sellerId; 302 | 303 | /** 304 | * The percentage of listings this competitor shares with the seller. 305 | */ 306 | public int percent; 307 | } 308 | 309 | public static class MerchantCategoryStatistics { 310 | 311 | /** 312 | * the category id 313 | */ 314 | public long catId; 315 | 316 | /** 317 | * the number of products this merchant sells with this category 318 | */ 319 | public int productCount; 320 | 321 | /** 322 | * the 30 day average sales rank of these products 323 | */ 324 | public int avg30SalesRank; 325 | 326 | /** 327 | * how many of these products have an Amazon offer 328 | */ 329 | public int productCountWithAmazonOffer; 330 | } 331 | 332 | public static class FeedbackObject { 333 | /** 334 | * the feedback star rating - value range from 10 (1 star) to 50 (5 stars) 335 | */ 336 | public int rating; 337 | 338 | /** 339 | * timestamp of the feedback, in Keepa Time minutes 340 | */ 341 | public int date; 342 | 343 | /** 344 | * the feedback text 345 | */ 346 | public String feedback = null; 347 | 348 | /** 349 | * whether or not the feedback is striked 350 | */ 351 | public boolean isStriked; 352 | } 353 | 354 | @Override 355 | public String toString() { 356 | return gson.toJson(this); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/DealRequest.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import static com.keepa.api.backend.helper.Utility.gsonPretty; 4 | 5 | /** 6 | * Required by the Browsing Deals request. 7 | * The queryJSON contains all request parameters. It must be URL encoded if the GET format is used. To quickly get a valid queryJSON there is a link on the deals page26 below the filters that generates this JSON for the current selection. 8 | */ 9 | public class DealRequest { 10 | 11 | /** 12 | * Most deal queries have more than 150 results (which is the maximum page size). To browse all deals found by a query (which is cached for 120 seconds) you iterate the page parameter and keep all other parameters identical. You start with page 0 and stop when the response contains less than 150 results. 13 | */ 14 | public int page; 15 | 16 | /** 17 | * The domainId of the products Amazon locale {@link AmazonLocale} 18 | */ 19 | public int domainId; 20 | 21 | /** 22 | * Determines the type of the deal. Even though it is an integer array, it can contain only one entry. Multiple types per query are not yet supported. 23 | * Uses {@link Product.CsvType} coding. Only those applicable with {@link Product.CsvType#isDealRelevant} set to true. 24 | */ 25 | public int[] priceTypes; 26 | 27 | /** 28 | * Include only products for which the specified price type is at its lowest value (since tracking began). 29 | */ 30 | public boolean isLowest; 31 | 32 | /** 33 | * Include only products for which the specified price type is at its lowest value in the past 90 days. 34 | */ 35 | public boolean isLowest90; 36 | 37 | /** 38 | * Include only products if the selected price type is the lowest of all New offers (applicable to Amazon and Marketplace New). 39 | */ 40 | public boolean isLowestOffer; 41 | 42 | /** 43 | * Our deals are devided in different sets, determined by the time interval in which the product changed. The shorter the interval, the more recent the change; which is good for big price drops but bad for slow incremental drops that accumulate over a longer period. 44 | * For most deals the shorter intervals can be considered as subsets of the longer intervals. To find more deals use the longer intervals.
45 | *

Possible values: 46 | *

    47 | *
  • 0 - day: the last 24 hours
  • 48 | *
  • 1 - week: the last 24 * 7 hours
  • 49 | *
  • 2 - month: the last 24 * 31 hours
  • 50 | *
  • 3 - 90 days: the last 24 * 90 hours
  • 51 | *

52 | */ 53 | public int dateRange; 54 | 55 | /** 56 | * Limit the range of the absolute difference between the current value and the one at the beginning of the chosen dateRange interval.
57 | * Example: [0,999] (= no minimum difference, maximum difference of $9.99). 58 | */ 59 | public int[] deltaRange; 60 | 61 | /** 62 | * Used to exclude products that are listed in these categories. If it is a sub category the product must be directly listed in this category. It will not exclude products in child categories of the specified ones, unless it is a root category. 63 | * Array with category node ids14. 64 | */ 65 | public long[] excludeCategories; 66 | 67 | /** 68 | * Used to only include products that are listed in these categories. If it is a sub category the product must be directly listed in this category. It will not include products in child categories of the specified ones, unless it is a root category. Array with category node ids14. 69 | */ 70 | public long[] includeCategories; 71 | 72 | /** 73 | * Same as deltaRange, but given in percent instead of absolute values. Minimum percent is 10, for Sales Rank it is 80.
74 | * Example: [30,80] (= between 30% and 80%). 75 | */ 76 | public int[] deltaPercentRange; 77 | 78 | /** 79 | * Limit the range of the current value of the price type.
80 | * Example: [105,50000] (= minimum price $1.05, maximum price $500, or a rank between 105 and 50000). 81 | */ 82 | public int[] currentRange; 83 | 84 | /** 85 | * Select deals by a keywords based product title search. The search is case-insensitive and supports multiple keywords which, if specified and separated by a space, must all match.
86 | * Examples: 87 | * "samsung galaxy" matches all products which have the character sequences "samsung" AND "galaxy" within their title 88 | */ 89 | public String titleSearch; 90 | 91 | /** 92 | * Only include products which were out of stock within the last 24 hours and can now be ordered. 93 | */ 94 | public boolean isBackInStock; 95 | 96 | /** 97 | * Only include products which were available to order within the last 24 hours and now out of stock. 98 | */ 99 | public boolean isOutOfStock; 100 | 101 | /** 102 | * Switch to enable the range options. 103 | */ 104 | public boolean isRangeEnabled; 105 | 106 | /** 107 | * Excludes all products that are listed as adult items. 108 | */ 109 | public boolean filterErotic; 110 | 111 | /** 112 | * Determines the sort order of the retrieved deals. To invert the sort order use negative values. 113 | *

Possible sort by values: 114 | *

    115 | *
  • 1 - deal age. Newest deals first, not invertible.
  • 116 | *
  • 2 - absolute delta. (the difference between the current value and the one at the beginning of the chosen dateRange interval). Sort order is highest delta to lowest.
  • 117 | *
  • 3 - sales rank. Sort order is lowest rank o highest.
  • 118 | *
  • 4 - percentage delta (the percentage difference between the current value and the one at the beginning of the chosen dateRange interval). Sort order is highest percent to lowest.
  • 119 | *

120 | */ 121 | public int sortType; 122 | 123 | /** 124 | * Limit the Sales Rank range of the product. Identical to currentRange if price type is set to Sales Rank. If you want to keep the upper bound open, you can specify -1 (which will translate to the maximum signed integer value).
125 | *

Important note: Once this range option is used all products with no Sales Rank will be excluded. Set it to null to not use it.

126 | * Examples:
127 | * [0,5000] (= only products which have a sales rank between 0 and 5000)
128 | * [5000,-1] (= higher than 5000) 129 | */ 130 | public int[] salesRankRange; 131 | 132 | /** 133 | * Switch to enable the filter options. 134 | */ 135 | public boolean isFilterEnabled; 136 | 137 | /** 138 | * Limit the range of the absolute difference between the current value and previous one.
139 | * Example: [100,500] (= last change between one $1 and $5) 140 | */ 141 | public int[] deltaLastRange; 142 | 143 | /** 144 | * If true excludes all products with no reviews. If false the filter is inactive. 145 | */ 146 | public boolean hasReviews; 147 | 148 | /** 149 | * Include only products flagged as Prime Exclusive. 150 | *

Example: {@code true}

151 | */ 152 | public boolean isPrimeExclusive; 153 | 154 | /** 155 | * Include only products that currently have an offer sold and fulfilled by Amazon. 156 | *

Example: {@code true}

157 | */ 158 | public boolean mustHaveAmazonOffer; 159 | 160 | /** 161 | * Include only products that currently have no offer sold and fulfilled by Amazon. 162 | *

Example: {@code true}

163 | */ 164 | public boolean mustNotHaveAmazonOffer; 165 | 166 | /** 167 | * Minimum product rating to include. 168 | *
    169 | *
  • Integer from 0 to 50 (e.g., 45 = 4.5 stars)
  • 170 | *
  • Use -1 to disable the filter
  • 171 | *
172 | *

Example: {@code 20} // ≥ 2.0 stars

173 | */ 174 | public Integer minRating; 175 | 176 | /** 177 | * Include only Amazon Warehouse deals that match these condition codes. 178 | *

Use integer-coded conditions such as: 179 | * 1 = New, 2 = Used - Like New, 3 = Used - Very Good, 24 = Used - Good, 5 = Used - Acceptable.

180 | *

Example: {@code [1, 2]}

181 | *

Note: API expects integers; choose a numeric type that fits your model.

182 | */ 183 | public Byte[] warehouseConditions; 184 | 185 | /** 186 | * If multiple variations match, return only a single (random) variation. 187 | *

Example: {@code true}

188 | */ 189 | public Boolean singleVariation; 190 | 191 | /** 192 | * Include only products made of the specified materials (e.g., "cotton"). 193 | *

Example: {@code ["cotton", "polyester"]}

194 | */ 195 | public String[] material; 196 | 197 | /** 198 | * Include only products matching the specified type (e.g., "shirt", "dress"). 199 | *

Example: {@code ["shirt"]}

200 | */ 201 | public String[] type; 202 | 203 | /** 204 | * Include only products from the specified manufacturer. 205 | *

Example: {@code ["Sony"]}

206 | */ 207 | public String[] manufacturer; 208 | 209 | /** 210 | * Include only products from the specified brand. 211 | *

Example: {@code ["Apple", "Samsung"]}

212 | */ 213 | public String[] brand; 214 | 215 | /** 216 | * Include only products in the specified Amazon product group (e.g., "home", "book"). 217 | *

Example: {@code ["book"]}

218 | */ 219 | public String[] productGroup; 220 | 221 | /** 222 | * Include only products matching the specified model identifier. 223 | *

Example: {@code ["A2179"]}

224 | */ 225 | public String[] model; 226 | 227 | /** 228 | * Include only products matching the specified color attribute. 229 | *

Example: {@code ["black", "navy"]}

230 | */ 231 | public String[] color; 232 | 233 | /** 234 | * Include only products matching the specified size (e.g., "small", "one size"). 235 | *

Example: {@code ["M", "L"]}

236 | */ 237 | public String[] size; 238 | 239 | /** 240 | * Include only products with the specified unit type (e.g., "count", "ounce"). 241 | *

Example: {@code ["count"]}

242 | */ 243 | public String[] unitType; 244 | 245 | /** 246 | * Include only products with the specified scent (e.g., "lavender", "citrus"). 247 | *

Example: {@code ["lavender"]}

248 | */ 249 | public String[] scent; 250 | 251 | /** 252 | * Include only products matching the specified item form (e.g., "liquid", "sheet"). 253 | *

Example: {@code ["liquid"]}

254 | */ 255 | public String[] itemForm; 256 | 257 | /** 258 | * Include only products matching the specified pattern (e.g., "striped", "solid"). 259 | *

Example: {@code ["striped"]}

260 | */ 261 | public String[] pattern; 262 | 263 | /** 264 | * Include only products matching the specified style attribute (e.g., "modern", "vintage"). 265 | *

Example: {@code ["modern"]}

266 | */ 267 | public String[] style; 268 | 269 | /** 270 | * Include only products matching the specified item type keyword (custom search term). 271 | *

Example: {@code ["books", "prints"]}

272 | */ 273 | public String[] itemTypeKeyword; 274 | 275 | /** 276 | * Include only products targeting the specified audience (e.g., "kids", "professional"). 277 | *

Example: {@code ["kids"]}

278 | */ 279 | public String[] targetAudienceKeyword; 280 | 281 | /** 282 | * Include only products matching the specified edition (e.g., "first edition", "standard edition"). 283 | *

Example: {@code ["first edition"]}

284 | */ 285 | public String[] edition; 286 | 287 | /** 288 | * Include only products in the specified format (e.g., "kindle ebook", "import", "dvd"). 289 | *

Example: {@code ["paperback"]}

290 | */ 291 | public String[] format; 292 | 293 | /** 294 | * Include only products by the specified author (applicable to books, music, etc.). 295 | *

Example: {@code ["Neil Gaiman"]}

296 | */ 297 | public String[] author; 298 | 299 | /** 300 | * Include only products with the specified binding type (e.g., "paperback"). 301 | *

Example: {@code ["hardcover"]}

302 | */ 303 | public String[] binding; 304 | 305 | /** 306 | * Include only products available in the specified languages (use language names). 307 | *

Example: {@code ["English", "German"]}

308 | */ 309 | public String[] languages; 310 | 311 | /** 312 | * Include only products sold under the specified Brand Store name on Amazon. 313 | *

Example: {@code ["Amazon Basics"]}

314 | */ 315 | public String[] brandStoreName; 316 | 317 | /** 318 | * Include only products sold under the specified URL-friendly Brand Store identifier. 319 | *

Example: {@code ["amazonbasics"]}

320 | */ 321 | public String[] brandStoreUrlName; 322 | 323 | /** 324 | * Include only products in the specified website display group. 325 | *

Example: {@code ["fashion_display_on_website"]}

326 | */ 327 | public String[] websiteDisplayGroup; 328 | 329 | /** 330 | * Include only products in the specified website display group name (user-friendly label). 331 | *

Example: {@code ["Fashion"]}

332 | */ 333 | public String[] websiteDisplayGroupName; 334 | 335 | /** 336 | * Include only products belonging to the specified sales rank display group 337 | * (e.g., "fashion_display_on_website"). 338 | *

Example: {@code ["fashion_display_on_website"]}

339 | */ 340 | public String[] salesRankDisplayGroup; 341 | 342 | 343 | @Override 344 | public String toString() { 345 | return gsonPretty.toJson(this); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/helper/ProductAnalyzer.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.helper; 2 | 3 | import static com.keepa.api.backend.structs.Product.CsvType; 4 | 5 | /** 6 | * Provides methods to work on the Keepa price history CSV format. 7 | */ 8 | public class ProductAnalyzer { 9 | 10 | /** 11 | * finds the extreme point in the specified interval 12 | * 13 | * @param csv value/price history csv 14 | * @param start start of the interval (keepa time minutes), can be 0. 15 | * @param end end of the interval (keepa time minutes), can be in the future (Integer.MAX_VALUE). 16 | * @param type the type of the csv data. If the csv includes shipping costs the extreme point will be the landing price (price + shipping). 17 | * @return extremePoints (time, lowest value/price, time, highest value/price) in the given interval or -1 if no extreme point was found. If the csv includes shipping costs it will be the landing price (price + shipping). 18 | */ 19 | public static int[] getExtremePointsInIntervalWithTime(int[] csv, int start, int end, CsvType type) { 20 | if (csv == null || start >= end || csv.length < (type.isWithShipping ? 6 : 4)) 21 | return new int[]{-1, -1, -1, -1}; 22 | 23 | int[] extremeValue = new int[]{-1, Integer.MAX_VALUE, -1, -1}; 24 | 25 | int lastTime = getLastTime(csv, type); 26 | int firstTime = csv[0]; 27 | if (lastTime == -1 || firstTime == -1 || firstTime > end) return new int[]{-1, -1, -1, -1}; 28 | 29 | if (firstTime > start) 30 | start = firstTime; 31 | 32 | int loopIncrement = (type.isWithShipping ? 3 : 2); 33 | int adjustedIndex = type.isWithShipping ? 2 : 1; 34 | 35 | for (int i = 1, j = csv.length; i < j; i += loopIncrement) { 36 | int c = csv[i]; 37 | int date = csv[i - 1]; 38 | if (date >= end) 39 | break; 40 | 41 | if (c != -1) { 42 | if (type.isWithShipping) { 43 | int s = csv[i + 1]; 44 | c += s < 0 ? 0 : s; 45 | } 46 | 47 | if (date >= start) { 48 | if (c < extremeValue[1]) { 49 | extremeValue[1] = c; 50 | extremeValue[0] = csv[i - 1]; 51 | } 52 | 53 | if (c > extremeValue[3]) { 54 | extremeValue[3] = c; 55 | extremeValue[2] = csv[i - 1]; 56 | } 57 | } else { 58 | boolean isValid = false; 59 | if (i == j - adjustedIndex) { 60 | isValid = true; 61 | } else { 62 | int nextDate = csv[i + adjustedIndex]; 63 | if (nextDate >= end || (nextDate >= start)) 64 | isValid = true; 65 | } 66 | 67 | if (isValid) { 68 | if (c < extremeValue[1]) { 69 | extremeValue[1] = c; 70 | extremeValue[0] = start; 71 | } 72 | 73 | if (c > extremeValue[3]) { 74 | extremeValue[3] = c; 75 | extremeValue[2] = start; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | if (extremeValue[1] == Integer.MAX_VALUE) return new int[]{-1, -1, -1, -1}; 83 | return extremeValue; 84 | } 85 | 86 | /** 87 | * Get the last value/price change. 88 | * 89 | * @param csv value/price history csv 90 | * @param type the type of the csv data. If the csv includes shipping costs the extreme point will be the landing price (price + shipping). 91 | * @return the last value/price change delta. If the csv includes shipping costs it will be the delta of the the landing prices (price + shipping). 92 | */ 93 | private static int getDeltaLast(int[] csv, CsvType type) { 94 | if (type.isWithShipping) { 95 | if (csv == null || csv.length < 6 || csv[csv.length - 1] == -1 || csv[csv.length - 5] == -1) 96 | return 0; 97 | 98 | int v = csv[csv.length - 5]; 99 | int s = csv[csv.length - 4]; 100 | int totalLast = v < 0 ? v : v + (s < 0 ? 0 : s); 101 | 102 | v = csv[csv.length - 2]; 103 | s = csv[csv.length - 1]; 104 | int totalCurrent = v < 0 ? v : v + (s < 0 ? 0 : s); 105 | 106 | return totalCurrent - totalLast; 107 | } else { 108 | if (csv == null || csv.length < 4 || csv[csv.length - 1] == -1 || csv[csv.length - 3] == -1) 109 | return 0; 110 | 111 | return csv[csv.length - 1] - csv[csv.length - 3]; 112 | } 113 | } 114 | 115 | /** 116 | * Get the last value/price. 117 | * 118 | * @param csv value/price history csv 119 | * @param type the type of the csv data. 120 | * @return the last value/price. If the csv includes shipping costs it will be the landing price (price + shipping). 121 | */ 122 | public static int getLast(int[] csv, CsvType type) { 123 | if (csv == null || csv.length == 0) return -1; 124 | 125 | if (type.isWithShipping) { 126 | int s = csv[csv.length - 1]; 127 | int v = csv[csv.length - 2]; 128 | return v < 0 ? v : v + (s < 0 ? 0 : s); 129 | } 130 | 131 | return csv[csv.length - 1]; 132 | } 133 | 134 | /** 135 | * Get the time (keepa time minutes) of the last entry. This does not correspond to the last update time, but to the last time we registered a price/value change. 136 | * 137 | * @param csv value/price history csv 138 | * @param type the type of the csv data. 139 | * @return keepa time minutes of the last entry 140 | */ 141 | public static int getLastTime(int[] csv, CsvType type) { 142 | return csv == null || csv.length == 0 ? -1 : csv[csv.length - (type.isWithShipping ? 3 : 2)]; 143 | } 144 | 145 | /** 146 | * Get the value/price at the specified time 147 | * 148 | * @param csv value/price history csv 149 | * @param time value/price lookup time (keepa time minutes) 150 | * @param type the type of the csv data. 151 | * @return the price or value of the product at the specified time. -1 if no value was found or if the product was out of stock. If the csv includes shipping costs it will be the landing price (price + shipping). 152 | */ 153 | public static int getValueAtTime(int[] csv, int time, CsvType type) { 154 | if (csv == null || csv.length == 0) return -1; 155 | int i = 0; 156 | 157 | int loopIncrement = (type.isWithShipping ? 3 : 2); 158 | for (; i < csv.length; i += loopIncrement) 159 | if (csv[i] > time) break; 160 | 161 | if (i > csv.length) return getLast(csv, type); 162 | if (i < loopIncrement) return -1; 163 | 164 | if (type.isWithShipping) { 165 | int v = csv[i - 2]; 166 | int s = csv[i - 1]; 167 | return v < 0 ? v : v + (s < 0 ? 0 : s); 168 | } 169 | 170 | return csv[i - 1]; 171 | } 172 | 173 | /** 174 | * Get the price and shipping cost at the specified time 175 | * 176 | * @param csv price with shipping history csv 177 | * @param time price lookup time (keepa time minutes) 178 | * @return int[price, shipping] - the price and shipping cost of the product at the specified time. [-1, -1] if no price was found or if the product was out of stock. 179 | */ 180 | public static int[] getPriceAndShippingAtTime(int[] csv, int time) { 181 | if (csv == null || csv.length == 0) return new int[]{-1, -1}; 182 | int i = 0; 183 | 184 | for (; i < csv.length; i += 3) { 185 | if (csv[i] > time) { 186 | break; 187 | } 188 | } 189 | 190 | if (i > csv.length) return getLastPriceAndShipping(csv); 191 | if (i < 3) return new int[]{-1, -1}; 192 | 193 | return new int[]{csv[i - 2], csv[i - 1]}; 194 | } 195 | 196 | 197 | /** 198 | * Get the last price and shipping cost. 199 | * 200 | * @param csv price with shipping history csv 201 | * @return int[price, shipping] - the last price and shipping cost. 202 | */ 203 | public static int[] getLastPriceAndShipping(int[] csv) { 204 | if (csv == null || csv.length < 3) return new int[]{-1, -1}; 205 | return new int[]{csv[csv.length - 2], csv[csv.length - 1]}; 206 | } 207 | 208 | 209 | /** 210 | * @param csv value/price history csv 211 | * @param time time to begin the search 212 | * @param type the type of the csv data. 213 | * @return the closest value/price found to the specified time. If the csv includes shipping costs it will be the landing price (price + shipping). 214 | */ 215 | public static int getClosestValueAtTime(int[] csv, int time, CsvType type) { 216 | if (csv == null || csv.length == 0) return -1; 217 | int i = 0; 218 | int loopIncrement = (type.isWithShipping ? 3 : 2); 219 | for (; i < csv.length; i += loopIncrement) 220 | if (csv[i] > time) break; 221 | 222 | if (i > csv.length) return getLast(csv, type); 223 | if (i < loopIncrement) { 224 | if (type.isWithShipping) { 225 | if (csv.length < 4) { 226 | int v = csv[2]; 227 | int s = csv[1]; 228 | return v < 0 ? v : v + (s < 0 ? 0 : s); 229 | } else 230 | i += 3; 231 | } else { 232 | if (csv.length < 3) 233 | return csv[1]; 234 | else 235 | i += 2; 236 | } 237 | } 238 | 239 | if (type.isWithShipping) { 240 | if (csv[i - 2] != -1) { 241 | int v = csv[i - 2]; 242 | int s = csv[i - 1]; 243 | return v < 0 ? v : v + (s < 0 ? 0 : s); 244 | } else { 245 | for (; i < csv.length; i += loopIncrement) { 246 | if (csv[i - 2] != -1) break; 247 | } 248 | if (i > csv.length) return getLast(csv, type); 249 | if (i < 3) return -1; 250 | int v = csv[i - 2]; 251 | int s = csv[i - 1]; 252 | return v < 0 ? v : v + (s < 0 ? 0 : s); 253 | } 254 | } else { 255 | if (csv[i - 1] != -1) 256 | return csv[i - 1]; 257 | else { 258 | for (; i < csv.length; i += 2) { 259 | if (csv[i - 1] != -1) break; 260 | } 261 | if (i > csv.length) return getLast(csv, type); 262 | if (i < 2) return -1; 263 | return csv[i - 1]; 264 | } 265 | } 266 | } 267 | 268 | 269 | /** 270 | * finds the lowest and highest value/price of the csv history 271 | * 272 | * @param csv value/price history csv 273 | * @param type the type of the csv data. 274 | * @return [0] = low, [1] = high. If the csv includes shipping costs the extreme point will be the landing price (price + shipping). [-1, -1] if insufficient data. 275 | */ 276 | public static int[] getLowestAndHighest(int[] csv, CsvType type) { 277 | int[] minMax = getExtremePointsInIntervalWithTime(csv, 0, Integer.MAX_VALUE, type); 278 | return new int[]{minMax[1], minMax[3]}; 279 | } 280 | 281 | /** 282 | * finds the lowest and highest value/price of the csv history including the dates of the occurrences (in keepa time minutes). 283 | * 284 | * @param csv value/price history csv 285 | * @param type the type of the csv data. 286 | * @return [0] = low time, [1] = low, [2] = high time, [3] = high. If the csv includes shipping costs the extreme point will be the landing price (price + shipping). [-1, -1, -1, -1] if insufficient data. 287 | */ 288 | public static int[] getLowestAndHighestWithTime(int[] csv, CsvType type) { 289 | return getExtremePointsInIntervalWithTime(csv, 0, Integer.MAX_VALUE, type); 290 | } 291 | 292 | /** 293 | * Returns a weighted mean of the products csv history in the last X days 294 | * 295 | * @param csv value/price history csv 296 | * @param now current keepa time minutes 297 | * @param days number of days the weighted mean will be calculated for (e.g. 90 days, 60 days, 30 days) 298 | * @param type the type of the csv data. 299 | * @return the weighted mean or -1 if insufficient history csv length (less than a day). If the csv includes shipping costs it will be the wieghted mean of the landing price (price + shipping). 300 | */ 301 | public static int calcWeightedMean(int[] csv, int now, double days, CsvType type) { 302 | return getWeightedMeanInInterval(csv, now, now - (int) (days * 24 * 60), now, type); 303 | } 304 | 305 | public static int getWeightedMeanInInterval(int[] v, int now, int start, int end, CsvType type) { 306 | long avg = -1; 307 | if (start >= end || v == null || v.length == 0) return -1; 308 | 309 | int size = v.length; 310 | int loopIncrement = type.isWithShipping ? 3 : 2; 311 | 312 | int lastTime = getLastTime(v, type); 313 | int firstTime = v[0]; 314 | 315 | if (lastTime == -1 || firstTime == -1 || firstTime > end) return -1; 316 | 317 | long count = 0; 318 | 319 | if (firstTime > start) start = firstTime; 320 | if (end > now) end = now; 321 | 322 | int adjustedIndex = type.isWithShipping ? 2 : 1; 323 | for (int i = 1; i < size; i += loopIncrement) { 324 | int date = v[i - 1]; 325 | if (date >= end) break; 326 | 327 | int c = v[i]; 328 | if (c < 0) continue; 329 | 330 | if (type.isWithShipping) { 331 | int s = v[i + 1]; 332 | c += Math.max(s, 0); 333 | } 334 | 335 | if (date >= start) { 336 | if (i == 1 && i + adjustedIndex == size) 337 | return c; 338 | 339 | int nextDate = (i + adjustedIndex == size) ? now : v[i + adjustedIndex]; 340 | if (nextDate > end) nextDate = end; 341 | 342 | long tmpCount = nextDate - date; 343 | count += tmpCount; 344 | avg += c * tmpCount; 345 | } else { 346 | if (i == size - adjustedIndex || v[i + adjustedIndex] >= end) 347 | return c; 348 | 349 | int nextDate = v[i + adjustedIndex]; 350 | if (nextDate >= start) { 351 | count = nextDate - start; 352 | avg = c * count; 353 | } 354 | } 355 | } 356 | 357 | if (avg > -1) 358 | avg = count != 0 ? (int) Math.floor(avg / (double) count) : -1; 359 | 360 | return (int) avg; 361 | } 362 | 363 | /** 364 | * Returns true if the CSV was out of stock in the given period. 365 | * 366 | * @param csv value/price history csv 367 | * @param start start of the interval (keepa time minutes), can be 0. 368 | * @param end end of the interval (keepa time minutes), can be in the future (Integer.MAX_VALUE). 369 | * @param type the type of the csv data. 370 | * @return was out of stock in interval, null if the csv is too short to tell. 371 | */ 372 | public static Boolean getOutOfStockInInterval(int[] csv, int start, int end, CsvType type) { 373 | if (type.isWithShipping) { 374 | if (csv == null || csv.length < 6) 375 | return null; 376 | } else if (start >= end || csv == null || csv.length < 4) 377 | return null; 378 | 379 | int loopIncrement = (type.isWithShipping ? 3 : 2); 380 | for (int i = 0; i < csv.length; i += loopIncrement) { 381 | int date = csv[i]; 382 | if (date <= start) continue; 383 | if (date >= end) break; 384 | if (csv[i + 1] == -1) return true; 385 | } 386 | 387 | return false; 388 | } 389 | 390 | /** 391 | * Returns a the percentage of time in the given interval the price type was out of stock 392 | * 393 | * @param csv value/price history csv 394 | * @param now current keepa time minutes 395 | * @param start start of the interval (keepa time minutes), can be 0. 396 | * @param end end of the interval (keepa time minutes), can be in the future (Integer.MAX_VALUE). 397 | * @param type the type of the csv data. 398 | * @param trackingSince the product object's trackingSince value 399 | * @return percentage between 0 and 100 or -1 if insufficient data. 100 = 100% out of stock in the interval. 400 | */ 401 | public static int getOutOfStockPercentageInInterval(int[] csv, int now, int start, int end, CsvType type, int trackingSince) { 402 | if (!type.isPrice) return -1; 403 | if (start >= end) return -1; 404 | if (csv == null || csv.length == 0) 405 | return -1; 406 | 407 | int size = csv.length; 408 | int loopIncrement = (type.isWithShipping ? 3 : 2); 409 | 410 | int lastTime = getLastTime(csv, type); 411 | int firstTime = csv[0]; 412 | 413 | if (lastTime == -1 || firstTime == -1 || firstTime > end || trackingSince > end) return -1; 414 | 415 | long count = 0; 416 | 417 | if (trackingSince > start) 418 | start = trackingSince; 419 | 420 | if (end > now) 421 | end = now; 422 | 423 | int adjustedIndex = type.isWithShipping ? 2 : 1; 424 | 425 | for (int i = 1, j = size; i < j; i += loopIncrement) { 426 | int c = csv[i]; 427 | int date = csv[i - 1]; 428 | 429 | if (date >= end) 430 | break; 431 | 432 | if (c != -1) { 433 | if (date >= start) { 434 | if (i == 1) { 435 | if (i + adjustedIndex == j) { 436 | return 0; 437 | } 438 | } 439 | 440 | int nextDate; 441 | if (i + adjustedIndex == j) { 442 | nextDate = Math.min(now, end); 443 | } else { 444 | nextDate = csv[i + adjustedIndex]; 445 | if (nextDate > end) 446 | nextDate = end; 447 | } 448 | 449 | long tmpCount = nextDate - date; 450 | 451 | count += tmpCount; 452 | } else { 453 | if (i == j - adjustedIndex) { 454 | return 0; 455 | } else { 456 | int nextDate = csv[i + adjustedIndex]; 457 | 458 | if (nextDate >= end) 459 | return 0; 460 | 461 | if (nextDate >= start) 462 | count = nextDate - start; 463 | } 464 | } 465 | } 466 | } 467 | 468 | if (count > 0) 469 | count = 100 - (int) Math.floor((count * 100) / (double) (end - start)); 470 | else if (count == 0) { 471 | count = 100; 472 | } 473 | 474 | return (int) count; 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Stats.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | import java.util.LinkedHashMap; 6 | 7 | import static com.keepa.api.backend.helper.Utility.gson; 8 | 9 | /** 10 | * Contains statistic values. 11 | * Only set if the stats parameter was used in the Product Request. Part of the {@link Product} 12 | */ 13 | public class Stats { 14 | 15 | /** 16 | * Contains the prices / ranks of the product of the time we last updated it. 17 | *

Uses {@link Product.CsvType} indexing.

18 | * The price is an integer of the respective Amazon locale's smallest currency unit (e.g. euro cents or yen). 19 | * If no offer was available in the given interval (e.g. out of stock) the price has the value -1. 20 | */ 21 | public int[] current = null; 22 | 23 | /** 24 | * Contains the weighted mean for the interval specified in the product request's stats parameter.
25 | *

Uses {@link Product.CsvType} indexing.

26 | * If no offer was available in the given interval or there is insufficient data it has the value -1. 27 | */ 28 | public int[] avg = null; 29 | 30 | 31 | /** 32 | * Contains the weighted mean for the last 30 days.
33 | *

Uses {@link Product.CsvType} indexing.

34 | * If no offer was available in the given interval or there is insufficient data it has the value -1. 35 | */ 36 | public int[] avg30 = null; 37 | 38 | 39 | /** 40 | * Contains the weighted mean for the last 90 days.
41 | *

Uses {@link Product.CsvType} indexing.

42 | * If no offer was available in the given interval or there is insufficient data it has the value -1. 43 | */ 44 | public int[] avg90 = null; 45 | 46 | /** 47 | * Contains the weighted mean for the last 180 days.
48 | *

Uses {@link Product.CsvType} indexing.

49 | * If no offer was available in the given interval or there is insufficient data it has the value -1. 50 | */ 51 | public int[] avg180 = null; 52 | 53 | /** 54 | * Contains the weighted mean for the last 365 days.
55 | *

Uses {@link Product.CsvType} indexing.

56 | * If no offer was available in the given interval or there is insufficient data it has the value -1. 57 | */ 58 | public int[] avg365 = null; 59 | 60 | /** 61 | * Contains the prices registered at the start of the interval specified in the product request's stats parameter.
62 | *

Uses {@link Product.CsvType} indexing.

63 | * If no offer was available in the given interval or there is insufficient data it has the value -1. 64 | */ 65 | public int[] atIntervalStart = null; 66 | 67 | /** 68 | * Contains the lowest prices registered for this product.
69 | * First dimension uses {@link Product.CsvType} indexing
70 | * Second dimension is either null, if there is no data available for the price type, or 71 | * an array of the size 2 with the first value being the time of the extreme point (in Keepa time minutes) and the second one the respective extreme value. 72 | *
73 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 74 | */ 75 | public int[][] min = null; 76 | 77 | /** 78 | * Contains the lowest prices registered in the interval specified in the product request's stats parameter.
79 | * First dimension uses {@link Product.CsvType} indexing
80 | * Second dimension is either null, if there is no data available for the price type, or 81 | * an array of the size 2 with the first value being the time of the extreme point (in Keepa time minutes) and the second one the respective extreme value. 82 | *
83 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 84 | */ 85 | public int[][] minInInterval = null; 86 | 87 | /** 88 | * Contains the highest prices registered for this product.
89 | * First dimension uses {@link Product.CsvType} indexing
90 | * Second dimension is either null, if there is no data available for the price type, or 91 | * an array of the size 2 with the first value being the time of the extreme point (in Keepa time minutes) and the second one the respective extreme value.
92 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 93 | */ 94 | public int[][] max = null; 95 | 96 | /** 97 | * Contains the highest prices registered in the interval specified in the product request's stats parameter.
98 | * First dimension uses {@link Product.CsvType} indexing
99 | * Second dimension is either null, if there is no data available for the price type, or 100 | * an array of the size 2 with the first value being the time of the extreme point (in Keepa time minutes) and the second one the respective extreme value.
101 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 102 | */ 103 | public int[][] maxInInterval = null; 104 | 105 | /** 106 | * Whether the current price is the all-time lowest price.
107 | * Uses {@link Product.CsvType} indexing 108 | */ 109 | public boolean[] isLowest = null; 110 | 111 | /** 112 | * Whether the current price is the lowest price in the last 90 days.
113 | * Uses {@link Product.CsvType} indexing 114 | */ 115 | public boolean[] isLowest90 = null; 116 | 117 | /** 118 | * Number of times in the last 30 days Amazon went out of stock. 119 | */ 120 | public Integer outOfStockCountAmazon30 = null; 121 | 122 | /** 123 | * Number of times in the last 90 days Amazon went out of stock. 124 | */ 125 | public Integer outOfStockCountAmazon90 = null; 126 | 127 | /** 128 | * Contains the difference in percent between the current monthlySold value and the average value of the last 90 days. 129 | * The value 0 means it did not change or could not be calculated. 130 | */ 131 | public Short deltaPercent90_monthlySold = null; 132 | 133 | /** 134 | * 135 | * Contains the out of stock percentage in the interval specified in the product request's stats parameter.
136 | *

Uses {@link Product.CsvType} indexing.

137 | * It has the value -1 if there is insufficient data or the CsvType is not a price.
138 | *

Examples: 0 = never was out of stock, 100 = was out of stock 100% of the time, 25 = was out of stock 25% of the time

139 | */ 140 | public int[] outOfStockPercentageInInterval = null; 141 | 142 | /** 143 | * Contains the 90 day out of stock percentage
144 | *

Uses {@link Product.CsvType} indexing.

145 | * It has the value -1 if there is insufficient data or the CsvType is not a price.
146 | *

Examples: 0 = never was out of stock, 100 = was out of stock 100% of the time, 25 = was out of stock 25% of the time

147 | */ 148 | public int[] outOfStockPercentage90 = null; 149 | 150 | /** 151 | * Contains the 30 day out of stock percentage
152 | *

Uses {@link Product.CsvType} indexing.

153 | * It has the value -1 if there is insufficient data or the CsvType is not a price.
154 | *

Examples: 0 = never was out of stock, 100 = was out of stock 100% of the time, 25 = was out of stock 25% of the time

155 | */ 156 | public int[] outOfStockPercentage30 = null; 157 | 158 | /** 159 | * Can be used to identify past, upcoming and current lightning deal offers.
160 | * Has the format [startDate, endDate] (if not null, always array length 2). *null* if the product never had a lightning deal. Both timestamps are in UTC and Keepa time minutes.
161 | * If there is a upcoming lightning deal, only startDate is be set (endDate has value -1)
162 | * If there is a current lightning deal, both startDate and endDate will be set. startDate will be older than the current time, but endDate will be a future date.
163 | * If there is only a past deal, both startDate and endDate will be set in the past.
164 | * Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 165 | */ 166 | public int[] lightningDealInfo = null; // [startDate, endDate], or null 167 | 168 | /** 169 | * the total count of offers this product has (all conditions combined). The offer count per condition can be found in the current field. 170 | */ 171 | public int totalOfferCount = -2; 172 | 173 | /** 174 | * the last time the offers information was updated. Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 175 | */ 176 | public int lastOffersUpdate = -1; 177 | 178 | /** 179 | * Contains the total stock available per item condition (of the retrieved offers) for 3rd party FBA 180 | * (fulfillment by Amazon, Warehouse Deals included) or FBM (fulfillment by merchant) offers. Uses the {@link Offer.OfferCondition} indexing. 181 | */ 182 | public int[] stockPerCondition3rdFBA = null; 183 | 184 | /** 185 | * Contains the total stock available per item condition (of the retrieved offers) for 3rd party FBM 186 | * (fulfillment by Amazon, Warehouse Deals included) or FBM (fulfillment by merchant) offers. Uses the {@link Offer.OfferCondition} indexing. 187 | */ 188 | public int[] stockPerConditionFBM = null; 189 | 190 | /** 191 | * Only set when the offers parameter was used. The stock of Amazon, if Amazon has an offer. Max. reported stock is 10. Otherwise -2. 192 | */ 193 | public int stockAmazon = -2; 194 | 195 | /** 196 | * Only set when the offers parameter was used. The stock of buy box offer. Max. reported stock is 10. If the boy box is empty/unqualified: -2. 197 | */ 198 | public int stockBuyBox = -2; 199 | 200 | /** 201 | * Only set when the offers parameter was used. The count of actually retrieved offers for this request. 202 | */ 203 | public int retrievedOfferCount = -2; 204 | 205 | /** 206 | * Only set when the offers parameter was used. The buy box price, if existent. Otherwise -2. 207 | */ 208 | public int buyBoxPrice = -2; 209 | 210 | /** 211 | * Only set when the offers parameter was used. The buy box shipping cost, if existent. Otherwise -2. 212 | */ 213 | public int buyBoxShipping = -2; 214 | 215 | /** 216 | * Only set when the offers parameter was used. Whether or not a seller won the buy box. If there are only sellers with bad offers none qualifies for the buy box. 217 | */ 218 | public Boolean buyBoxIsUnqualified = null; 219 | 220 | /** 221 | * Only set when the offers parameter was used. Whether or not the buy box is listed as being shippable. 222 | */ 223 | public Boolean buyBoxIsShippable = null; 224 | 225 | /** 226 | * Only set when the offers parameter was used. If the buy box is a pre-order. 227 | */ 228 | public Boolean buyBoxIsPreorder = null; 229 | 230 | /** 231 | * Only set when the offers parameter was used. Whether or not the buy box is fulfilled by Amazon. 232 | */ 233 | public Boolean buyBoxIsFBA = null; 234 | 235 | /** 236 | * The last time the Buy Box price was updated. Use {@link KeepaTime#keepaMinuteToUnixInMillis(int)} (long)} to get an uncompressed timestamp (Unix epoch time). 237 | */ 238 | public Integer lastBuyBoxUpdate = null; 239 | 240 | /** 241 | * Only set when the offers parameter was used. Whether or not there is a used buy box offer. 242 | */ 243 | public Boolean buyBoxIsUsed = null; 244 | 245 | /** 246 | * Whether the buy box offer is back-ordered 247 | */ 248 | public Boolean buyBoxIsBackorder = null; 249 | 250 | /** 251 | * Only set when the offers parameter was used. If Amazon is the seller in the buy box. 252 | */ 253 | public Boolean buyBoxIsAmazon = null; 254 | 255 | /** 256 | * Only set when the offers parameter was used. If the buy box price is hidden on Amazon due to MAP restrictions (minimum advertised price). 257 | */ 258 | public Boolean buyBoxIsMAP = null; 259 | 260 | /** 261 | * The minimum order quantity of the buy box. -1 if not available, 0 if no limit exists. 262 | */ 263 | public int buyBoxMinOrderQuantity = -1; 264 | 265 | /** 266 | * The maximum order quantity of the buy box. -1 if not available, 0 if no limit exists. 267 | */ 268 | public int buyBoxMaxOrderQuantity = -1; 269 | 270 | /** 271 | * The availability message of the buy box. null if not available. 272 | * Example: “In Stock.” 273 | */ 274 | public String buyBoxAvailabilityMessage = null; 275 | 276 | /** 277 | * The seller id of the buy box offer, if existent. Otherwise "-2" or null 278 | */ 279 | public String buyBoxSellerId = null; 280 | 281 | /** 282 | * The default shipping country of the buy box seller. null if not available. Example: “US” 283 | */ 284 | public String buyBoxShippingCountry = null; 285 | 286 | /** 287 | * If the buy box is Prime exclusive. null if not available. 288 | */ 289 | public Boolean buyBoxIsPrimeExclusive = null; 290 | 291 | /** 292 | * If the buy box is free shipping eligible. null if not available. 293 | */ 294 | public Boolean buyBoxIsFreeShippingEligible = null; 295 | 296 | /** 297 | * If the buy box is Prime eligible. null if not available. 298 | */ 299 | public Boolean buyBoxIsPrimeEligible = null; 300 | 301 | /** 302 | * If the buy box is a Prime Pantry offer. null if not available. 303 | */ 304 | public Boolean buyBoxIsPrimePantry = null; 305 | 306 | /** 307 | * A map containing buy box statistics for the interval specified. Each key represents the sellerId of the buy box seller and each object a buy box statistics object. 308 | */ 309 | public LinkedHashMap buyBoxStats = null; 310 | 311 | /** 312 | * The buy box saving basis price (strikethrough, typical price). null if unavailable. 313 | */ 314 | public Integer buyBoxSavingBasis = null; 315 | 316 | /** 317 | * Only set when the offers parameter was used. Price of the used buy box, if existent. Otherwise "-1" or null 318 | */ 319 | public Integer buyBoxUsedPrice = null; 320 | 321 | /** 322 | * Only set when the offers parameter was used. Shipping cost of the used buy box, if existent. Otherwise "-1" or null 323 | */ 324 | public Integer buyBoxUsedShipping = null; 325 | 326 | /** 327 | * Only set when the offers parameter was used. Seller id of the used boy box, if existent. Otherwise null. 328 | */ 329 | public String buyBoxUsedSellerId = null; 330 | 331 | /** 332 | * Only set when the offers parameter was used. Whether or not the used buy box is fulfilled by Amazon. 333 | */ 334 | public Boolean buyBoxUsedIsFBA = null; 335 | 336 | /** 337 | * Only set when the offers parameter was used. The used sub type condition of the used buy box offer
338 | *
The {@link Offer.OfferCondition} condition of the offered product. Integer value: 339 | *
2 - Used - Like New 340 | *
3 - Used - Very Good 341 | *
4 - Used - Good 342 | *
5 - Used - Acceptable 343 | *
Note: Open Box conditions will be coded as Used conditions. 344 | */ 345 | public Byte buyBoxUsedCondition = null; 346 | 347 | /** 348 | * A map containing used buy box statistics for the interval specified. Each key represents the sellerId of the used buy box seller and each object a buy box statistics object. 349 | */ 350 | public LinkedHashMap buyBoxUsedStats = null; 351 | 352 | /** 353 | * Only set when the offers parameter was used. If the product is an add-on item (add-on Items ship with orders that include $25 or more of items shipped by Amazon). 354 | */ 355 | public Boolean isAddonItem = null; 356 | 357 | /** 358 | * Only set when the offers parameter was used. Contains the seller ids (if any) of the lowest priced live FBA offer(s). Multiple entries if multiple offers share the same price. 359 | */ 360 | public String[] sellerIdsLowestFBA = null; 361 | 362 | /** 363 | * Only set when the offers parameter was used. Contains the seller ids (if any) of the lowest priced live FBM offer(s). Multiple entries if multiple offers share the same price. 364 | */ 365 | public String[] sellerIdsLowestFBM = null; 366 | 367 | /** 368 | * Only set when the offers parameter was used. Count of retrieved live FBA offers. 369 | */ 370 | public int offerCountFBA = -2; 371 | 372 | /** 373 | * Only set when the offers parameter was used. Count of retrieved live FBM offers. 374 | */ 375 | public int offerCountFBM = -2; 376 | 377 | /** 378 | * The count of sales rank drops (from high value to low value) within the last 30 days which are considered to indicate sales. 379 | */ 380 | public int salesRankDrops30 = -1; 381 | 382 | /** 383 | * The count of sales rank drops (from high value to low value) within the last 90 days which are considered to indicate sales. 384 | */ 385 | public int salesRankDrops90 = -1; 386 | 387 | /** 388 | * The count of sales rank drops (from high value to low value) within the last 180 days which are considered to indicate sales. 389 | */ 390 | public int salesRankDrops180 = -1; 391 | 392 | /** 393 | * The count of sales rank drops (from high value to low value) within the last 365 days which are considered to indicate sales. 394 | */ 395 | public int salesRankDrops365 = -1; 396 | 397 | public static class BuyBoxStatsObject { 398 | /** an approximation of the percentage the seller won the buy box **/ 399 | public float percentageWon; 400 | /** avg. price of the Buy Box offer of this seller **/ 401 | public int avgPrice; 402 | /** avg. "New" offer count during the time the seller held the Buy Box **/ 403 | public int avgNewOfferCount; 404 | /** whether or not this offer is fulfilled by Amazon **/ 405 | public boolean isFBA; 406 | /** last time the seller won the buy box **/ 407 | public int lastSeen; 408 | } 409 | 410 | @Override 411 | public String toString() { 412 | return gson.toJson(this); 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/main/java/com/keepa/api/backend/structs/Request.java: -------------------------------------------------------------------------------- 1 | package com.keepa.api.backend.structs; 2 | 3 | import com.keepa.api.backend.helper.KeepaTime; 4 | 5 | import java.util.HashMap; 6 | 7 | import static com.keepa.api.backend.helper.Utility.*; 8 | 9 | /** 10 | * Common Request 11 | */ 12 | public class Request { 13 | // public enum RequestType { 14 | // REQUEST_PRODUCTS, BROWSING_DEALS, CATEGORY_LOOKUP, CATEGORY_SEARCH, PRODUCT_SEARCH, SELLER_REQUEST 15 | // } 16 | 17 | public HashMap parameter; 18 | public String postData; 19 | public String path; 20 | 21 | public Request() { 22 | parameter = new HashMap<>(20); 23 | } 24 | 25 | /** 26 | * By accessing our deals you can find products that recently changed and match your search criteria. A single request will return a maximum of 150 deals. 27 | * 28 | * @param dealRequest The dealRequest contains all request parameters. 29 | * @return A ready to send request. 30 | */ 31 | public static Request getDealsRequest(DealRequest dealRequest) { 32 | Request r = new Request(); 33 | r.path = "deal"; 34 | r.postData = gson.toJson(dealRequest); 35 | return r; 36 | } 37 | 38 | /** 39 | * This request provides access to all current and upcoming lightning deals. 40 | * You can request a specific lightning deal by specifying an ASIN or get the complete list. 41 | * Our lightning deals information is updated every 10 minutes. 42 | * 43 | * @param domainId Amazon locale of the product {@link AmazonLocale} 44 | * @param asin The ASIN to retrieve the lightning deal for or null to retrieve all lightning deals 45 | * @return A ready to send request. 46 | */ 47 | public static Request getLightningDealRequest(final AmazonLocale domainId, String asin) { 48 | Request r = new Request(); 49 | r.path = "lightningdeal"; 50 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 51 | 52 | if(asin != null) 53 | r.parameter.put("asin", asin); 54 | return r; 55 | } 56 | 57 | /** 58 | * Query our product database to find products matching your criteria. Almost all product fields can be searched for and sorted by. 59 | * The product finder request provides the same core functionality as our Product Finder. 60 | * 61 | * @param finderRequest The FinderRequest contains all filter parameters. 62 | * @return A ready to send request. 63 | */ 64 | public static Request getProductFinderRequest(final AmazonLocale domainId, ProductFinderRequest finderRequest) { 65 | Request r = new Request(); 66 | r.path = "query"; 67 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 68 | r.parameter.put("selection", gson.toJson(finderRequest)); 69 | return r; 70 | } 71 | 72 | /** 73 | * Add tracking to your list. 74 | * 75 | * @param trackingRequest The trackingRequest contains all request parameters. 76 | * @return A ready to send request. 77 | */ 78 | public static Request getTrackingAddRequest(TrackingRequest trackingRequest) { 79 | return getTrackingBatchAddRequest(trackingRequest); 80 | } 81 | 82 | /** 83 | * Add multiple tracking to your list. Much more efficient than individual additions. 84 | * 85 | * @param trackingRequests The trackingRequests (up to 1000). 86 | * @return A ready to send request. 87 | */ 88 | public static Request getTrackingBatchAddRequest(TrackingRequest... trackingRequests) { 89 | if(trackingRequests.length > 1000) return null; 90 | Request r = new Request(); 91 | r.path = "tracking"; 92 | r.parameter.put("type", "add"); 93 | r.postData = gson.toJson(trackingRequests); 94 | return r; 95 | } 96 | 97 | /** 98 | * Get a single tracking from your list. 99 | * 100 | * @param asin The ASIN to retrieve the tracking for. 101 | * @return A ready to send request. 102 | */ 103 | public static Request getTrackingGetRequest(String asin) { 104 | Request r = new Request(); 105 | r.path = "tracking"; 106 | r.parameter.put("type", "get"); 107 | r.parameter.put("asin", asin); 108 | return r; 109 | } 110 | 111 | /** 112 | * Get all trackings from your list. If you track a lot of products this may be a resource intensive operation. Use responsibly. 113 | * 114 | * @param asinsOnly Wether or not to only request an ASIN list of tracked products or to return all tracking objects (much larger response). 115 | * @return A ready to send request. 116 | */ 117 | public static Request getTrackingListRequest(boolean asinsOnly) { 118 | Request r = new Request(); 119 | r.path = "tracking"; 120 | r.parameter.put("type", "list"); 121 | if(asinsOnly) 122 | r.parameter.put("asins-only", "1"); 123 | return r; 124 | } 125 | 126 | /** 127 | * Get your recent notifications. 128 | * @param since Retrieve all available notifications that occurred since this date, in {@link KeepaTime}. Use 0 to request all. 129 | * @param revise Whether or not to request notifications already marked as read. 130 | * @return A ready to send request. 131 | */ 132 | public static Request getTrackingNotificationRequest(int since, boolean revise) { 133 | Request r = new Request(); 134 | r.path = "tracking"; 135 | r.parameter.put("since", String.valueOf(since)); 136 | r.parameter.put("revise", revise ? "1" : "0"); 137 | r.parameter.put("type", "notification"); 138 | return r; 139 | } 140 | 141 | 142 | /** 143 | * Remove a tracking from your list. 144 | * 145 | * @param asin The ASIN to remove the tracking for. 146 | * @return A ready to send request. 147 | */ 148 | public static Request getTrackingRemoveRequest(String asin) { 149 | Request r = new Request(); 150 | r.path = "tracking"; 151 | r.parameter.put("type", "remove"); 152 | r.parameter.put("asin", asin); 153 | return r; 154 | } 155 | 156 | /** 157 | * Removes all your trackings from your list. 158 | * 159 | * @return A ready to send request. 160 | */ 161 | public static Request getTrackingRemoveAllRequest() { 162 | Request r = new Request(); 163 | r.path = "tracking"; 164 | r.parameter.put("type", "removeAll"); 165 | return r; 166 | } 167 | 168 | /** 169 | * Updates the webhook URL our service will call whenever a notification is triggered. The URL can also be updated and tested via the website. 170 | * A push notification will be a HTTP POST call with a single notification object as its content. 171 | * Your server must respond with a status code of 200 to confirm the successful retrieval. 172 | * If delivery fails a second attempt will be made with a 15 seconds delay. 173 | * 174 | * @param url The new webhook URL 175 | * @return A ready to send request. 176 | */ 177 | public static Request getTrackingWebhookRequest(String url) { 178 | Request r = new Request(); 179 | r.path = "tracking"; 180 | r.parameter.put("type", "webhook"); 181 | r.parameter.put("url", url); 182 | return r; 183 | } 184 | 185 | /** 186 | * Retrieve an ASIN list of the most popular products based on sales in a specific category. 187 | * 188 | * @param domainId Amazon locale of the product {@link AmazonLocale} 189 | * @param category The category node id of the category you want to request the best sellers list for 190 | * @return A ready to send request. 191 | */ 192 | public static Request getBestSellersRequest(final AmazonLocale domainId, long category) { 193 | return getBestSellersRequest(domainId, String.valueOf(category)); 194 | } 195 | 196 | /** 197 | * Retrieve an ASIN list of the most popular products based on sales of a specific product group. 198 | * 199 | * @param domainId Amazon locale of the product {@link AmazonLocale} 200 | * @param productGroup The productGroup you want to request the best sellers list for 201 | * @return A ready to send request. 202 | */ 203 | public static Request getBestSellersRequest(final AmazonLocale domainId, String productGroup) { 204 | Request r = new Request(); 205 | r.path = "bestsellers"; 206 | r.parameter.put("category", productGroup); 207 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 208 | return r; 209 | } 210 | 211 | /** 212 | * Retrieve category objects using their node ids and (optional) their parent tree. 213 | * 214 | * @param domainId Amazon locale of the product {@link AmazonLocale} 215 | * @param category The category node id of the category you want to request. For batch requests a comma separated list of ids (up to 10, the token cost stays the same). 216 | * @param parents Whether or not to include the category tree for each category. 217 | * @return A ready to send request. 218 | */ 219 | public static Request getCategoryLookupRequest(final AmazonLocale domainId, boolean parents, long category) { 220 | Request r = new Request(); 221 | r.path = "category"; 222 | r.parameter.put("category", String.valueOf(category)); 223 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 224 | 225 | if (parents) 226 | r.parameter.put("parents", "1"); 227 | 228 | return r; 229 | } 230 | 231 | /** 232 | * Search for Amazon category names. Retrieves the category objects12 and optional their parent tree. 233 | * 234 | * @param domainId Amazon locale of the product {@link AmazonLocale} 235 | * @param term The term you want to search for. Multiple space separated keywords are possible and if provided must all match. The minimum length of a keyword is 3 characters. 236 | * @param parents Whether or not to include the category tree for each category. 237 | * @return A ready to send request. 238 | */ 239 | public static Request getCategorySearchRequest(final AmazonLocale domainId, String term, boolean parents) { 240 | Request r = new Request(); 241 | r.path = "search"; 242 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 243 | r.parameter.put("type", "category"); 244 | r.parameter.put("term", term); 245 | 246 | if (parents) 247 | r.parameter.put("parents", "1"); 248 | 249 | return r; 250 | } 251 | 252 | /** 253 | * Retrieves the seller object via the seller id. If a seller is not found no tokens will be consumed. 254 | * 255 | * @param domainId Amazon locale of the product {@link AmazonLocale} 256 | * @param seller The seller id of the merchant you want to request. For batch requests a comma separated list of sellerIds (up to 100). The seller id is part of the offer object and can also be found on Amazon on seller profile pages in the seller parameter of the URL. 257 | * Example: A2L77EE7U53NWQ (Amazon.com Warehouse Deals) 258 | * @return A ready to send request. 259 | */ 260 | public static Request getSellerRequest(final AmazonLocale domainId, String... seller) { 261 | Request r = new Request(); 262 | r.path = "seller"; 263 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 264 | r.parameter.put("seller", arrayToCsv(seller)); 265 | return r; 266 | } 267 | 268 | /** 269 | * Retrieves the seller object via the seller id. If a seller is not found no tokens will be consumed. 270 | * 271 | * @param domainId Amazon locale of the product {@link AmazonLocale} 272 | * @param seller The seller id of the merchant you want to request. For batch requests a comma separated list of sellerIds (up to 100). 273 | * The seller id is part of the offer object and can also be found on Amazon on seller profile pages in the seller parameter of the URL. 274 | * Example: A2L77EE7U53NWQ (Amazon.com Warehouse Deals) 275 | * @param storefront Valid values: 0 (false) and 1 (true). If specified the seller object will contain additional information about what items the seller is listing on Amazon. 276 | * This includes a list of ASINs as well as the total amount of items the seller has listed. 277 | * The following seller object fields will be set if data is available: asinList, asinListLastSeen. 278 | * If no data is available no additional tokens will be consumed. The ASIN list can contain up to 100,000 items. 279 | * As using the storefront parameter does not trigger any new collection it does not increase the processing time 280 | * of the request, though the response may be much bigger in size. 281 | * @return A ready to send request. 282 | */ 283 | public static Request getSellerRequest(final AmazonLocale domainId, String seller, boolean storefront) { 284 | Request r = new Request(); 285 | r.path = "seller"; 286 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 287 | r.parameter.put("seller", seller); 288 | 289 | if (storefront) 290 | r.parameter.put("storefront", "1"); 291 | 292 | return r; 293 | } 294 | 295 | /** 296 | * Retrieves the seller object via the seller id. If a seller is not found no tokens will be consumed. 297 | * 298 | * @param domainId Amazon locale of the product {@link AmazonLocale} 299 | * @param seller The seller id of the merchant you want to request. For batch requests a comma separated list of sellerIds (up to 100). 300 | * The seller id is part of the offer object and can also be found on Amazon on seller profile pages in the seller parameter of the URL. 301 | * Example: A2L77EE7U53NWQ (Amazon.com Warehouse Deals) 302 | * @param storefront Valid values: 0 (false) and 1 (true). If specified the seller object will contain additional information about what items the seller is listing on Amazon. 303 | * This includes a list of ASINs as well as the total amount of items the seller has listed. 304 | * The following seller object fields will be set if data is available: asinList, asinListLastSeen. 305 | * If no data is available no additional tokens will be consumed. The ASIN list can contain up to 100,000 items. 306 | * As using the storefront parameter does not trigger any new collection it does not increase the processing time 307 | * of the request, though the response may be much bigger in size. 308 | * @param update Positive integer value. If the last live data collection from the Amazon storefront page is older than update hours force a new collection. Use this parameter in conjunction with the 309 | * storefront parameter. Token cost will only be applied if a new collection is triggered. 310 | * @return A ready to send request. 311 | */ 312 | public static Request getSellerRequest(final AmazonLocale domainId, String seller, boolean storefront, int update) { 313 | Request r = new Request(); 314 | r.path = "seller"; 315 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 316 | r.parameter.put("seller", seller); 317 | if (update >= 0) 318 | r.parameter.put("update", String.valueOf(update)); 319 | 320 | if (storefront || update >= 0) 321 | r.parameter.put("storefront", "1"); 322 | 323 | return r; 324 | } 325 | 326 | /** 327 | * Retrieve a Seller ID list of the most rated Amazon marketplace sellers. 328 | * 329 | * @param domainId Amazon locale of the product {@link AmazonLocale}. China is not supported. 330 | * @return A ready to send request. 331 | */ 332 | public static Request getTopSellerRequest(final AmazonLocale domainId) { 333 | Request r = new Request(); 334 | r.path = "topseller"; 335 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 336 | return r; 337 | } 338 | 339 | /** 340 | * Search for Amazon products using keywords with a maximum of 50 results per search term. 341 | * 342 | * @param domainId Amazon locale of the product {@link AmazonLocale} 343 | * @param term The term you want to search for. 344 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 345 | * @return A ready to send request. 346 | */ 347 | public static Request getProductSearchRequest(final AmazonLocale domainId, String term, Integer stats) { 348 | Request r = new Request(); 349 | r.path = "search"; 350 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 351 | r.parameter.put("type", "product"); 352 | r.parameter.put("term", term); 353 | 354 | if (stats != null && stats > 0) 355 | r.parameter.put("stats", String.valueOf(stats)); 356 | 357 | return r; 358 | } 359 | 360 | 361 | /** 362 | * Search for Amazon products using keywords with a maximum of 50 results per search term. 363 | * 364 | * @param domainId Amazon locale of the product {@link AmazonLocale} 365 | * @param term The term you want to search for. 366 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 367 | * @param page Valid values 0 - 9. Each search result page provides up to 10 results. To retrieve more results iterate the page parameter and keep all other parameters identical. Start with page 0 and stop when the response contains less than 10 results or you have reached page 9, which is the limit. When not using the page parameter the first 40 results will be returned. 368 | * @return A ready to send request. 369 | */ 370 | public static Request getProductSearchRequest(final AmazonLocale domainId, String term, Integer stats, int page) { 371 | Request r = new Request(); 372 | r.path = "search"; 373 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 374 | r.parameter.put("type", "product"); 375 | r.parameter.put("term", term); 376 | r.parameter.put("page", String.valueOf(page)); 377 | 378 | if (stats != null && stats > 0) 379 | r.parameter.put("stats", String.valueOf(stats)); 380 | 381 | return r; 382 | } 383 | 384 | /** 385 | * Search for Amazon products using keywords with a maximum of 50 results per search term. 386 | * 387 | * @param domainId Amazon locale of the product {@link AmazonLocale} 388 | * @param term The term you want to search for. 389 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 390 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 391 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 392 | * @param asinsOnly If true only the ASINs of the found products will be provided (instead of the product objects). 393 | * @return A ready to send request. 394 | */ 395 | public static Request getProductSearchRequest(final AmazonLocale domainId, String term, Integer stats, int update, boolean history, boolean asinsOnly) { 396 | Request r = new Request(); 397 | r.path = "search"; 398 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 399 | r.parameter.put("type", "product"); 400 | r.parameter.put("term", term); 401 | r.parameter.put("update", String.valueOf(update)); 402 | r.parameter.put("history", history ? "1" : "0"); 403 | r.parameter.put("asins-only", asinsOnly ? "1" : "0"); 404 | 405 | if (stats != null && stats > 0) 406 | r.parameter.put("stats", String.valueOf(stats)); 407 | 408 | return r; 409 | } 410 | 411 | /** 412 | * Search for Amazon products using keywords with a maximum of 50 results per search term. 413 | * 414 | * @param domainId Amazon locale of the product {@link AmazonLocale} 415 | * @param term The term you want to search for. 416 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 417 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 418 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 419 | * @param asinsOnly If true only the ASINs of the found products will be provided (instead of the product objects). 420 | * @param page Valid values 0 - 9. Each search result page provides up to 10 results. To retrieve more results iterate the page parameter and keep all other parameters identical. Start with page 0 and stop when the response contains less than 10 results or you have reached page 9, which is the limit. When not using the page parameter the first 40 results will be returned. 421 | * @return A ready to send request. 422 | */ 423 | public static Request getProductSearchRequest(final AmazonLocale domainId, String term, Integer stats, int update, boolean history, boolean asinsOnly, int page) { 424 | Request r = new Request(); 425 | r.path = "search"; 426 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 427 | r.parameter.put("type", "product"); 428 | r.parameter.put("term", term); 429 | r.parameter.put("update", String.valueOf(update)); 430 | r.parameter.put("history", history ? "1" : "0"); 431 | r.parameter.put("asins-only", asinsOnly ? "1" : "0"); 432 | r.parameter.put("page", String.valueOf(page)); 433 | 434 | if (stats != null && stats > 0) 435 | r.parameter.put("stats", String.valueOf(stats)); 436 | 437 | return r; 438 | } 439 | 440 | /** 441 | * Retrieves the product object for the specified ASIN and domain. 442 | * 443 | * @param domainId Amazon locale of the product {@link AmazonLocale} 444 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 445 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 446 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 447 | * @return A ready to send request. 448 | */ 449 | public static Request getProductRequest(final AmazonLocale domainId, Integer stats, Integer offers, final String... asins) { 450 | Request r = new Request(); 451 | r.path = "product"; 452 | r.parameter.put("asin", arrayToCsv(asins)); 453 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 454 | if (stats != null && stats > 0) 455 | r.parameter.put("stats", String.valueOf(stats)); 456 | 457 | if (offers != null && offers > 0) 458 | r.parameter.put("offers", String.valueOf(offers)); 459 | 460 | return r; 461 | } 462 | 463 | /** 464 | * Retrieves the product object(s) for the specified product code and domain. 465 | * 466 | * @param domainId Amazon locale of the product {@link AmazonLocale} 467 | * @param codes The product code of the product you want to request. We currently allow UPC, EAN and ISBN-13 codes. For batch requests a comma separated list of codes (up to 100). Multiple ASINs can have the same product code, so requesting a product code can return multiple products. 468 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 469 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 470 | * @return A ready to send request. 471 | */ 472 | public static Request getProductByCodeRequest(final AmazonLocale domainId, Integer stats, Integer offers, final String... codes) { 473 | Request r = new Request(); 474 | r.path = "product"; 475 | r.parameter.put("code", arrayToCsv(codes)); 476 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 477 | if (stats != null && stats > 0) 478 | r.parameter.put("stats", String.valueOf(stats)); 479 | 480 | if (offers != null && offers > 0) 481 | r.parameter.put("offers", String.valueOf(offers)); 482 | 483 | return r; 484 | } 485 | 486 | /** 487 | * Retrieves the product object for the specified ASIN and domain. 488 | * 489 | * @param domainId Amazon locale of the product {@link AmazonLocale} 490 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 491 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 492 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 493 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 494 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 495 | * @return A ready to send request. 496 | */ 497 | public static Request getProductRequest(final AmazonLocale domainId, Integer stats, Integer offers, int update, boolean history, final String... asins) { 498 | if (stats == null) { 499 | return getProductRequest(domainId, offers, null, null, update, history, asins); 500 | } else { 501 | long now = System.currentTimeMillis(); 502 | return getProductRequest(domainId, offers, String.valueOf((now - (stats * 24 * 60 * 60 * 1000L))), String.valueOf(now), update, history, asins); 503 | } 504 | } 505 | 506 | /** 507 | * Retrieves the product object(s) for the specified product code and domain. 508 | * 509 | * @param domainId Amazon locale of the product {@link AmazonLocale} 510 | * @param codes The product code of the product you want to request. We currently allow UPC, EAN and ISBN-13 codes. For batch requests a comma separated list of codes (up to 100). Multiple ASINs can have the same product code, so requesting a product code can return multiple products. 511 | * @param stats If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values of the last x days, where x is the value of the stats parameter. 512 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 513 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 514 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 515 | * @return A ready to send request. 516 | */ 517 | public static Request getProductByCodeRequest(final AmazonLocale domainId, Integer stats, Integer offers, int update, boolean history, final String... codes) { 518 | if (stats == null) { 519 | return getProductByCodeRequest(domainId, offers, null, null, update, history, codes); 520 | } else { 521 | long now = System.currentTimeMillis(); 522 | return getProductByCodeRequest(domainId, offers, String.valueOf((now - (stats * 24 * 60 * 60 * 1000L))), String.valueOf(now), update, history, codes); 523 | } 524 | } 525 | 526 | /** 527 | * Retrieves the product object for the specified ASIN and domain. 528 | * 529 | * @param domainId Amazon locale of the product {@link AmazonLocale} 530 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 531 | * @param statsStartDate Must be Unix epoch time milliseconds. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 532 | * @param statsEndDate the end of the stats interval. See statsStartDate. 533 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 534 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 535 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 536 | * @return A ready to send request. 537 | */ 538 | public static Request getProductRequest(final AmazonLocale domainId, Long statsStartDate, Long statsEndDate, Integer offers, int update, boolean history, final String... asins) { 539 | if (statsStartDate == null) { 540 | return getProductRequest(domainId, offers, null, null, update, history, asins); 541 | } else { 542 | return getProductRequest(domainId, offers, String.valueOf(statsStartDate), String.valueOf(statsEndDate), update, history, asins); 543 | } 544 | } 545 | 546 | 547 | /** 548 | * Retrieves the product object(s) for the specified product code and domain. 549 | * 550 | * @param domainId Amazon locale of the product {@link AmazonLocale} 551 | * @param codes The product code of the product you want to request. We currently allow UPC, EAN and ISBN-13 codes. For batch requests a comma separated list of codes (up to 100). Multiple ASINs can have the same product code, so requesting a product code can return multiple products. 552 | * @param statsStartDate Must be Unix epoch time milliseconds. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 553 | * @param statsEndDate the end of the stats interval. See statsStartDate. 554 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 555 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 556 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 557 | * @return A ready to send request. 558 | */ 559 | public static Request getProductByCodeRequest(final AmazonLocale domainId, Long statsStartDate, Long statsEndDate, Integer offers, int update, boolean history, final String... codes) { 560 | if (statsStartDate == null) { 561 | return getProductByCodeRequest(domainId, offers, null, null, update, history, codes); 562 | } else { 563 | return getProductByCodeRequest(domainId, offers, String.valueOf(statsStartDate), String.valueOf(statsEndDate), update, history, codes); 564 | } 565 | } 566 | 567 | /** 568 | * Retrieves the product object for the specified ASIN and domain. 569 | * 570 | * @param domainId Amazon locale of the product {@link AmazonLocale} 571 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 572 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 573 | * @param statsEndDate the end of the stats interval. See statsStartDate. 574 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 575 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 576 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 577 | * @return A ready to send request. 578 | */ 579 | public static Request getProductRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, int update, boolean history, final String... asins) { 580 | Request r = new Request(); 581 | r.path = "product"; 582 | r.parameter.put("asin", arrayToCsv(asins)); 583 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 584 | r.parameter.put("update", String.valueOf(update)); 585 | r.parameter.put("history", history ? "1" : "0"); 586 | 587 | if (statsStartDate != null && statsEndDate != null) 588 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 589 | 590 | if (offers != null && offers > 0) 591 | r.parameter.put("offers", String.valueOf(offers)); 592 | 593 | return r; 594 | } 595 | 596 | /** 597 | * Retrieves the product object for the specified ASIN and domain. 598 | * 599 | * @param domainId Amazon locale of the product {@link AmazonLocale} 600 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 601 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 602 | * @param statsEndDate the end of the stats interval. See statsStartDate. 603 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 604 | * @param buybox If specified and true the product and statistics object will include all available buy box related data 605 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 606 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 607 | * @param rental If true the rental price will be collected when available. Can only be used in conjunction with the offers parameter. 608 | * @param rating If true the product object will include our existing RATING and COUNT_REVIEWS history of the csv field, regardless if the offers parameter is used 609 | * @return A ready to send request. 610 | */ 611 | public static Request getProductRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, boolean buybox, int update, boolean history, 612 | boolean rental, boolean rating, final String... asins) { 613 | Request r = new Request(); 614 | r.path = "product"; 615 | r.parameter.put("asin", arrayToCsv(asins)); 616 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 617 | r.parameter.put("update", String.valueOf(update)); 618 | r.parameter.put("history", history ? "1" : "0"); 619 | r.parameter.put("rental", rental ? "1" : "0"); 620 | r.parameter.put("rating", rating ? "1" : "0"); 621 | r.parameter.put("buybox", buybox ? "1" : "0"); 622 | 623 | if (statsStartDate != null && statsEndDate != null) 624 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 625 | 626 | if (offers != null && offers > 0) 627 | r.parameter.put("offers", String.valueOf(offers)); 628 | 629 | return r; 630 | } 631 | 632 | 633 | /** 634 | * Retrieves the product object for the specified ASIN and domain. 635 | * 636 | * @param domainId Amazon locale of the product {@link AmazonLocale} 637 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 638 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 639 | * @param statsEndDate the end of the stats interval. See statsStartDate. 640 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 641 | * @param buybox If specified and true the product and statistics object will include all available buy box related data 642 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 643 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 644 | * @param rental If true the rental price will be collected when available. Can only be used in conjunction with the offers parameter. 645 | * @param rating If true the product object will include our existing RATING and COUNT_REVIEWS history of the csv field, regardless if the offers parameter is used 646 | * @param onlyLiveOffers If specified and has the value 1 the product object will only include live marketplace offers (when used in combination with the offers parameter). If you do not need historical offers use this to have them removed from the response. This can improve processing time and considerably decrease the size of the response. 647 | * @param days Any positive integer value. If specified and has positive value X the product object will limit all historical data to the recent X days. This includes the csv, buyBoxSellerIdHistory, salesRanks, offers and offers.offerCSV fields. If you do not need old historical data use this to have it removed from the response. This can improve processing time and considerably decrease the size of the response. 648 | * @return A ready to send request. 649 | */ 650 | public static Request getProductRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, boolean buybox, int update, boolean history, 651 | boolean rental, boolean rating, boolean onlyLiveOffers, int days, final String... asins) { 652 | Request r = new Request(); 653 | r.path = "product"; 654 | r.parameter.put("asin", arrayToCsv(asins)); 655 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 656 | r.parameter.put("update", String.valueOf(update)); 657 | r.parameter.put("history", history ? "1" : "0"); 658 | r.parameter.put("rental", rental ? "1" : "0"); 659 | r.parameter.put("rating", rating ? "1" : "0"); 660 | r.parameter.put("buybox", buybox ? "1" : "0"); 661 | r.parameter.put("only-live-offers", onlyLiveOffers ? "1" : "0"); 662 | r.parameter.put("days", String.valueOf(days)); 663 | 664 | if (statsStartDate != null && statsEndDate != null) 665 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 666 | 667 | if (offers != null && offers > 0) 668 | r.parameter.put("offers", String.valueOf(offers)); 669 | 670 | return r; 671 | } 672 | 673 | /** 674 | * Retrieves the product object for the specified ASIN and domain. 675 | * 676 | * @param domainId Amazon locale of the product {@link AmazonLocale} 677 | * @param asins ASINs to request, must contain between 1 and 100 ASINs - or max 20 ASINs if the offers parameter is used. 678 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 679 | * @param statsEndDate the end of the stats interval. See statsStartDate. 680 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 681 | * @param buybox If specified and true the product and statistics object will include all available buy box related data 682 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 683 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 684 | * @param rental If true the rental price will be collected when available. Can only be used in conjunction with the offers parameter. 685 | * @param rating If true the product object will include our existing RATING and COUNT_REVIEWS history of the csv field, regardless if the offers parameter is used 686 | * @param videos If true the product object will include video metadata 687 | * @param aplus If true the product object will include A+ content 688 | * @param stock If true the product object will include stock data 689 | * @param onlyLiveOffers If specified and has the value 1 the product object will only include live marketplace offers (when used in combination with the offers parameter). If you do not need historical offers use this to have them removed from the response. This can improve processing time and considerably decrease the size of the response. 690 | * @param days Any positive integer value. If specified and has positive value X the product object will limit all historical data to the recent X days. This includes the csv, buyBoxSellerIdHistory, salesRanks, offers and offers.offerCSV fields. If you do not need old historical data use this to have it removed from the response. This can improve processing time and considerably decrease the size of the response. 691 | * @return A ready to send request. 692 | */ 693 | public static Request getProductRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, boolean buybox, int update, boolean history, 694 | boolean rental, boolean rating, boolean videos, boolean aplus, boolean stock, boolean onlyLiveOffers, int days, final String... asins) { 695 | Request r = new Request(); 696 | r.path = "product"; 697 | r.parameter.put("asin", arrayToCsv(asins)); 698 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 699 | r.parameter.put("update", String.valueOf(update)); 700 | r.parameter.put("history", history ? "1" : "0"); 701 | r.parameter.put("rental", rental ? "1" : "0"); 702 | r.parameter.put("rating", rating ? "1" : "0"); 703 | r.parameter.put("buybox", buybox ? "1" : "0"); 704 | r.parameter.put("videos", videos ? "1" : "0"); 705 | r.parameter.put("aplus", aplus ? "1" : "0"); 706 | r.parameter.put("stock", stock ? "1" : "0"); 707 | r.parameter.put("only-live-offers", onlyLiveOffers ? "1" : "0"); 708 | r.parameter.put("days", String.valueOf(days)); 709 | 710 | if (statsStartDate != null && statsEndDate != null) 711 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 712 | 713 | if (offers != null && offers > 0) 714 | r.parameter.put("offers", String.valueOf(offers)); 715 | 716 | return r; 717 | } 718 | 719 | 720 | /** 721 | * Retrieves the product object(s) for the specified product code and domain. 722 | * 723 | * @param domainId Amazon locale of the product {@link AmazonLocale} 724 | * @param codes The product code of the product you want to request. We currently allow UPC, EAN and ISBN-13 codes. For batch requests a comma separated list of codes (up to 100). Multiple ASINs can have the same product code, so requesting a product code can return multiple products. 725 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 726 | * @param statsEndDate the end of the stats interval. See statsStartDate. 727 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 728 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 729 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 730 | * @return A ready to send request. 731 | */ 732 | public static Request getProductByCodeRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, int update, boolean history, final String... codes) { 733 | Request r = new Request(); 734 | r.path = "product"; 735 | r.parameter.put("code", arrayToCsv(codes)); 736 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 737 | r.parameter.put("update", String.valueOf(update)); 738 | r.parameter.put("history", history ? "1" : "0"); 739 | 740 | if (statsStartDate != null && statsEndDate != null) 741 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 742 | 743 | if (offers != null && offers > 0) 744 | r.parameter.put("offers", String.valueOf(offers)); 745 | 746 | return r; 747 | } 748 | 749 | /** 750 | * Retrieves the product object(s) for the specified product code and domain. 751 | * 752 | * @param domainId Amazon locale of the product {@link AmazonLocale} 753 | * @param codes The product code of the product you want to request. We currently allow UPC, EAN and ISBN-13 codes. For batch requests a comma separated list of codes (up to 100). Multiple ASINs can have the same product code, so requesting a product code can return multiple products. 754 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 755 | * @param statsEndDate the end of the stats interval. See statsStartDate. 756 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 757 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 758 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 759 | * @param rental If true the rental price will be collected when available. Can only be used in conjunction with the offers parameter. 760 | * @param rating If true the product object will include our existing RATING and COUNT_REVIEWS history of the csv field, regardless if the offers parameter is used 761 | * @return A ready to send request. 762 | */ 763 | public static Request getProductByCodeRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, int update, boolean history, 764 | boolean rental, boolean rating, final String... codes) { 765 | Request r = new Request(); 766 | r.path = "product"; 767 | r.parameter.put("code", arrayToCsv(codes)); 768 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 769 | r.parameter.put("update", String.valueOf(update)); 770 | r.parameter.put("history", history ? "1" : "0"); 771 | r.parameter.put("rental", rental ? "1" : "0"); 772 | r.parameter.put("rating", rating ? "1" : "0"); 773 | 774 | if (statsStartDate != null && statsEndDate != null) 775 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 776 | 777 | if (offers != null && offers > 0) 778 | r.parameter.put("offers", String.valueOf(offers)); 779 | 780 | return r; 781 | } 782 | 783 | /** 784 | * Retrieves the product object(s) for the specified product code and domain. 785 | * 786 | * @param domainId Amazon locale of the product {@link AmazonLocale} 787 | * @param codes The product code of the product you want to request. We currently allow UPC, EAN and ISBN-13 codes. For batch requests a comma separated list of codes (up to 100). Multiple ASINs can have the same product code, so requesting a product code can return multiple products. 788 | * @param statsStartDate Must ISO8601 coded date (with or without time in UTC). Example: 2015-12-31 or 2015-12-31T14:51Z. If specified (= not null) the product object will have a stats field with quick access to current prices, min/max prices and the weighted mean values in the interval specified statsStartDate to statsEndDate. . 789 | * @param statsEndDate the end of the stats interval. See statsStartDate. 790 | * @param history Whether or not to include the product's history data (csv field). If you do not evaluate the csv field set to false to speed up the request and reduce traffic. 791 | * @param buybox If specified and true the product and statistics object will include all available buy box related data 792 | * @param update If the product's last refresh is older than update-hours force a refresh. Use this to speed up requests if up-to-date data is not required. Might cost an extra token if 0 (= live data). Default 1. 793 | * @param offers If specified (= not null) determines the number of marketplace offers to retrieve. 794 | * @param rental If true the rental price will be collected when available. Can only be used in conjunction with the offers parameter. 795 | * @param rating If true the product object will include our existing RATING and COUNT_REVIEWS history of the csv field, regardless if the offers parameter is used 796 | * @return A ready to send request. 797 | */ 798 | public static Request getProductByCodeRequest(final AmazonLocale domainId, Integer offers, String statsStartDate, String statsEndDate, boolean buybox, int update, boolean history, 799 | boolean rental, boolean rating, final String... codes) { 800 | Request r = new Request(); 801 | r.path = "product"; 802 | r.parameter.put("code", arrayToCsv(codes)); 803 | r.parameter.put("domain", String.valueOf(domainId.ordinal())); 804 | r.parameter.put("update", String.valueOf(update)); 805 | r.parameter.put("history", history ? "1" : "0"); 806 | r.parameter.put("rental", rental ? "1" : "0"); 807 | r.parameter.put("rating", rating ? "1" : "0"); 808 | r.parameter.put("buybox", buybox ? "1" : "0"); 809 | 810 | if (statsStartDate != null && statsEndDate != null) 811 | r.parameter.put("stats", statsStartDate + "," + statsEndDate); 812 | 813 | if (offers != null && offers > 0) 814 | r.parameter.put("offers", String.valueOf(offers)); 815 | 816 | return r; 817 | } 818 | 819 | 820 | @Override 821 | public String toString() { 822 | return gsonPretty.toJson(this); 823 | } 824 | } --------------------------------------------------------------------------------