diff --git a/package-lock.json b/package-lock.json
index c757228b2cbc7f7d9d5280848c8c5b1bf2156425..4f806e283d0c8e34f7e5a3a888f5cb88dcc1f993 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -181,6 +181,11 @@
         "ansi-regex": "^4.1.0"
       }
     },
+    "uuid": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
+    },
     "which-module": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
diff --git a/package.json b/package.json
index 40bd4ded330c2f4bdfdae0ae532addec32eb2579..5993bd71b2e9c9111a67b8bc2620f284651802ae 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
   "dependencies": {
     "axios": "^0.19.0",
     "random-words": "^1.1.0",
+    "uuid": "^3.3.3",
     "yargs": "^14.0.0"
   }
 }
diff --git a/pom.xml b/pom.xml
index 5bb4f6219ffff0c1d535b912f7e3e218c3f68779..25df236c7cd3e9811920db4084528af0f9704c23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>org.eclipsefoundation</groupId>
 	<artifactId>marketplace-rest-api</artifactId>
-	<version>0.1-ALPHA</version>
+	<version>0.2-BETA</version>
 	<properties>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<surefire-plugin.version>2.22.0</surefire-plugin.version>
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java b/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java
index 586d631d258233c52382f7844610e705062f9ed5..56829ef4ae15ba3259d282dc88ae9a037e837d00 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java
@@ -60,8 +60,6 @@ public class DefaultMongoDao implements MongoDao {
 		if (LOGGER.isDebugEnabled()) {
 			LOGGER.debug("Querying MongoDB using the following query: {}", q);
 		}
-
-		LOGGER.error("{}", q);
 		
 		LOGGER.debug("Getting aggregate results");
 		return getCollection(q.getDocType()).aggregate(q.getPipeline(getLimit(q)), q.getDocType()).limit(getLimit(q))
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java
index 29d30607c16d7b6d3b7f5f03f72e6bd4a2177d11..1d9dbb4bdf83d10313555286ad6beb8c3cc494fa 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java
@@ -9,8 +9,7 @@
 */
 package org.eclipsefoundation.marketplace.dto;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Objects;
 
 import io.quarkus.runtime.annotations.RegisterForReflection;
 
@@ -22,22 +21,21 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
  */
 @RegisterForReflection
 public class Category {
-	private Integer id;
+	private String id;
 	private String name;
 	private String url;
-	private List<Integer> marketIds;
 
 	/**
 	 * @return the id
 	 */
-	public int getId() {
+	public String getId() {
 		return id;
 	}
 
 	/**
 	 * @param id the id to set
 	 */
-	public void setId(int id) {
+	public void setId(String id) {
 		this.id = id;
 	}
 
@@ -69,58 +67,24 @@ public class Category {
 		this.url = url;
 	}
 
-	/**
-	 * @return the marketIds
-	 */
-	public List<Integer> getMarketIds() {
-		return new ArrayList<>(marketIds);
-	}
-
-	/**
-	 * @param marketIds the marketIds to set
-	 */
-	public void setMarketIds(List<Integer> marketIds) {
-		this.marketIds = new ArrayList<>(marketIds);
-	}
-
 	@Override
 	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + id;
-		result = prime * result + ((marketIds == null) ? 0 : marketIds.hashCode());
-		result = prime * result + ((name == null) ? 0 : name.hashCode());
-		result = prime * result + ((url == null) ? 0 : url.hashCode());
-		return result;
+		return Objects.hash(id, name, url);
 	}
 
 	@Override
 	public boolean equals(Object obj) {
-		if (this == obj)
+		if (this == obj) {
 			return true;
-		if (obj == null)
+		}
+		if (obj == null) {
 			return false;
-		if (getClass() != obj.getClass())
+		}
+		if (getClass() != obj.getClass()) {
 			return false;
+		}
 		Category other = (Category) obj;
-		if (id != other.id)
-			return false;
-		if (marketIds == null) {
-			if (other.marketIds != null)
-				return false;
-		} else if (!marketIds.equals(other.marketIds))
-			return false;
-		if (name == null) {
-			if (other.name != null)
-				return false;
-		} else if (!name.equals(other.name))
-			return false;
-		if (url == null) {
-			if (other.url != null)
-				return false;
-		} else if (!url.equals(other.url))
-			return false;
-		return true;
+		return Objects.equals(id, other.id) && Objects.equals(name, other.name) && Objects.equals(url, other.url);
 	}
 
 	@Override
@@ -132,8 +96,6 @@ public class Category {
 		builder.append(name);
 		builder.append(", url=");
 		builder.append(url);
-		builder.append(", marketIds=");
-		builder.append(marketIds);
 		builder.append("]");
 		return builder.toString();
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java
index 69a2be27d43275f458a35e6f902420a54fe5909e..09d3138392ecb8f1a202d5287ce16298bd4430b4 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java
@@ -8,6 +8,9 @@ package org.eclipsefoundation.marketplace.dto;
 
 import java.sql.Date;
 
+import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
+
 /**
  * Domain object representing the data stored for installs.
  * 
@@ -18,9 +21,17 @@ public class Install {
 	private Date installDate;
 	private String os;
 	private String version;
-	private int listingId;
+	private String listingId;
 	private String javaVersion;
 
+	public static Install createFromRequest(RequestWrapper wrap) {
+		Install install = new Install();
+		install.installDate = new Date(System.currentTimeMillis());
+		install.listingId = wrap.getFirstParam(UrlParameterNames.ID).get();
+		
+		return install;
+	}
+	
 	/**
 	 * @return the installDate
 	 */
@@ -66,14 +77,14 @@ public class Install {
 	/**
 	 * @return the listingId
 	 */
-	public int getListingId() {
+	public String getListingId() {
 		return listingId;
 	}
 
 	/**
 	 * @param listingId the listingId to set
 	 */
-	public void setListingId(int listingId) {
+	public void setListingId(String listingId) {
 		this.listingId = listingId;
 	}
 
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
index f12e7ec131bbfc765e8534de59641666c7aa0843..c4819e02cc86a35b7be6fd16a2741cb19f543036 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
@@ -17,7 +17,7 @@ import javax.json.bind.annotation.JsonbProperty;
 import javax.json.bind.annotation.JsonbTransient;
 
 import org.eclipsefoundation.marketplace.model.SortableField;
-import org.eclipsefoundation.marketplace.namespace.MongoFieldNames;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
 import io.quarkus.runtime.annotations.RegisterForReflection;
 
@@ -29,10 +29,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
 @RegisterForReflection
 public class Listing {
 
-	@JsonbTransient
 	private String id;
-	@SortableField(name = "listing_id")
-	private long listingId;
 	@SortableField
 	private String title;
 	private String url;
@@ -55,16 +52,16 @@ public class Listing {
 	@SortableField
 	private long favoriteCount;
 
-	@SortableField(name = MongoFieldNames.CREATION_DATE)
-	@JsonbProperty(MongoFieldNames.CREATION_DATE)
+	@SortableField(name = DatabaseFieldNames.CREATION_DATE)
+	@JsonbProperty(DatabaseFieldNames.CREATION_DATE)
 	private long creationDate;
 
-	@SortableField(name = MongoFieldNames.UPDATE_DATE)
-	@JsonbProperty(MongoFieldNames.UPDATE_DATE)
+	@SortableField(name = DatabaseFieldNames.UPDATE_DATE)
+	@JsonbProperty(DatabaseFieldNames.UPDATE_DATE)
 	private long updateDate;
-	@JsonbProperty(MongoFieldNames.LICENSE_TYPE)
+	@JsonbProperty(DatabaseFieldNames.LICENSE_TYPE)
 	private String license;
-	private List<Integer> categoryIds;
+	private List<String> categoryIds;
 	private List<Category> categories;
 	private List<Organization> organizations;
 	private List<Author> authors;
@@ -96,21 +93,7 @@ public class Listing {
 	public void setId(String id) {
 		this.id = id;
 	}
-
-	/**
-	 * @return the listingId
-	 */
-	public long getListingId() {
-		return listingId;
-	}
-
-	/**
-	 * @param listingId the id to set
-	 */
-	public void setListingId(long listingId) {
-		this.listingId = listingId;
-	}
-
+	
 	/**
 	 * @return the title
 	 */
@@ -325,14 +308,15 @@ public class Listing {
 	/**
 	 * @return the categoryIds
 	 */
-	public List<Integer> getCategoryIds() {
+	@JsonbTransient
+	public List<String> getCategoryIds() {
 		return categoryIds;
 	}
 
 	/**
 	 * @param categoryIds the categoryIds to set
 	 */
-	public void setCategoryIds(List<Integer> categoryIds) {
+	public void setCategoryIds(List<String> categoryIds) {
 		this.categoryIds = new ArrayList<>(categoryIds);
 	}
 
@@ -411,11 +395,42 @@ public class Listing {
 		this.versions = new ArrayList<>(versions);
 	}
 	
+	@Override
+	public int hashCode() {
+		return Objects.hash(authors, body, categories, categoryIds, creationDate, favoriteCount, foundationMember,
+				homepageUrl, id, installsRecent, installsTotal, license, logo, organizations, status, supportUrl, tags,
+				teaser, title, updateDate, url, versions);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		Listing other = (Listing) obj;
+		return Objects.equals(authors, other.authors) && Objects.equals(body, other.body)
+				&& Objects.equals(categories, other.categories) && Objects.equals(categoryIds, other.categoryIds)
+				&& creationDate == other.creationDate && favoriteCount == other.favoriteCount
+				&& foundationMember == other.foundationMember && Objects.equals(homepageUrl, other.homepageUrl)
+				&& Objects.equals(id, other.id) && installsRecent == other.installsRecent
+				&& installsTotal == other.installsTotal && Objects.equals(license, other.license)
+				&& Objects.equals(logo, other.logo) && Objects.equals(organizations, other.organizations)
+				&& Objects.equals(status, other.status) && Objects.equals(supportUrl, other.supportUrl)
+				&& Objects.equals(tags, other.tags) && Objects.equals(teaser, other.teaser)
+				&& Objects.equals(title, other.title) && updateDate == other.updateDate
+				&& Objects.equals(url, other.url) && Objects.equals(versions, other.versions);
+	}
+
 	@Override
 	public String toString() {
 		StringBuilder sb = new StringBuilder();
 		sb.append(", id=").append(id);
-		sb.append(", listingId=").append(listingId);
 		sb.append(", title=").append(title);
 		sb.append(", url=").append(url);
 		sb.append(", supportUrl=").append(supportUrl);
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java
index 0a41208468257f2588a45aa867bdb0a149331333..2b1c2e0b8a329ecbde4278ad6481f51a79ac4788 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java
@@ -9,11 +9,11 @@
 */
 package org.eclipsefoundation.marketplace.dto;
 
-import java.security.InvalidParameterException;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Objects;
+
+import javax.json.bind.annotation.JsonbTransient;
 
 import io.quarkus.runtime.annotations.RegisterForReflection;
 
@@ -25,9 +25,10 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
  */
 @RegisterForReflection
 public class Market {
-	private int id;
+	private String id;
 	private String name;
 	private String url;
+	private List<String> categoryIds;
 	private List<Category> categories;
 
 	/**
@@ -36,19 +37,20 @@ public class Market {
 	 */
 	public Market() {
 		this.categories = new LinkedList<>();
+		this.categoryIds = new LinkedList<>();
 	}
 
 	/**
 	 * @return the id
 	 */
-	public int getId() {
+	public String getId() {
 		return id;
 	}
 
 	/**
 	 * @param id the id to set
 	 */
-	public void setId(int id) {
+	public void setId(String id) {
 		this.id = id;
 	}
 
@@ -90,36 +92,23 @@ public class Market {
 	/**
 	 * @param categories the categories to set
 	 */
+	@JsonbTransient
 	public void setCategories(List<Category> categories) {
 		this.categories = new ArrayList<>(categories);
 	}
 
 	/**
-	 * Remove a category from the market definition.
-	 * 
-	 * @param c the category to remove if present
-	 * @return the category removed if it was present, or null if it was missing.
-	 * @throws InvalidParameterException if the passed category is an illegal value
-	 *                                   (i.e. null)
+	 * @return the categoryIds
 	 */
-	public Category removeCategory(Category c) {
-		int idx = categories.indexOf(c);
-		if (idx > 0) {
-			return this.categories.remove(idx);
-		} else {
-			return null;
-		}
+	@JsonbTransient
+	public List<String> getCategoryIds() {
+		return new ArrayList<>(categoryIds);
 	}
 
 	/**
-	 * Add a category to the market definition.
-	 * 
-	 * @param c the category to add
-	 * @throws IllegalArgumentException if the passed category is an illegal value
-	 *                                   (i.e. null)
+	 * @param categoryIds the categoryIds to set
 	 */
-	public void addCategory(Category c) {
-		Objects.requireNonNull(c);
-		this.categories.add(c);
+	public void setCategoryIds(List<String> categoryIds) {
+		this.categoryIds = new ArrayList<>(categoryIds);
 	}
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java
index a08d4ad7289da51c2ff59c6b70b6a7a80a2d47e0..cbaa4dc0f091fb0c35720d72c29761a1f791a3b3 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java
@@ -6,6 +6,7 @@
  */
 package org.eclipsefoundation.marketplace.dto.codecs;
 
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 import org.bson.BsonReader;
@@ -19,7 +20,7 @@ import org.bson.codecs.DecoderContext;
 import org.bson.codecs.EncoderContext;
 import org.eclipsefoundation.marketplace.dto.Catalog;
 import org.eclipsefoundation.marketplace.dto.converters.TabConverter;
-import org.eclipsefoundation.marketplace.namespace.MongoFieldNames;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
 import com.mongodb.MongoClient;
 
@@ -49,14 +50,14 @@ public class CatalogCodec implements CollectibleCodec<Catalog> {
 	public void encode(BsonWriter writer, Catalog value, EncoderContext encoderContext) {
 		Document doc = new Document();
 
-		doc.put(MongoFieldNames.DOCID, value.getId());
-		doc.put(MongoFieldNames.CATALOG_TITLE, value.getTitle());
-		doc.put(MongoFieldNames.CATALOG_URL, value.getUrl());
-		doc.put(MongoFieldNames.CATALOG_ICON, value.getIcon());
-		doc.put(MongoFieldNames.CATALOG_SELF_CONTAINED, value.isSelfContained());
-		doc.put(MongoFieldNames.CATALOG_SEARCH_ENABLED, value.isSearchEnabled());
-		doc.put(MongoFieldNames.CATALOG_DEPENDENCIES_REPOSITORY, value.getDependenciesRepository());
-		doc.put(MongoFieldNames.CATALOG_TABS,
+		doc.put(DatabaseFieldNames.DOCID, value.getId());
+		doc.put(DatabaseFieldNames.CATALOG_TITLE, value.getTitle());
+		doc.put(DatabaseFieldNames.CATALOG_URL, value.getUrl());
+		doc.put(DatabaseFieldNames.CATALOG_ICON, value.getIcon());
+		doc.put(DatabaseFieldNames.CATALOG_SELF_CONTAINED, value.isSelfContained());
+		doc.put(DatabaseFieldNames.CATALOG_SEARCH_ENABLED, value.isSearchEnabled());
+		doc.put(DatabaseFieldNames.CATALOG_DEPENDENCIES_REPOSITORY, value.getDependenciesRepository());
+		doc.put(DatabaseFieldNames.CATALOG_TABS,
 				value.getTabs().stream().map(tabConverter::convert).collect(Collectors.toList()));
 		documentCodec.encode(writer, doc, encoderContext);
 	}
@@ -70,20 +71,23 @@ public class CatalogCodec implements CollectibleCodec<Catalog> {
 	public Catalog decode(BsonReader reader, DecoderContext decoderContext) {
 		Document document = documentCodec.decode(reader, decoderContext);
 		Catalog out = new Catalog();
-		out.setId(document.getString(MongoFieldNames.DOCID));
-		out.setUrl(document.getString(MongoFieldNames.CATALOG_URL));
-		out.setTitle(document.getString(MongoFieldNames.CATALOG_TITLE));
-		out.setIcon(document.getString(MongoFieldNames.CATALOG_ICON));
-		out.setSelfContained(document.getBoolean(MongoFieldNames.CATALOG_SELF_CONTAINED));
-		out.setSearchEnabled(document.getBoolean(MongoFieldNames.CATALOG_SEARCH_ENABLED));
-		out.setDependenciesRepository(document.getString(MongoFieldNames.CATALOG_DEPENDENCIES_REPOSITORY));
-		out.setTabs(document.getList(MongoFieldNames.CATALOG_TABS, Document.class).stream().map(tabConverter::convert)
+		out.setId(document.getString(DatabaseFieldNames.DOCID));
+		out.setUrl(document.getString(DatabaseFieldNames.CATALOG_URL));
+		out.setTitle(document.getString(DatabaseFieldNames.CATALOG_TITLE));
+		out.setIcon(document.getString(DatabaseFieldNames.CATALOG_ICON));
+		out.setSelfContained(document.getBoolean(DatabaseFieldNames.CATALOG_SELF_CONTAINED));
+		out.setSearchEnabled(document.getBoolean(DatabaseFieldNames.CATALOG_SEARCH_ENABLED));
+		out.setDependenciesRepository(document.getString(DatabaseFieldNames.CATALOG_DEPENDENCIES_REPOSITORY));
+		out.setTabs(document.getList(DatabaseFieldNames.CATALOG_TABS, Document.class).stream().map(tabConverter::convert)
 				.collect(Collectors.toList()));
 		return out;
 	}
 
 	@Override
 	public Catalog generateIdIfAbsentFromDocument(Catalog document) {
+		if (!documentHasId(document)) {
+			document.setId(UUID.randomUUID().toString());
+		}
 		return document;
 	}
 
@@ -94,7 +98,6 @@ public class CatalogCodec implements CollectibleCodec<Catalog> {
 
 	@Override
 	public BsonValue getDocumentId(Catalog document) {
-		// TODO Auto-generated method stub
 		return new BsonString(document.getId());
 	}
 
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java
index 187cd7d9455101a5536b0f551308020f894c7771..944aedf4e73b32b0b894429f9a4f34b054e6a01b 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java
@@ -6,8 +6,11 @@
  */
 package org.eclipsefoundation.marketplace.dto.codecs;
 
-import org.bson.BsonInt32;
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
 import org.bson.BsonReader;
+import org.bson.BsonString;
 import org.bson.BsonValue;
 import org.bson.BsonWriter;
 import org.bson.Document;
@@ -55,18 +58,20 @@ public class CategoryCodec implements CollectibleCodec<Category> {
 
 	@Override
 	public Category generateIdIfAbsentFromDocument(Category document) {
-		// TODO Auto-generated method stub
+		if (!documentHasId(document)) {
+			document.setId(UUID.randomUUID().toString());
+		}
 		return document;
 	}
 
 	@Override
 	public boolean documentHasId(Category document) {
-		return document.getId() > 0;
+		return !StringUtils.isBlank(document.getId());
 	}
 
 	@Override
 	public BsonValue getDocumentId(Category document) {
-		return new BsonInt32(document.getId());
+		return new BsonString(document.getId());
 	}
 
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
index 8327390711f154bae6867fcac2ccc61010f7458d..993e0d00b0919cc9357fac81f5229a8536c45ba0 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
@@ -25,7 +25,7 @@ import org.eclipsefoundation.marketplace.dto.converters.CategoryConverter;
 import org.eclipsefoundation.marketplace.dto.converters.OrganizationConverter;
 import org.eclipsefoundation.marketplace.dto.converters.SolutionVersionConverter;
 import org.eclipsefoundation.marketplace.dto.converters.TagConverter;
-import org.eclipsefoundation.marketplace.namespace.MongoFieldNames;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
 import com.mongodb.MongoClient;
 
@@ -64,33 +64,32 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 		Document doc = new Document();
 
 		// for each of the fields, get the value from the unencoded object and set it
-		doc.put(MongoFieldNames.DOCID, value.getId());
-		doc.put(MongoFieldNames.LISTING_ID, value.getListingId());
-		doc.put(MongoFieldNames.LISTING_TITLE, value.getTitle());
-		doc.put(MongoFieldNames.LISTING_URL, value.getUrl());
-		doc.put(MongoFieldNames.SUPPORT_PAGE_URL, value.getSupportUrl());
-		doc.put(MongoFieldNames.HOME_PAGE_URL, value.getHomepageUrl());
-		doc.put(MongoFieldNames.LISTING_BODY, value.getTeaser());
-		doc.put(MongoFieldNames.LISTING_TEASER, value.getBody());
-		doc.put(MongoFieldNames.MARKETPLACE_FAVORITES, value.getFavoriteCount());
-		doc.put(MongoFieldNames.RECENT_NSTALLS, value.getInstallsRecent());
-		doc.put(MongoFieldNames.TOTAL_NSTALLS, value.getInstallsTotal());
-		doc.put(MongoFieldNames.LICENSE_TYPE, value.getLicense());
-		doc.put(MongoFieldNames.LISTING_STATUS, value.getStatus());
-		doc.put(MongoFieldNames.UPDATE_DATE, new Date(value.getUpdateDate()));
-		doc.put(MongoFieldNames.CREATION_DATE, new Date(value.getCreationDate()));
-		doc.put(MongoFieldNames.FOUNDATION_MEMBER_FLAG, value.isFoundationMember());
-		doc.put(MongoFieldNames.CATEGORY_IDS, value.getCategoryIds());
+		doc.put(DatabaseFieldNames.DOCID, value.getId());
+		doc.put(DatabaseFieldNames.LISTING_TITLE, value.getTitle());
+		doc.put(DatabaseFieldNames.URL, value.getUrl());
+		doc.put(DatabaseFieldNames.SUPPORT_PAGE_URL, value.getSupportUrl());
+		doc.put(DatabaseFieldNames.HOME_PAGE_URL, value.getHomepageUrl());
+		doc.put(DatabaseFieldNames.LISTING_BODY, value.getTeaser());
+		doc.put(DatabaseFieldNames.LISTING_TEASER, value.getBody());
+		doc.put(DatabaseFieldNames.MARKETPLACE_FAVORITES, value.getFavoriteCount());
+		doc.put(DatabaseFieldNames.RECENT_NSTALLS, value.getInstallsRecent());
+		doc.put(DatabaseFieldNames.TOTAL_NSTALLS, value.getInstallsTotal());
+		doc.put(DatabaseFieldNames.LICENSE_TYPE, value.getLicense());
+		doc.put(DatabaseFieldNames.LISTING_STATUS, value.getStatus());
+		doc.put(DatabaseFieldNames.UPDATE_DATE, new Date(value.getUpdateDate()));
+		doc.put(DatabaseFieldNames.CREATION_DATE, new Date(value.getCreationDate()));
+		doc.put(DatabaseFieldNames.FOUNDATION_MEMBER_FLAG, value.isFoundationMember());
+		doc.put(DatabaseFieldNames.CATEGORY_IDS, value.getCategoryIds());
 
 		// for nested document types, use the converters to safely transform into BSON
 		// documents
-		doc.put(MongoFieldNames.LISTING_ORGANIZATIONS,
+		doc.put(DatabaseFieldNames.LISTING_ORGANIZATIONS,
 				value.getOrganizations().stream().map(organizationConverter::convert).collect(Collectors.toList()));
-		doc.put(MongoFieldNames.LISTING_AUTHORS,
+		doc.put(DatabaseFieldNames.LISTING_AUTHORS,
 				value.getAuthors().stream().map(authorConverter::convert).collect(Collectors.toList()));
-		doc.put(MongoFieldNames.LISTING_TAGS,
+		doc.put(DatabaseFieldNames.LISTING_TAGS,
 				value.getTags().stream().map(tagConverter::convert).collect(Collectors.toList()));
-		doc.put(MongoFieldNames.LISTING_VERSIONS,
+		doc.put(DatabaseFieldNames.LISTING_VERSIONS,
 				value.getVersions().stream().map(versionConverter::convert).collect(Collectors.toList()));
 		documentCodec.encode(writer, doc, encoderContext);
 	}
@@ -106,37 +105,36 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 		Listing out = new Listing();
 
 		// for each field, get the value from the encoded object and set it in POJO
-		out.setId(document.getString(MongoFieldNames.DOCID));
-		out.setListingId(document.getLong(MongoFieldNames.LISTING_ID));
-		out.setTitle(document.getString(MongoFieldNames.LISTING_TITLE));
-		out.setUrl(document.getString(MongoFieldNames.LISTING_URL));
-		out.setSupportUrl(document.getString(MongoFieldNames.SUPPORT_PAGE_URL));
-		out.setHomepageUrl(document.getString(MongoFieldNames.HOME_PAGE_URL));
-		out.setTeaser(document.getString(MongoFieldNames.LISTING_TEASER));
-		out.setBody(document.getString(MongoFieldNames.LISTING_BODY));
-		out.setStatus(document.getString(MongoFieldNames.LISTING_STATUS));
-		out.setInstallsRecent(document.getLong(MongoFieldNames.RECENT_NSTALLS));
-		out.setInstallsTotal(document.getLong(MongoFieldNames.TOTAL_NSTALLS));
-		out.setLicense(document.getString(MongoFieldNames.LICENSE_TYPE));
-		out.setFavoriteCount(document.getLong(MongoFieldNames.MARKETPLACE_FAVORITES));
-		out.setFoundationMember(document.getBoolean(MongoFieldNames.FOUNDATION_MEMBER_FLAG));
-		out.setCategoryIds(document.getList(MongoFieldNames.CATEGORY_IDS, Integer.class));
+		out.setId(document.getString(DatabaseFieldNames.DOCID));
+		out.setTitle(document.getString(DatabaseFieldNames.LISTING_TITLE));
+		out.setUrl(document.getString(DatabaseFieldNames.URL));
+		out.setSupportUrl(document.getString(DatabaseFieldNames.SUPPORT_PAGE_URL));
+		out.setHomepageUrl(document.getString(DatabaseFieldNames.HOME_PAGE_URL));
+		out.setTeaser(document.getString(DatabaseFieldNames.LISTING_TEASER));
+		out.setBody(document.getString(DatabaseFieldNames.LISTING_BODY));
+		out.setStatus(document.getString(DatabaseFieldNames.LISTING_STATUS));
+		out.setInstallsRecent(document.getLong(DatabaseFieldNames.RECENT_NSTALLS));
+		out.setInstallsTotal(document.getLong(DatabaseFieldNames.TOTAL_NSTALLS));
+		out.setLicense(document.getString(DatabaseFieldNames.LICENSE_TYPE));
+		out.setFavoriteCount(document.getLong(DatabaseFieldNames.MARKETPLACE_FAVORITES));
+		out.setFoundationMember(document.getBoolean(DatabaseFieldNames.FOUNDATION_MEMBER_FLAG));
+		out.setCategoryIds(document.getList(DatabaseFieldNames.CATEGORY_IDS, String.class));
 
 		// for nested document types, use the converters to safely transform into POJO
-		out.setAuthors(document.getList(MongoFieldNames.LISTING_AUTHORS, Document.class).stream()
+		out.setAuthors(document.getList(DatabaseFieldNames.LISTING_AUTHORS, Document.class).stream()
 				.map(authorConverter::convert).collect(Collectors.toList()));
-		out.setOrganizations(document.getList(MongoFieldNames.LISTING_ORGANIZATIONS, Document.class).stream()
+		out.setOrganizations(document.getList(DatabaseFieldNames.LISTING_ORGANIZATIONS, Document.class).stream()
 				.map(organizationConverter::convert).collect(Collectors.toList()));
-		out.setTags(document.getList(MongoFieldNames.LISTING_TAGS, Document.class).stream().map(tagConverter::convert)
+		out.setTags(document.getList(DatabaseFieldNames.LISTING_TAGS, Document.class).stream().map(tagConverter::convert)
 				.collect(Collectors.toList()));
-		out.setVersions(document.getList(MongoFieldNames.LISTING_VERSIONS, Document.class).stream()
+		out.setVersions(document.getList(DatabaseFieldNames.LISTING_VERSIONS, Document.class).stream()
 				.map(versionConverter::convert).collect(Collectors.toList()));
-		out.setCategories(document.getList(MongoFieldNames.LISTING_CATEGORIES, Document.class).stream()
+		out.setCategories(document.getList(DatabaseFieldNames.LISTING_CATEGORIES, Document.class).stream()
 				.map(categoryConverter::convert).collect(Collectors.toList()));
 		
 		// convert date to epoch milli
-		out.setCreationDate(document.getDate(MongoFieldNames.CREATION_DATE).toInstant().toEpochMilli());
-		out.setUpdateDate(document.getDate(MongoFieldNames.UPDATE_DATE).toInstant().toEpochMilli());
+		out.setCreationDate(document.getDate(DatabaseFieldNames.CREATION_DATE).toInstant().toEpochMilli());
+		out.setUpdateDate(document.getDate(DatabaseFieldNames.UPDATE_DATE).toInstant().toEpochMilli());
 
 		return out;
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java
new file mode 100644
index 0000000000000000000000000000000000000000..52db3a960da6c0874543e3aa5f3cff4d32405086
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java
@@ -0,0 +1,90 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.dto.codecs;
+
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bson.BsonReader;
+import org.bson.BsonString;
+import org.bson.BsonValue;
+import org.bson.BsonWriter;
+import org.bson.Document;
+import org.bson.codecs.Codec;
+import org.bson.codecs.CollectibleCodec;
+import org.bson.codecs.DecoderContext;
+import org.bson.codecs.EncoderContext;
+import org.eclipsefoundation.marketplace.dto.Market;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
+
+import com.mongodb.MongoClient;
+
+/**
+ * MongoDB codec for transcoding of {@link Market} and {@link Document}
+ * objects. Used when writing or retrieving objects of given type from the
+ * database.
+ * 
+ * @author Martin Lowe
+ */
+public class MarketCodec implements CollectibleCodec<Market> {
+	private final Codec<Document> documentCodec;
+
+	/**
+	 * Creates the codec and initializes the codecs and converters needed to create
+	 * a listing from end to end.
+	 */
+	public MarketCodec() {
+		this.documentCodec = MongoClient.getDefaultCodecRegistry().get(Document.class);
+	}
+	
+	@Override
+	public void encode(BsonWriter writer, Market value, EncoderContext encoderContext) {
+		Document doc = new Document();
+
+		doc.put(DatabaseFieldNames.DOCID, value.getId());
+		doc.put(DatabaseFieldNames.URL, value.getUrl());
+		doc.put(DatabaseFieldNames.MARKET_NAME, value.getName());
+		doc.put(DatabaseFieldNames.CATEGORY_IDS, value.getCategoryIds());
+
+		documentCodec.encode(writer, doc, encoderContext);
+	}
+
+	@Override
+	public Class<Market> getEncoderClass() {
+		return Market.class;
+	}
+
+	@Override
+	public Market decode(BsonReader reader, DecoderContext decoderContext) {
+		Document document = documentCodec.decode(reader, decoderContext);
+		Market out = new Market();
+		out.setId(document.getString(DatabaseFieldNames.DOCID));
+		out.setUrl(document.getString(DatabaseFieldNames.URL));
+		out.setName(document.getString(DatabaseFieldNames.MARKET_NAME));
+		
+		return out;
+	}
+
+	@Override
+	public Market generateIdIfAbsentFromDocument(Market document) {
+		if (!documentHasId(document)) {
+			document.setId(UUID.randomUUID().toString());
+		}
+		return document;
+	}
+
+	@Override
+	public boolean documentHasId(Market document) {
+		return StringUtils.isNotBlank(document.getId());
+	}
+
+	@Override
+	public BsonValue getDocumentId(Market document) {
+		return new BsonString(document.getId());
+	}
+
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java
index 74b55bfc9a001479a094c861da1e255b569b6831..c307fcc9d4232887ee20e6e22b4d615e75e27c8e 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java
@@ -8,7 +8,7 @@ package org.eclipsefoundation.marketplace.dto.converters;
 
 import org.bson.Document;
 import org.eclipsefoundation.marketplace.dto.Category;
-import org.eclipsefoundation.marketplace.namespace.MongoFieldNames;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
 /**
  * @author martin
@@ -19,20 +19,18 @@ public class CategoryConverter implements Converter<Category> {
 	@Override
 	public Category convert(Document src) {
 		Category out = new Category();
-		out.setId(src.getInteger(MongoFieldNames.DOCID));
-		out.setName(src.getString(MongoFieldNames.CATEGORY_NAME));
-		out.setUrl(src.getString(MongoFieldNames.CATEGORY_URL));
-		out.setMarketIds(src.getList(MongoFieldNames.MARKET_IDS, Integer.class));
+		out.setId(src.getString(DatabaseFieldNames.DOCID));
+		out.setName(src.getString(DatabaseFieldNames.CATEGORY_NAME));
+		out.setUrl(src.getString(DatabaseFieldNames.CATEGORY_URL));
 		return out;
 	}
 
 	@Override
 	public Document convert(Category src) {
 		Document doc = new Document();
-		doc.put(MongoFieldNames.DOCID, src.getId());
-		doc.put(MongoFieldNames.CATEGORY_NAME, src.getName());
-		doc.put(MongoFieldNames.CATEGORY_URL, src.getUrl());
-		doc.put(MongoFieldNames.MARKET_IDS, src.getMarketIds());
+		doc.put(DatabaseFieldNames.DOCID, src.getId());
+		doc.put(DatabaseFieldNames.CATEGORY_NAME, src.getName());
+		doc.put(DatabaseFieldNames.CATEGORY_URL, src.getUrl());
 		return doc;
 	}
 
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java
index 86e46632232873ab2647791a0ef828b3783b2366..cb4df1a65ba217dce1d2955acb4536024d19d70b 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java
@@ -30,10 +30,7 @@ public class CatalogFilter implements DtoFilter<Catalog> {
 
 	@Override
 	public List<Bson> getFilters(RequestWrapper qps) {
-		List<Bson> filters = new ArrayList<>();
-
-		
-		return filters;
+		return Collections.emptyList();
 	}
 	
 	@Override
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java
index b3bc7305d205e4896e7d4e622ffd3787e78369bd..91ab362a268f5d2d765780b83891be6781b43577 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java
@@ -15,8 +15,8 @@ import javax.enterprise.context.ApplicationScoped;
 import org.bson.conversions.Bson;
 import org.eclipsefoundation.marketplace.dto.Listing;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 import org.eclipsefoundation.marketplace.namespace.DtoTableNames;
-import org.eclipsefoundation.marketplace.namespace.MongoFieldNames;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 
 import com.mongodb.client.model.Aggregates;
@@ -35,49 +35,53 @@ import com.mongodb.client.model.Filters;
  * <li>tags
  * </ul>
  * 
+ * <p>
+ * Injects categories into the results by way of aggregate pipeline.
+ * </p>
+ * 
  * @author Martin Lowe
  */
 @ApplicationScoped
 public class ListingFilter implements DtoFilter<Listing> {
 
 	@Override
-	public List<Bson> getFilters(RequestWrapper qps) {
+	public List<Bson> getFilters(RequestWrapper wrap) {
 		List<Bson> filters = new ArrayList<>();
 
 		// Listing ID check
-		Optional<String> id = qps.getFirstParam(MongoFieldNames.LISTING_ID);
+		Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
 		if (id.isPresent()) {
-			filters.add(Filters.eq(MongoFieldNames.LISTING_ID, Long.valueOf(id.get())));
+			filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
 		}
 
 		// select by multiple IDs
-		List<String> ids = qps.getParams(UrlParameterNames.IDS);
+		List<String> ids = wrap.getParams(UrlParameterNames.IDS);
 		if (!ids.isEmpty()) {
-			filters.add(Filters.in(MongoFieldNames.LISTING_ID, ids));
+			filters.add(Filters.in(DatabaseFieldNames.DOCID, ids));
 		}
 
 		// Listing license type check
-		Optional<String> licType = qps.getFirstParam(MongoFieldNames.LICENSE_TYPE);
+		Optional<String> licType = wrap.getFirstParam(DatabaseFieldNames.LICENSE_TYPE);
 		if (licType.isPresent()) {
-			filters.add(Filters.eq(MongoFieldNames.LICENSE_TYPE, licType.get()));
+			filters.add(Filters.eq(DatabaseFieldNames.LICENSE_TYPE, licType.get()));
 		}
 
 		// handle version sub document selection
 		List<Bson> versionFilters = new ArrayList<>();
 		// solution version - OS filter
-		Optional<String> os = qps.getFirstParam(UrlParameterNames.OS);
+		Optional<String> os = wrap.getFirstParam(UrlParameterNames.OS);
 		if (os.isPresent()) {
 			versionFilters.add(Filters.eq("platforms", os.get()));
 		}
 		// solution version - eclipse version
-		Optional<String> eclipseVersion = qps.getFirstParam(UrlParameterNames.ECLIPSE_VERSION);
+		Optional<String> eclipseVersion = wrap.getFirstParam(UrlParameterNames.ECLIPSE_VERSION);
 		if (eclipseVersion.isPresent()) {
 			versionFilters.add(Filters.eq("compatible_versions", eclipseVersion.get()));
 		}
 		// TODO this sorts by naturally by character rather than by actual number (e.g.
 		// 1.9 is technically greater than 1.10)
 		// solution version - Java version
-		Optional<String> javaVersion = qps.getFirstParam(UrlParameterNames.JAVA_VERSION);
+		Optional<String> javaVersion = wrap.getFirstParam(UrlParameterNames.JAVA_VERSION);
 		if (javaVersion.isPresent()) {
 			versionFilters.add(Filters.gte("min_java_version", javaVersion.get()));
 		}
@@ -86,13 +90,13 @@ public class ListingFilter implements DtoFilter<Listing> {
 		}
 
 		// select by multiple tags
-		List<String> tags = qps.getParams(UrlParameterNames.TAGS);
+		List<String> tags = wrap.getParams(UrlParameterNames.TAGS);
 		if (!tags.isEmpty()) {
-			filters.add(Filters.in(MongoFieldNames.LISTING_TAGS + ".title", tags));
+			filters.add(Filters.in(DatabaseFieldNames.LISTING_TAGS + ".title", tags));
 		}
 
 		// text search
-		Optional<String> text = qps.getFirstParam(UrlParameterNames.QUERY_STRING);
+		Optional<String> text = wrap.getFirstParam(UrlParameterNames.QUERY_STRING);
 		if (text.isPresent()) {
 			filters.add(Filters.text(text.get()));
 		}
@@ -103,16 +107,9 @@ public class ListingFilter implements DtoFilter<Listing> {
 	public List<Bson> getAggregates(RequestWrapper wrap) {
 		List<Bson> aggs = new ArrayList<>();
 		// adds a $lookup aggregate, joining categories on categoryIDS as "categories"
-		aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), MongoFieldNames.CATEGORY_IDS, "id",
-				"categories"));
-		List<String> marketIdsRaw = wrap.getParams(UrlParameterNames.MARKET_IDS);
-		List<Integer> marketIds = new ArrayList<>(marketIdsRaw.size());
-		try {
-			marketIdsRaw.forEach(s -> marketIds.add(Integer.valueOf(s)));
-		} catch (NumberFormatException e) {
-			// suppress
-		}
-		
+		aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), DatabaseFieldNames.CATEGORY_IDS, DatabaseFieldNames.DOCID,
+				DatabaseFieldNames.LISTING_CATEGORIES));
+		List<String> marketIds = wrap.getParams(UrlParameterNames.MARKET_IDS);
 		if (!marketIds.isEmpty()) {
 			aggs.add(Aggregates.match(Filters.in("categories.market_ids", marketIds)));
 		}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..7472d7549fe8c474547a536dfc236c4450b5195c
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
@@ -0,0 +1,51 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.dto.filter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.bson.conversions.Bson;
+import org.eclipsefoundation.marketplace.dto.Market;
+import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
+import org.eclipsefoundation.marketplace.namespace.DtoTableNames;
+
+import com.mongodb.client.model.Aggregates;
+
+/**
+ * Filter implementation for the {@linkplain Market} class.
+ * 
+ * @author Martin Lowe
+ */
+@ApplicationScoped
+public class MarketFilter implements DtoFilter<Market> {
+
+	@Override
+	public List<Bson> getFilters(RequestWrapper wrap) {
+		return Collections.emptyList();
+	}
+
+	@Override
+	public List<Bson> getAggregates(RequestWrapper wrap) {
+		List<Bson> aggs = new ArrayList<>();
+		// adds a $lookup aggregate, joining categories on categoryIDS as "categories"
+		aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), DatabaseFieldNames.CATEGORY_IDS, DatabaseFieldNames.DOCID,
+				"categories"));
+		
+		return aggs;
+	}
+
+	@Override
+	public Class<Market> getType() {
+		return Market.class;
+	}
+
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/providers/MarketCodecProvider.java b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/MarketCodecProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..a849c57bdb19078d68937de9f0cabc1fe395d972
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/MarketCodecProvider.java
@@ -0,0 +1,37 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.dto.providers;
+
+import org.bson.codecs.Codec;
+import org.bson.codecs.configuration.CodecProvider;
+import org.bson.codecs.configuration.CodecRegistry;
+import org.eclipsefoundation.marketplace.dto.Category;
+import org.eclipsefoundation.marketplace.dto.Market;
+import org.eclipsefoundation.marketplace.dto.codecs.CategoryCodec;
+import org.eclipsefoundation.marketplace.dto.codecs.MarketCodec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides the {@link CategoryCodec} to MongoDB for conversions of
+ * {@link Category} objects.
+ * 
+ * @author Martin Lowe
+ */
+public class MarketCodecProvider implements CodecProvider {
+	private static final Logger LOGGER = LoggerFactory.getLogger(MarketCodecProvider.class);
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
+		if (clazz == Market.class) {
+			LOGGER.debug("Registering custom Category class MongoDB codec");
+			return (Codec<T>) new MarketCodec();
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java b/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java
index 742d111d9f5e7a36a55ce06914057400e1d47921..f1f3aeeec58129ef362849d95304e093ec41e586 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java
@@ -83,7 +83,6 @@ public class MongoQuery<T> {
 				setSort(sortVal.substring(0, idx), sortVal.substring(idx + 1), filters);
 			}
 		}
-		LOGGER.error("{}", filters);
 		if (!filters.isEmpty()) {
 			this.filter = Filters.and(filters);
 		}
@@ -142,11 +141,8 @@ public class MongoQuery<T> {
 		
 		List<Sortable<?>> fields = SortableHelper.getSortableFields(getDocType());
 		Optional<Sortable<?>> fieldContainer = SortableHelper.getSortableFieldByName(fields, sortField);
-
-		LOGGER.error("{}:{}", sortField, sortOrder);
 		if (fieldContainer.isPresent()) {
 			this.order = SortOrder.getOrderByName(sortOrder);
-			LOGGER.error("{}", order);
 			// add sorting query if the sortOrder matches a defined order
 			switch (order) {
 			case RANDOM:
diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java b/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java
index 37ded5c837af1acce27acfc5917bf7127b223f1a..341ca9744eeeca2edc4073bf7956fdd10a9061f3 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java
@@ -40,6 +40,7 @@ public class RequestWrapper {
 	
 	private UriInfo uriInfo;
 	private HttpServletRequest request;
+	private UserAgent userAgent;
 
 	/**
 	 * Generates a wrapper around the 
@@ -48,6 +49,7 @@ public class RequestWrapper {
 	RequestWrapper() {
 		this.uriInfo = ResteasyContext.getContextData(UriInfo.class);
 		this.request = ResteasyContext.getContextData(HttpServletRequest.class);
+		this.userAgent = null;
 	}
 	
 	/**
@@ -136,4 +138,15 @@ public class RequestWrapper {
 	public Object getAttribute(String key) {
 		return request.getAttribute(key);
 	}
+
+	public String getHeader(String key) {
+		return request.getHeader(key);
+	}
+	
+	public UserAgent getUserAgent() {
+		if (userAgent == null) {
+			this.userAgent = new UserAgent(getHeader("user-agent"));
+		}
+		return this.userAgent;
+	}
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/UserAgent.java b/src/main/java/org/eclipsefoundation/marketplace/model/UserAgent.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc1782286d750660ab91565281a6927811796e09
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/model/UserAgent.java
@@ -0,0 +1,178 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.model;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.base.Splitter;
+
+/**
+ * Custom User-Agent capture that handles and stores information based on MPC
+ * information.
+ * 
+ * @author Martin Lowe
+ */
+public class UserAgent {
+
+	/**
+	 * Matches a generic form of User-Agent strings with the following sections
+	 * 
+	 * <code>Agent-Name/Agent-Version (System-Properties;) Platform (Platform-Details) Enhancements</code>
+	 * 
+	 * As User-Agents aren't a standardized format, there is leniency on there being
+	 * trailing sections being missing. The only required field is the Agent name
+	 * and version, separated by a slash.
+	 */
+	private static final Pattern USER_AGENT_PATTERN = Pattern
+			.compile("^(\\S+\\/\\S+)\\s?(?:\\(([^\\)]*?)\\)([^\\(]+(?:\\(([^\\)]*?)\\)([^\\(]+)?)?)?)?$");
+	private static final String MPC_CLIENT_AGENT_NAME = "mpc";
+
+	private final String name;
+	private final String version;
+	private final String systemProperties;
+	private final String platform;
+	private final String platformDetails;
+	private final String enhancements;
+
+	private String javaVersion;
+	private String javaVendor;
+
+	private String os;
+	private String osVersion;
+	private String locale;
+
+	public UserAgent(String userAgent) {
+		Objects.requireNonNull(userAgent);
+
+		// check that the user agent matches the expected standard pattern
+		Matcher m = USER_AGENT_PATTERN.matcher(userAgent);
+		if (!m.matches()) {
+			throw new IllegalArgumentException("Passed string does not match an expected user-agent");
+		}
+
+		// get the name and version of the user agent
+		String agentDeclaration = m.group(0);
+		Iterator<String> it = Splitter.on('/').trimResults().split(agentDeclaration).iterator();
+		this.name = it.next();
+		this.version = it.next();
+
+		this.systemProperties = m.group(1);
+		this.platform = m.group(2);
+		this.platformDetails = m.group(3);
+		this.enhancements = m.group(4);
+		if (MPC_CLIENT_AGENT_NAME.equalsIgnoreCase(name)) {
+			handleMpc();
+		} else if (name.contains("bot")) {
+			// drop as we don't care about bot properties
+		} else {
+			handleWeb();
+		}
+	}
+
+	private void handleMpc() {
+		List<String> systemPropList = Splitter.on(';').splitToList(systemProperties);
+		if (systemPropList.size() != 3) {
+			// TODO throw exception?
+		}
+		// expected form example: Java <java version> <vendor>
+		String javaPropStr = systemPropList.get(0);
+		String javaPlatform = javaPropStr.substring(javaPropStr.indexOf(' ') + 1);
+		List<String> javaProps = Splitter.on(' ').limit(2).splitToList(javaPlatform);
+		this.javaVersion = javaProps.get(0);
+		this.javaVendor = javaProps.get(1);
+		
+		// expected form: <OS name> <OS version> <??>
+		String systemPlatform = systemPropList.get(1);
+		this.locale = systemPropList.get(2);
+	}
+
+	private void handleWeb() {
+
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the version
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+	/**
+	 * @return the version
+	 */
+	public String getSystemProperties() {
+		return systemProperties;
+	}
+
+	/**
+	 * @return the platform
+	 */
+	public String getPlatform() {
+		return platform;
+	}
+
+	/**
+	 * @return the platformDetails
+	 */
+	public String getPlatformDetails() {
+		return platformDetails;
+	}
+
+	/**
+	 * @return the enhancements
+	 */
+	public String getEnhancements() {
+		return enhancements;
+	}
+
+	/**
+	 * @return the javaVersion
+	 */
+	public String getJavaVersion() {
+		return javaVersion;
+	}
+
+	/**
+	 * @return the javaVendor
+	 */
+	public String getJavaVendor() {
+		return javaVendor;
+	}
+
+	/**
+	 * @return the os
+	 */
+	public String getOs() {
+		return os;
+	}
+
+	/**
+	 * @return the osVersion
+	 */
+	public String getOsVersion() {
+		return osVersion;
+	}
+
+	/**
+	 * @return the locale
+	 */
+	public String getLocale() {
+		return locale;
+	}
+
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/MongoFieldNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java
similarity index 90%
rename from src/main/java/org/eclipsefoundation/marketplace/namespace/MongoFieldNames.java
rename to src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java
index 408e075285170529d05076c7f2d907a4189f8c2d..2fd71bc80187cd910ec4ef57ef2f24223f2d5b84 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/namespace/MongoFieldNames.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java
@@ -15,12 +15,14 @@ package org.eclipsefoundation.marketplace.namespace;
  * @author Martin Lowe
  *
  */
-public final class MongoFieldNames {
+public final class DatabaseFieldNames {
 
-	public static final String DOCID = "id";
-	public static final String LISTING_ID = "listing_id";
+	// base fields
+	public static final String DOCID = "_id";
+	public static final String URL = "url";
+	
+	// listing fields
 	public static final String LISTING_TITLE = "title";
-	public static final String LISTING_URL = "url";
 	public static final String LISTING_TEASER = "teaser";
 	public static final String LISTING_BODY = "body";
 	public static final String LISTING_AUTHORS = "authors";
@@ -57,6 +59,8 @@ public final class MongoFieldNames {
 	public static final String CATEGORY_NAME = "name";
 	public static final String CATEGORY_URL = "url";
 	
-	private MongoFieldNames() {
+	public static final String MARKET_NAME = "name";
+	
+	private DatabaseFieldNames() {
 	}
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java
index 4c40791c330a03ca4e8238f867bf37ca341591da..7b90e763e1a22d5351a1211af39fbfd2e77b024a 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java
@@ -9,6 +9,7 @@ package org.eclipsefoundation.marketplace.namespace;
 import org.eclipsefoundation.marketplace.dto.Catalog;
 import org.eclipsefoundation.marketplace.dto.Category;
 import org.eclipsefoundation.marketplace.dto.Listing;
+import org.eclipsefoundation.marketplace.dto.Market;
 
 /**
  * Mapping of DTO classes to their respective tables in the DB.
@@ -19,7 +20,7 @@ import org.eclipsefoundation.marketplace.dto.Listing;
 public enum DtoTableNames {
 	LISTING(Listing.class, "listings"),
 	CATEGORY(Category.class, "categories"),
-	CATALOG(Catalog.class, "catalogs");
+	CATALOG(Catalog.class, "catalogs"), MARKET(Market.class, "markets");
 
 	private Class<?> baseClass;
 	private String tableName;
diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java
index 6fc96cd1c2d0661b0839d9745b714b4af37263d7..feb92164115d415fe4fab60cd4e362e501385a18 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java
@@ -21,6 +21,7 @@ public final class UrlParameterNames {
 	public static final String IDS = "ids";
 	public static final String TAGS = "tags";
 	public static final String MARKET_IDS = "market_ids";
+	public static final String ID = "id";
 
 	private UrlParameterNames() {
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java
index c2368b1a557e011479272e7dd46ad1442a2b00cf..945c32e66737b56359bc69c3e08d5500b6a1d462 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java
@@ -31,7 +31,7 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
 import org.eclipsefoundation.marketplace.model.ResourceDataType;
-import org.eclipsefoundation.marketplace.namespace.MongoFieldNames;
+import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
 import org.slf4j.Logger;
@@ -89,7 +89,7 @@ public class ListingResource {
 	 */
 	@POST
 	public Response postListing(Listing listing) {
-		MongoQuery<Listing> q = new MongoQuery<>(params, dtoFilter,cachingService);
+		MongoQuery<Listing> q = new MongoQuery<>(params, dtoFilter, cachingService);
 
 		// add the object, and await the result
 		StreamHelper.awaitCompletionStage(dao.add(q, Arrays.asList(listing)));
@@ -102,17 +102,17 @@ public class ListingResource {
 	 * Endpoint for /listing/\<listingId\> to retrieve a specific listing from the
 	 * database.
 	 * 
-	 * @param listingId int version of the listing ID
+	 * @param listingId the listing ID
 	 * @return response for the browser
 	 */
 	@GET
 	@Path("/{listingId}")
-	public Response select(@PathParam("listingId") int listingId) {
-		params.addParam(MongoFieldNames.LISTING_ID, Integer.toString(listingId));
+	public Response select(@PathParam("listingId") String listingId) {
+		params.addParam(UrlParameterNames.ID, listingId);
 
 		MongoQuery<Listing> q = new MongoQuery<>(params, dtoFilter, cachingService);
 		// retrieve a cached version of the value for the current listing
-		Optional<List<Listing>> cachedResults = cachingService.get(Integer.toString(listingId), params,
+		Optional<List<Listing>> cachedResults = cachingService.get(listingId, params,
 				() -> StreamHelper.awaitCompletionStage(dao.get(q)));
 		if (!cachedResults.isPresent()) {
 			LOGGER.error("Error while retrieving cached listing for ID {}", listingId);
@@ -132,7 +132,7 @@ public class ListingResource {
 	 */
 	@GET
 	@Path("/{listingId}/installs")
-	public Response selectInstallMetrics(@PathParam("listingId") int listingId) {
+	public Response selectInstallMetrics(@PathParam("listingId") String listingId) {
 		throw new UnsupportedOperationException("Getting install statistics is not yet supported");
 	}
 
@@ -147,7 +147,7 @@ public class ListingResource {
 	 */
 	@GET
 	@Path("/{listingId}/versions/{version}/installs")
-	public Response selectInstallMetrics(@PathParam("listingId") int listingId, @PathParam("version") int version) {
+	public Response selectInstallMetrics(@PathParam("listingId") String listingId, @PathParam("version") int version) {
 		throw new UnsupportedOperationException("Getting install statistics is not yet supported");
 	}
 
@@ -161,7 +161,7 @@ public class ListingResource {
 	 */
 	@POST
 	@Path("/{listingId}/versions/{version}/installs")
-	public Response postInstallMetrics(@PathParam("listingId") int listingId, @PathParam("version") int version,
+	public Response postInstallMetrics(@PathParam("listingId") String listingId, @PathParam("version") String version,
 			Install installDetails) {
 		return Response.ok(installDetails).build();
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..d91451faeea5c8bd839644bbbaa3f79c9cbc4a07
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java
@@ -0,0 +1,113 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.resource;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.eclipsefoundation.marketplace.dao.MongoDao;
+import org.eclipsefoundation.marketplace.dto.Market;
+import org.eclipsefoundation.marketplace.dto.filter.DtoFilter;
+import org.eclipsefoundation.marketplace.helper.StreamHelper;
+import org.eclipsefoundation.marketplace.model.MongoQuery;
+import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.model.ResourceDataType;
+import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
+import org.eclipsefoundation.marketplace.service.CachingService;
+import org.jboss.resteasy.annotations.jaxrs.PathParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author martin
+ *
+ */
+@ResourceDataType(Market.class)
+@Path("/markets")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestScoped
+public class MarketResource {
+	private static final Logger LOGGER = LoggerFactory.getLogger(MarketResource.class);
+
+	@Inject
+	MongoDao dao;
+	@Inject
+	CachingService<List<Market>> cachingService;
+	@Inject
+	RequestWrapper params;
+	@Inject
+	DtoFilter<Market> dtoFilter;
+
+	@GET
+	public Response select() {
+		MongoQuery<Market> q = new MongoQuery<>(params, dtoFilter, cachingService);
+		// retrieve the possible cached object
+		Optional<List<Market>> cachedResults = cachingService.get("all", params,
+				() -> StreamHelper.awaitCompletionStage(dao.get(q)));
+		if (!cachedResults.isPresent()) {
+			LOGGER.error("Error while retrieving cached Categorys");
+			return Response.serverError().build();
+		}
+
+		// return the results as a response
+		return Response.ok(cachedResults.get()).build();
+	}
+
+	/**
+	 * Endpoint for /markets/ to post a new Market to the persistence layer.
+	 * 
+	 * @param market the Category object to insert into the database.
+	 * @return response for the browser
+	 */
+	@POST
+	public Response postMarket(Market market) {
+		MongoQuery<Market> q = new MongoQuery<>(params, dtoFilter, cachingService);
+
+		// add the object, and await the result
+		StreamHelper.awaitCompletionStage(dao.add(q, Arrays.asList(market)));
+
+		// return the results as a response
+		return Response.ok().build();
+	}
+
+	/**
+	 * Endpoint for /markets/\<marketId\> to retrieve a specific Market from the
+	 * database.
+	 * 
+	 * @param marketId int version of the listing ID
+	 * @return response for the browser
+	 */
+	@GET
+	@Path("/{marketId}")
+	public Response select(@PathParam("marketId") String marketId) {
+		params.addParam(UrlParameterNames.ID, marketId);
+
+		MongoQuery<Market> q = new MongoQuery<>(params, dtoFilter, cachingService);
+		// retrieve a cached version of the value for the current listing
+		Optional<List<Market>> cachedResults = cachingService.get(marketId, params,
+				() -> StreamHelper.awaitCompletionStage(dao.get(q)));
+		if (!cachedResults.isPresent()) {
+			LOGGER.error("Error while retrieving cached listing for ID {}", marketId);
+			return Response.serverError().build();
+		}
+
+		// return the results as a response
+		return Response.ok(cachedResults.get()).build();
+	}
+}
diff --git a/src/main/node/hammer.js b/src/main/node/hammer.js
new file mode 100644
index 0000000000000000000000000000000000000000..9778db00d5b4903106e137a7ee73b417bdf30ced
--- /dev/null
+++ b/src/main/node/hammer.js
@@ -0,0 +1,51 @@
+const argv = require('yargs')
+  .option('c', {
+    description: 'Number of calls to queue',
+    alias: 'count',
+    default: 100,
+    nargs: 1
+  })
+  .option('s', {
+    description: 'Server address (e.g. http://localhost:8090)',
+    alias: 'server',
+    nargs: 1,
+    demandOption: true
+  }).argv;
+const axios = require('axios');
+
+var start = new Date();
+var arr = [];
+var dupes = 0;
+var promises = [];
+for (var i=0;i<argv.c;i++) {
+  promises.push(axios.get(`${argv.s}/listings?c=${Math.random()}`)
+    .then(result => report(result))
+    .catch(err => console.log(err)));
+}
+
+
+
+toodles();
+async function toodles() {
+  try {
+    await Promise.all(promises)
+  } catch (error) {
+    this.errormsg = error.message;
+  } finally {
+    this.loading = false
+    console.log(`Unique entires: ${arr.length}: duplicates: ${dupes}`)
+    var end = new Date();
+    console.log(`Start: ${start}, end: ${end}`)
+  }
+}
+function report(result) {
+  var d = result.data;
+  for (l in d) {
+    var id = d[l].id;
+    if (arr.indexOf(id) == -1) {
+      arr.push(id);
+    } else {
+      dupes++;
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/main/node/index.js b/src/main/node/index.js
index 4f7af38dea0e7e71eafa1c93154fc74d4dc5ebd6..fd12f21a14421a895dbe419d888c2bc48193c447 100644
--- a/src/main/node/index.js
+++ b/src/main/node/index.js
@@ -1,5 +1,6 @@
 const axios = require('axios');
 const randomWords = require('random-words');
+const uuid = require('uuid');
 const argv = require('yargs')
   .option('c', {
     description: 'Number of listings to generate',
@@ -19,11 +20,18 @@ const lic_types = ["EPL-2.0","EPL-1.0","GPL"];
 const platforms = ["windows","macos","linux"];
 const eclipseVs = ["4.6","4.7","4.8","4.9","4.10","4.11","4.12"];
 const javaVs = ["1.5", "1.6", "1.7", "1.8", "1.9", "1.10"];
-const categoryIds = [...Array(20).keys()];
-const marketIds = [...Array(5).keys()];
+const categoryIds = [];
+for (var i=0;i<20;i++) {
+  categoryIds.push(uuid.v4());
+}
+const marketIds = [];
+for (var i=0;i<5;i++) {
+  marketIds.push(uuid.v4());
+}
 
 createListing(0);
 createCategory(0);
+createMarket(0);
 
 function shuff(arr) {
   var out = Array.from(arr);
@@ -51,22 +59,32 @@ function createListing(count) {
   if (count >= max) {
     return;
   }
-
-  axios.post(argv.s+"/listings/", generateJSON(count++))
+  count++;
+  axios.post(argv.s+"/listings/", generateJSON(uuid.v4()))
     .then(createListing(count))
     .catch(err => console.log(err));
 }
 
 function createCategory(count) {
-  if (count >= 20) {
+  if (count >= categoryIds.length) {
     return;
   }
 
-  axios.post(argv.s+"/categories/", generateCategoryJSON(count++))
+  axios.post(argv.s+"/categories/", generateCategoryJSON(categoryIds[count++]))
     .then(createCategory(count))
     .catch(err => console.log(err));
 }
 
+function createMarket(count) {
+  if (count >= marketIds.length) {
+    return;
+  }
+
+  axios.post(argv.s+"/markets/", generateMarketJSON(marketIds[count++]))
+    .then(createMarket(count))
+    .catch(err => console.log(err));
+}
+
 function generateJSON(id) {
   var solutions = [];
   var solsCount = Math.floor(Math.random()*5);
@@ -80,7 +98,6 @@ function generateJSON(id) {
   }
   
   return {
-    "listing_id": id,
   	"title": "Sample",
   	"url": "https://jakarta.ee",
   	"foundation_ember": false,
@@ -113,15 +130,23 @@ function generateJSON(id) {
   		}
   	],
   	"versions": solutions,
-  	"category_ids": splice(categoryIds)
+  	"category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1)
   };
 }
 
 function generateCategoryJSON(id) {
+  return {
+    "id": id,
+    "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
+    "url": "https://www.eclipse.org"
+  };
+}
+
+function generateMarketJSON(id) {
   return {
     "id": id,
     "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
     "url": "https://www.eclipse.org",
-    "market_ids": [splice(marketIds)[0]]
+    "category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1)
   };
 }
diff --git a/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java b/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java
index fc6a73aca3e985e6ab3ef2b234aca3bf0cdf817e..f6b07e61c51c3070a94cd7941b60c3ba69ce3329 100644
--- a/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java
+++ b/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java
@@ -9,6 +9,7 @@ package org.eclipsefoundation.marketplace.helper;
 import java.util.List;
 import java.util.Optional;
 
+import org.eclipsefoundation.marketplace.helper.SortableHelper;
 import org.eclipsefoundation.marketplace.helper.SortableHelper.Sortable;
 import org.eclipsefoundation.marketplace.model.SortableField;
 import org.junit.jupiter.api.Assertions;
diff --git a/src/test/java/org/eclipsefoundation/marketplace/model/RequestWrapperMock.java b/src/test/java/org/eclipsefoundation/marketplace/model/RequestWrapperMock.java
new file mode 100644
index 0000000000000000000000000000000000000000..7549e7f90e8566b9ff4ce57c37212ead77a631bb
--- /dev/null
+++ b/src/test/java/org/eclipsefoundation/marketplace/model/RequestWrapperMock.java
@@ -0,0 +1,18 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.model;
+
+import org.eclipsefoundation.marketplace.model.RequestWrapper;
+
+/**
+ * Wraps RequestWrapper for access outside of request scope in tests.
+ * 
+ * @author Martin Lowe
+ */
+public class RequestWrapperMock extends RequestWrapper {
+	
+}
diff --git a/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java b/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java
index 0da665bc8c3bb122332fa5b3aa8dcb771b03fbe8..54f802f3edf9f40a2715ede04fbb2dc455a5ebd7 100644
--- a/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java
+++ b/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java
@@ -9,26 +9,30 @@ package org.eclipsefoundation.marketplace.service.impl;
 import java.util.Optional;
 
 import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.UriInfo;
 
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.model.RequestWrapperMock;
+import org.eclipsefoundation.marketplace.service.impl.GuavaCachingService;
+import org.jboss.resteasy.core.ResteasyContext;
+import org.jboss.resteasy.specimpl.ResteasyUriInfo;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import io.quarkus.test.junit.DisabledOnSubstrate;
 import io.quarkus.test.junit.QuarkusTest;
+import io.undertow.servlet.spec.HttpServletRequestImpl;
 
 /**
  * @author martin
  *
  */
-@DisabledOnSubstrate
 @QuarkusTest
 public class GuavaCachingServiceTest {
 
 	@Inject
 	GuavaCachingService<Object> gcs;
-	@Inject
 	RequestWrapper sample;
 
 	/**
@@ -36,6 +40,11 @@ public class GuavaCachingServiceTest {
 	 */
 	@BeforeEach
 	public void pre() {
+		// inject empty objects into the Request context before creating a mock object
+		ResteasyContext.pushContext(UriInfo.class, new ResteasyUriInfo("",""));
+		ResteasyContext.pushContext(HttpServletRequest.class, new HttpServletRequestImpl(null, null));
+		
+		this.sample = new RequestWrapperMock();
 		// expire all active key values
 		gcs.removeAll();
 	}