diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
index e249b487d5d5972749f4979712666315f1cf9e39..2a67b4679221aa8d864d5d152657cd242f47eb6b 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
@@ -26,6 +26,7 @@
 import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MalwareResultBean;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.MalwareResultBean.Status;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
 import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
 import java.text.MessageFormat;
@@ -103,7 +104,6 @@ private static class SharedProcessing {
         private static final long MAX_UPLOAD_SIZE = 1_000_000_000;
         private static final int NUM_FILE_UPLOAD_RETRIES = 60 * 5;
         private static final long FILE_UPLOAD_RETRY_SLEEP_MILLIS = 60 * 1000;
-        
 
         private static final Set<String> EXECUTABLE_MIME_TYPES = Stream.of(
                 "application/x-bat",//NON-NLS
@@ -128,6 +128,7 @@ private static class SharedProcessing {
         private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance();
         private final CTApiDAO ctApiDAO = CTApiDAO.getInstance();
 
+        // TODO minimize state
         private RunState runState = null;
 
         private SleuthkitCase tskCase = null;
@@ -137,6 +138,7 @@ private static class SharedProcessing {
         private long dsId = 0;
         private long ingestJobId = 0;
         private boolean uploadUnknownFiles = false;
+        private Map<String, List<Long>> unidentifiedHashes = null;
 
         @Messages({
             "MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low",
@@ -196,7 +198,8 @@ synchronized void startUp(IngestJobContext context) throws IngestModuleException
                 ingestJobId = context.getJobId();
                 licenseInfo = licenseInfoOpt.get();
                 uploadUnknownFiles = ctSettingsPersistence.loadMalwareIngestSettings().isUploadFiles();
-
+                unidentifiedHashes = new HashMap<>();
+                
                 // set run state to initialized
                 runState = RunState.STARTED_UP;
             } catch (Exception ex) {
@@ -306,66 +309,79 @@ private void handleBatch(List<FileRecord> fileRecords) {
             }
 
             try {
-                // get an auth token with the license
-                AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense());
-
-                // make sure we are in bounds for the remaining scans
-                long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
-                if (remainingScans <= 0) {
-                    runState = RunState.DISABLED;
-                    notifyWarning(
-                            Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
-                            Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
-                            null);
-                    return;
-                }
+                List<CTCloudBean> repResult = getHashLookupResults(md5Hashes);
+                Map<Boolean, List<CTCloudBean>> partitioned = repResult.stream()
+                        .filter(bean -> bean.getMalwareResult() != null)
+                        .collect(Collectors.partitioningBy(bean -> bean.getMalwareResult().getStatus() == Status.FOUND));
+                
+                // TODO handle caching list and creating new items
+                
+                createArtifacts(repResult, md5ToObjId);
+            } catch (Exception ex) {
+                notifyWarning(
+                        Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
+                        Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
+                        ex);
+            }
+        }
 
-                // using auth token, get results
-                List<CTCloudBean> repResult = ctApiDAO.getReputationResults(
-                        new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse),
-                        md5Hashes
-                );
-
-                List<BlackboardArtifact> createdArtifacts = new ArrayList<>();
-                if (!CollectionUtils.isEmpty(repResult)) {
-                    SleuthkitCase.CaseDbTransaction trans = null;
-                    try {
-                        trans = tskCase.beginTransaction();
-                        for (CTCloudBean result : repResult) {
-                            String sanitizedMd5 = sanitizedMd5(result.getMd5HashValue());
-                            List<Long> objIds = md5ToObjId.remove(sanitizedMd5);
-                            if (objIds == null || objIds.isEmpty()) {
-                                continue;
-                            }
+        private void createArtifacts(List<CTCloudBean> repResult, Map<String, List<Long>> md5ToObjId) throws Blackboard.BlackboardException, TskCoreException {
+            List<BlackboardArtifact> createdArtifacts = new ArrayList<>();
+            if (!CollectionUtils.isEmpty(repResult)) {
+                SleuthkitCase.CaseDbTransaction trans = null;
+                try {
+                    trans = tskCase.beginTransaction();
+                    for (CTCloudBean result : repResult) {
+                        String sanitizedMd5 = sanitizedMd5(result.getMd5HashValue());
+                        List<Long> objIds = md5ToObjId.remove(sanitizedMd5);
+                        if (objIds == null || objIds.isEmpty()) {
+                            continue;
+                        }
 
-                            for (Long objId : objIds) {
-                                AnalysisResult res = createAnalysisResult(objId, result, trans);
-                                if (res != null) {
-                                    createdArtifacts.add(res);
-                                }
+                        for (Long objId : objIds) {
+                            AnalysisResult res = createAnalysisResult(objId, result, trans);
+                            if (res != null) {
+                                createdArtifacts.add(res);
                             }
                         }
+                    }
 
-                        trans.commit();
+                    trans.commit();
+                    trans = null;
+                } finally {
+                    if (trans != null) {
+                        trans.rollback();
+                        createdArtifacts.clear();
                         trans = null;
-                    } finally {
-                        if (trans != null) {
-                            trans.rollback();
-                            createdArtifacts.clear();
-                            trans = null;
-                        }
                     }
+                }
 
-                    if (!CollectionUtils.isEmpty(createdArtifacts)) {
-                        tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId);
-                    }
+                if (!CollectionUtils.isEmpty(createdArtifacts)) {
+                    tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId);
                 }
-            } catch (Exception ex) {
+            }
+        }
+
+        private List<CTCloudBean> getHashLookupResults(List<String> md5Hashes) throws CTCloudException {
+            // get an auth token with the license
+            AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense());
+
+            // make sure we are in bounds for the remaining scans
+            long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
+            if (remainingScans <= 0) {
+                runState = RunState.DISABLED;
                 notifyWarning(
-                        Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
-                        Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
-                        ex);
+                        Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
+                        Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
+                        null);
+                return Collections.emptyList();
             }
+
+            // using auth token, get results
+            return ctApiDAO.getReputationResults(
+                    new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse),
+                    md5Hashes
+            );
         }
 
         private String sanitizedMd5(String orig) {
@@ -392,7 +408,7 @@ private boolean uploadFile(CTCloudBean cloudBean, long objId) throws CTCloudExce
                 return false;
             }
 
-            AbstractFile af = skCase.getAbstractFileById(objId);
+            AbstractFile af = tskCase.getAbstractFileById(objId);
             if (af == null) {
                 return false;
             }
@@ -402,7 +418,7 @@ private boolean uploadFile(CTCloudBean cloudBean, long objId) throws CTCloudExce
             }
 
             // get auth token / file upload url
-            AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(decrypted, true);
+            AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense(), true);
             if (StringUtils.isBlank(authTokenResponse.getFileUploadUrl())) {
                 throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR);
             } else if (remaining(authTokenResponse.getFileUploadLimit(), authTokenResponse.getFileUploadCount()) <= 0) {
@@ -425,27 +441,29 @@ private boolean uploadFile(CTCloudBean cloudBean, long objId) throws CTCloudExce
                     .setSha1(af.getSha1Hash())
                     .setSha256(af.getSha256Hash());
 
-            ctApiDAO.uploadMeta(new AuthenticatedRequestData(decrypted, authTokenResponse), metaRequest);
+            ctApiDAO.uploadMeta(new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse), metaRequest);
             return true;
         }
 
-        private boolean getUploadedFileResults(Map<String, List<Long>> md5objIdMapping) {
+        private boolean getUploadedFileResults(Map<String, List<Long>> md5objIdMapping) throws InterruptedException, CTCloudException, Blackboard.BlackboardException, TskCoreException {
+            // TODO integrate this
             Map<String, List<Long>> remaining = new HashMap<>(md5objIdMapping);
 
             for (int retry = 0; retry < NUM_FILE_UPLOAD_RETRIES; retry++) {
                 List<List<String>> md5Batches = Lists.partition(new ArrayList<>(remaining.keySet()), BATCH_SIZE);
                 for (List<String> batch : md5Batches) {
-                    // TODO query and capture still unknown
+                    List<CTCloudBean> repResult = getHashLookupResults(batch);
+                    createArtifacts(repResult, remaining);
                 }
-                
+
                 if (remaining.isEmpty()) {
                     return true;
                 }
-                
-               
+
                 Thread.sleep(FILE_UPLOAD_RETRY_SLEEP_MILLIS);
             }
 
+            return false;
         }
 
         @Messages({