diff --git a/Makefile b/Makefile
index 3dfea6a7a846f6f297572962aa6f6be986a4cf7b..808b8283bc4305e684c420fec727481b97b469fe 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,8 @@ list:
 	@echo "  phpcs            > Run php code sniffer test suite"
 	@echo "  install_services > install services dependencies (virtualenv)"
 	@echo "  shell_services   > Shell into python services"
+	@echo "  install_tasks    > install tasks dependencies (virtualenv)"
+	@echo "  shell_tasks      > Shell into python tasks"
 	@echo "  create-db        > Create a database for dev only (need token_enabled=0)"
 	@echo "  remove-pgdata    > Remove the anis-next database"
 	@echo ""
@@ -91,6 +93,12 @@ install_services:
 shell_services:
 	@docker-compose exec services bash
 
+install_tasks:
+	@docker run --init -it --rm --user $(UID):$(GID) -v $(CURDIR)/tasks:/project -w /project python:3.8 /bin/bash -c "python3 -m venv venv && source /project/venv/bin/activate && pip install -r requirements.txt"
+
+shell_tasks:
+	@docker-compose exec tasks bash
+
 create-db:
 	@docker-compose exec server sh /mnt/init-keycloak.sh
 	@docker-compose exec server sh /mnt/create-db.sh
diff --git a/client/src/app/instance/instance.reducer.ts b/client/src/app/instance/instance.reducer.ts
index 062c160c5fe4f3fa84539d5e9665a2b5c299f5e5..ed942d2b4006a1d7be4b27325465d2089b8d506e 100644
--- a/client/src/app/instance/instance.reducer.ts
+++ b/client/src/app/instance/instance.reducer.ts
@@ -16,6 +16,7 @@ import * as samp from './store/reducers/samp.reducer';
 import * as coneSearch from './store/reducers/cone-search.reducer';
 import * as detail from './store/reducers/detail.reducer';
 import * as svomJsonKw from './store/reducers/svom-json-kw.reducer';
+import * as downloadFile from './store/reducers/download-file.reducer';
 
 /**
  * Interface for instance state.
@@ -28,7 +29,8 @@ export interface State {
     samp: samp.State,
     coneSearch: coneSearch.State
     detail: detail.State,
-    svomJsonKw: svomJsonKw.State
+    svomJsonKw: svomJsonKw.State,
+    downloadFile: downloadFile.State
 }
 
 const reducers = {
@@ -37,7 +39,8 @@ const reducers = {
     samp: samp.sampReducer,
     coneSearch: coneSearch.coneSearchReducer,
     detail: detail.detailReducer,
-    svomJsonKw: svomJsonKw.svomJsonKwReducer
+    svomJsonKw: svomJsonKw.svomJsonKwReducer,
+    downloadFile: downloadFile.fileReducer
 };
 
 export const instanceReducer = combineReducers(reducers);
diff --git a/client/src/app/instance/search/components/result/datatable-actions.component.html b/client/src/app/instance/search/components/result/datatable-actions.component.html
index a1ff010a95fbcfa5304aadb79791f8841c341466..38e119dfcc5fcf3f7a52e8252ea369de276b1cfe 100644
--- a/client/src/app/instance/search/components/result/datatable-actions.component.html
+++ b/client/src/app/instance/search/components/result/datatable-actions.component.html
@@ -4,29 +4,29 @@
     </button>
     <ul id="dropdown-basic" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic">
         <li *ngIf="getConfigDownloadResultFormat('download_csv')" role="menuitem">
-            <a class="dropdown-item" [href]="getUrl('csv')" (click)="click($event, getUrl('csv'), 'csv')">
+            <a class="dropdown-item" (click)="downloadResult('csv')">
                 <span class="fas fa-file-csv"></span> Download CSV
             </a>
         </li>
         <li *ngIf="getConfigDownloadResultFormat('download_ascii')" role="menuitem">
-            <a class="dropdown-item" [href]="getUrl('ascii')" (click)="click($event, getUrl('ascii'), 'txt')">
+            <a class="dropdown-item" (click)="downloadResult('ascii')">
                 <span class="fas fa-file"></span> Download ASCII
             </a>
         </li>
         <li *ngIf="getConfigDownloadResultFormat('download_vo')" role="menuitem">
-            <a class="dropdown-item" [href]="getUrl('votable')" (click)="click($event, getUrl('votable'), 'xml')">
+            <a class="dropdown-item" (click)="downloadResult('votable')">
                 <span class="fas fa-file"></span> VOtable
             </a>
         </li>
-        <li *ngIf="getConfigDownloadResultFormat('download_vo')" role="menuitem">
-            <a class="dropdown-item" (click)="broadcastVotable()">
+        <li *ngIf="getConfigDownloadResultFormat('download_vo')" role="menuitem" [class.disabled]="!sampRegistered">
+            <a class="dropdown-item" [class.disabled]="!sampRegistered" (click)="broadcast.emit()">
                 <span class="fas fa-broadcast-tower"></span> Broadcast VOtable
             </a>
         </li>
         <li *ngIf="getConfigDownloadResultFormat('download_archive')" role="menuitem">
-            <a class="dropdown-item" [href]="getUrlArchive()" (click)="click($event, getUrlArchive(), 'zip')">
+            <a class="dropdown-item" (click)="downloadArchive()">
                 <span class="fas fa-archive"></span> Download files archive
             </a>
         </li>
     </ul>
-</div>
+</div>
\ No newline at end of file
diff --git a/client/src/app/instance/search/components/result/datatable-actions.component.ts b/client/src/app/instance/search/components/result/datatable-actions.component.ts
index 5c29499606f57e2117b0fbec16fc780b7a23d630..73f55fd897f3a3f896422fe82f8efbe1e17a6d06 100644
--- a/client/src/app/instance/search/components/result/datatable-actions.component.ts
+++ b/client/src/app/instance/search/components/result/datatable-actions.component.ts
@@ -1,12 +1,7 @@
 import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { HttpClient } from '@angular/common/http';
-
-import { ToastrService } from 'ngx-toastr';
 
 import { Dataset, Attribute } from 'src/app/metamodel/models';
-import { Criterion, ConeSearch, criterionToString } from 'src/app/instance/store/models';
-import { getHost } from 'src/app/shared/utils';
-import { AppConfigService } from 'src/app/app-config.service';
+import { Criterion, ConeSearch } from 'src/app/instance/store/models';
 
 @Component({
     selector: 'app-datatable-actions',
@@ -23,8 +18,8 @@ export class DatatableActionsComponent {
     @Input() dataLength: number;
     @Input() sampRegistered: boolean;
     @Output() broadcast: EventEmitter<string> = new EventEmitter();
-
-    constructor(private appConfig: AppConfigService, private http: HttpClient, private toastr: ToastrService) { }
+    @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean }> = new EventEmitter();
+    @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter();
 
     /**
      * Checks if the download format is allowed by Anis Admin configuration.
@@ -42,76 +37,21 @@ export class DatatableActionsComponent {
         return this.datasetList.find(d => d.name === this.datasetSelected);
     }
 
-    /**
-     * Returns URL to download file for the given format.
-     *
-     * @param  {string} format - The file format to download.
-     *
-     * @return string
-     */
-    getUrl(format: string): string {
-        let query: string = `${getHost(this.appConfig.apiUrl)}/search/${this.datasetSelected}?a=${this.outputList.join(';')}`;
-        if (this.criteriaList.length > 0) {
-            query += `&c=${this.criteriaList.map(criterion => criterionToString(criterion)).join(';')};${this.getCriterionSelectedData()}`;
-        } else {
-            query += `&c=${this.getCriterionSelectedData()}`;
-        }
-        if (this.coneSearch) {
-            query += `&cs=${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}`;
-        }
-        query += `&f=${format}`;
-        return query;
-    }
-
     getCriterionSelectedData() {
         const attributeId = this.attributeList.find(a => a.search_flag === 'ID');
         return `${attributeId.id}::in::${this.selectedData.join('|')}`;
     }
 
-    /**
-     * Returns URL to download archive.
-     *
-     * @return boolean
-     */
-    getUrlArchive(): string {
-        let query: string = `${getHost(this.appConfig.apiUrl)}/archive/${this.datasetSelected}?a=${this.outputList.join(';')}`;
-        if (this.criteriaList.length > 0) {
-            query += `&c=${this.criteriaList.map(criterion => criterionToString(criterion)).join(';')};${this.getCriterionSelectedData()}`;
-        } else {
-            query += `&c=${this.getCriterionSelectedData()}`;
-        }
-        if (this.coneSearch) {
-            query += `&cs=${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}`;
-        }
-        return query;
-    }
-
-    /**
-     * Emits event to  action to broadcast data.
-     *
-     * @fires EventEmitter<string>
-     */
-    broadcastVotable(): void {
-        this.broadcast.emit(this.getUrl('votable'));
+    downloadResult(format: string) {
+        this.startTaskCreateResult.emit({
+            format,
+            selectedData: true
+        });
     }
 
-    /**
-     * Allows to download file.
-     */
-    click(event, href, extension): void {
-        event.preventDefault();
-
-        if (extension === 'zip') {
-            this.toastr.info('Achive is under construction, please wait', 'Download archive');
-        }
-
-        this.http.get(href, {responseType: "blob"}).subscribe(
-            data => {
-                let downloadLink = document.createElement('a');
-                downloadLink.href = window.URL.createObjectURL(data);
-                downloadLink.setAttribute('download', `${this.datasetSelected}.${extension}`);
-                downloadLink.click();
-            }
-        );
+    downloadArchive() {
+        this.startTaskCreateArchive.emit({
+            selectedData: true
+        });
     }
 }
diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.html b/client/src/app/instance/search/components/result/datatable-tab.component.html
index b0ad6b0c98a437955dc1be5f759df0ce1f640a94..3a9773c09cb7445b215159963028ddaeece775f7 100644
--- a/client/src/app/instance/search/components/result/datatable-tab.component.html
+++ b/client/src/app/instance/search/components/result/datatable-tab.component.html
@@ -18,22 +18,25 @@
             [coneSearch]="coneSearch"
             [dataLength]="dataLength"
             [sampRegistered]="sampRegistered"
-            (broadcast)="broadcast.emit($event)">
+            (broadcast)="broadcast.emit($event)"
+            (startTaskCreateResult)="startTaskCreateResult.emit($event)"
+            (startTaskCreateArchive)="startTaskCreateArchive.emit($event)">
         </app-datatable-actions>
         <app-datatable
-                [dataset]="datasetList | datasetByName:datasetSelected"
-                [instance]="instance"
-                [attributeList]="attributeList"
-                [outputList]="outputList"
-                [queryParams]="queryParams"
-                [dataLength]="dataLength"
-                [data]="data"
-                [dataIsLoading]="dataIsLoading"
-                [dataIsLoaded]="dataIsLoaded"
-                [selectedData]="selectedData"
-                (retrieveData)="retrieveData.emit($event)"
-                (addSelectedData)="addSelectedData.emit($event)"
-                (deleteSelectedData)="deleteSelectedData.emit($event)">
+            [dataset]="datasetList | datasetByName:datasetSelected"
+            [instance]="instance"
+            [attributeList]="attributeList"
+            [outputList]="outputList"
+            [queryParams]="queryParams"
+            [dataLength]="dataLength"
+            [data]="data"
+            [dataIsLoading]="dataIsLoading"
+            [dataIsLoaded]="dataIsLoaded"
+            [selectedData]="selectedData"
+            (retrieveData)="retrieveData.emit($event)"
+            (addSelectedData)="addSelectedData.emit($event)"
+            (deleteSelectedData)="deleteSelectedData.emit($event)"
+            (downloadFile)="downloadFile.emit($event)">
         </app-datatable>
     </accordion-group>
 </accordion>
diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.ts b/client/src/app/instance/search/components/result/datatable-tab.component.ts
index c70564ba46caa5d342e2c80c5516221d549dab58..2f4e3475b628d1e746d03f88ec93c27d5eef1cd5 100644
--- a/client/src/app/instance/search/components/result/datatable-tab.component.ts
+++ b/client/src/app/instance/search/components/result/datatable-tab.component.ts
@@ -40,4 +40,7 @@ export class DatatableTabComponent {
     @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter();
     @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter();
     @Output() broadcast: EventEmitter<string> = new EventEmitter();
+    @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean }> = new EventEmitter();
+    @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter();
+    @Output() downloadFile: EventEmitter<{url: string, fileId: string, datasetName: string, filename: string}> = new EventEmitter();
 }
diff --git a/client/src/app/instance/search/components/result/datatable.component.html b/client/src/app/instance/search/components/result/datatable.component.html
index bbf61801f4fcfe35d312fcebf46867356116be76..0647cae6aa7c479d26a4a022839dca28464509f0 100644
--- a/client/src/app/instance/search/components/result/datatable.component.html
+++ b/client/src/app/instance/search/components/result/datatable.component.html
@@ -62,7 +62,8 @@
                                 [value]="datum[attribute.label]"
                                 [datasetName]="dataset.name"
                                 [datasetPublic]="dataset.public"
-                                [config]="getRendererConfig(attribute)">
+                                [config]="getRendererConfig(attribute)"
+                                (downloadFile)="downloadFile.emit($event)">
                             </app-download-renderer>
                         </div>
                         <div *ngSwitchCase="'image'">
diff --git a/client/src/app/instance/search/components/result/datatable.component.ts b/client/src/app/instance/search/components/result/datatable.component.ts
index bab2e9c71b6b4105fa0e39b3c018bee5f042e6a7..0718b70e9c2ef8024bd9fb4b35a568f53532852e 100644
--- a/client/src/app/instance/search/components/result/datatable.component.ts
+++ b/client/src/app/instance/search/components/result/datatable.component.ts
@@ -44,6 +44,7 @@ export class DatatableComponent implements OnInit {
     @Output() retrieveData: EventEmitter<Pagination> = new EventEmitter();
     @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter();
     @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter();
+    @Output() downloadFile: EventEmitter<{url: string, fileId: string, datasetName: string, filename: string}> = new EventEmitter();
 
     public page = 1;
     public nbItems = 10;
diff --git a/client/src/app/instance/search/components/result/download-file-tab.component.html b/client/src/app/instance/search/components/result/download-file-tab.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..49f60cee2bf34aa7a4d379d4b12829c8b6b6bdee
--- /dev/null
+++ b/client/src/app/instance/search/components/result/download-file-tab.component.html
@@ -0,0 +1,11 @@
+<div class="jumbotron mb-4 py-4">
+    <div class="lead">
+        Files downloaded : 
+        <ul>
+            <li *ngFor="let downloadFile of downloadedFiles">
+                {{ downloadFile.fileName }} : 
+                <progressbar [value]="downloadFile.progress" [type]="getType(downloadFile.progress)" [animate]="true">{{ downloadFile.progress }}%</progressbar> 
+            </li>
+        </ul>
+    </div>
+</div>
\ No newline at end of file
diff --git a/client/src/app/instance/search/components/result/download-file-tab.component.scss b/client/src/app/instance/search/components/result/download-file-tab.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/client/src/app/instance/search/components/result/download-file-tab.component.ts b/client/src/app/instance/search/components/result/download-file-tab.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..95d1e5b6389f9ef6b91263070574821c752901d9
--- /dev/null
+++ b/client/src/app/instance/search/components/result/download-file-tab.component.ts
@@ -0,0 +1,33 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input } from '@angular/core';
+
+import { DownloadFile } from 'src/app/instance/store/models';
+
+/**
+ * @class
+ * @classdesc Search result reminder component.
+ */
+@Component({
+    selector: 'app-download-file-tab',
+    templateUrl: 'download-file-tab.component.html',
+    styleUrls: ['download-file-tab.component.scss']
+})
+export class DownloadFileTabComponent {
+    @Input() downloadedFiles: DownloadFile[];
+
+    getType(value: number): 'success' | 'info' {
+        if (value < 100) {
+            return 'info';
+        }
+        
+        return 'success';
+    }
+}
diff --git a/client/src/app/instance/search/components/result/download.component.html b/client/src/app/instance/search/components/result/download.component.html
index 24b48f2a7d503452bb2c9e1f82e65e92e0ccd57f..0c9c15a510fa3b62b7405b882a5d30a4068808dd 100644
--- a/client/src/app/instance/search/components/result/download.component.html
+++ b/client/src/app/instance/search/components/result/download.component.html
@@ -18,19 +18,19 @@
                     <p>Download results just here:</p>
                 </div>
                 <div class="col">
-                    <a *ngIf="getConfigDownloadResultFormat('download_csv')" [href]="getUrl('csv')" (click)="click($event, getUrl('csv'), 'csv')" class="btn btn-outline-primary" title="Download results in CSV format">
+                    <a *ngIf="getConfigDownloadResultFormat('download_csv')" (click)="downloadResult('csv')" class="btn btn-outline-primary" title="Download results in CSV format">
                         <span class="fas fa-file-csv"></span> CSV
                     </a>
                     &nbsp;
-                    <a *ngIf="getConfigDownloadResultFormat('download_ascii')" [href]="getUrl('ascii')" (click)="click($event, getUrl('ascii'), 'txt')" class="btn btn-outline-primary" title="Download results in ASCII format">
+                    <a *ngIf="getConfigDownloadResultFormat('download_ascii')" (click)="downloadResult('ascii')" class="btn btn-outline-primary" title="Download results in ASCII format">
                         <span class="fas fa-file"></span> ASCII
                     </a>
                     &nbsp;
-                    <a *ngIf="getConfigDownloadResultFormat('download_vo')" [href]="getUrl('votable')" (click)="click($event, getUrl('votable'), 'xml')" class="btn btn-outline-primary" title="Download results in VO format">
+                    <a *ngIf="getConfigDownloadResultFormat('download_vo')" (click)="downloadResult('votable')" class="btn btn-outline-primary" title="Download results in VO format">
                         <span class="fas fa-file"></span> VOtable
                     </a>
                     &nbsp;
-                    <button *ngIf="getConfigDownloadResultFormat('download_vo')" [disabled]="!sampRegistered" (click)="broadcastVotable()" class="btn btn-outline-primary" title="Broadcast samp votable">
+                    <button *ngIf="getConfigDownloadResultFormat('download_vo')" [disabled]="!sampRegistered" (click)="downloadResult('votable')" class="btn btn-outline-primary" title="Broadcast samp votable">
                         <span class="fas fa-broadcast-tower"></span> Broadcast VOtable
                     </button>
                 </div>
@@ -41,7 +41,7 @@
                     <p>Download archive files just here:</p>
                 </div>
                 <div class="col">
-                    <a [href]="getUrlArchive()" (click)="click($event, getUrlArchive(), 'zip')" class="btn btn-outline-primary" title="Download an archive with all files">
+                    <a (click)="downloadArchive()" class="btn btn-outline-primary" title="Download an archive with all files">
                         <span class="fas fa-archive"></span> Files archive
                     </a>
                 </div>
diff --git a/client/src/app/instance/search/components/result/download.component.spec.ts b/client/src/app/instance/search/components/result/download.component.spec.ts
index 840d91af39bb8e863a90a42c9dbc9792085a52b8..dace40ba4e39bd0d7205c26cb89181ae887b2d8b 100644
--- a/client/src/app/instance/search/components/result/download.component.spec.ts
+++ b/client/src/app/instance/search/components/result/download.component.spec.ts
@@ -6,7 +6,6 @@ import { AccordionModule } from 'ngx-bootstrap/accordion';
 
 import { DownloadComponent } from './download.component';
 import { AppConfigService } from '../../../../app-config.service';
-import { FieldCriterion } from '../../../store/models/criterion';
 
 describe('[Instance][Search][Component][Result] DownloadComponent', () => {
     let component: DownloadComponent;
@@ -380,40 +379,4 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => {
         expect(component.getConfigDownloadResultFormat('download_csv')).toBeTruthy();
         expect(component.getConfigDownloadResultFormat('download_ascii')).toBeFalsy();
     });
-
-    it('#getUrl(format) should construct url with format parameter', () => {
-        appConfigServiceStub.apiUrl = 'http://test.com';
-        component.datasetSelected = 'myDataset';
-        component.outputList = [1, 2, 3];
-        component.criteriaList = [];
-        expect(component.getUrl('csv')).toBe('http://test.com/search/myDataset?a=1;2;3&f=csv');
-        component.criteriaList = [
-            {'id':1,'type':'field','operator':'eq','value':'one'} as FieldCriterion,
-            {'id':2,'type':'field','operator':'eq','value':'two'} as FieldCriterion
-        ];
-        component.coneSearch = { ra: 4, dec: 5, radius: 6 };
-        expect(component.getUrl('csv')).toBe('http://test.com/search/myDataset?a=1;2;3&c=1::eq::one;2::eq::two&cs=4:5:6&f=csv');
-    });
-
-    it('#getUrlArchive() should construct url to access to archive', () => {
-        appConfigServiceStub.apiUrl = 'http://test.com';
-        component.datasetSelected = 'myDataset';
-        component.outputList = [1, 2, 3];
-        component.criteriaList = [
-            {'id':1,'type':'field','operator':'eq','value':'one'} as FieldCriterion,
-            {'id':2,'type':'field','operator':'eq','value':'two'} as FieldCriterion
-        ];
-        component.coneSearch = { ra: 4, dec: 5, radius: 6 };
-        expect(component.getUrlArchive()).toBe('http://test.com/archive/myDataset?a=1;2;3&c=1::eq::one;2::eq::two&cs=4:5:6');
-    });
-
-    it('#broadcastVotable() should raise broadcast event when clicked', () => {
-        appConfigServiceStub.apiUrl = 'http://test.com';
-        component.datasetSelected = 'myDataset';
-        component.outputList = [1, 2, 3];
-        component.criteriaList = [];
-        component.broadcast.subscribe((event: string) => expect(event).toEqual('toto'));
-        component.broadcastVotable();
-
-    });
 });
diff --git a/client/src/app/instance/search/components/result/download.component.ts b/client/src/app/instance/search/components/result/download.component.ts
index f7ca76a176db606ccccea47bf798de3dfc1599ea..e9e03f9256c88c0f53ee73a4c59b695d97e9a491 100644
--- a/client/src/app/instance/search/components/result/download.component.ts
+++ b/client/src/app/instance/search/components/result/download.component.ts
@@ -8,12 +8,9 @@
  */
 
 import { Component, EventEmitter, Input, Output } from '@angular/core';
-import { HttpClient } from '@angular/common/http';
 
-import { Criterion, ConeSearch, criterionToString } from '../../../store/models';
+import { Criterion, ConeSearch } from '../../../store/models';
 import { Dataset } from 'src/app/metamodel/models';
-import { getHost } from 'src/app/shared/utils';
-import { AppConfigService } from 'src/app/app-config.service';
 
 /**
  * @class
@@ -32,8 +29,8 @@ export class DownloadComponent {
     @Input() dataLength: number;
     @Input() sampRegistered: boolean;
     @Output() broadcast: EventEmitter<string> = new EventEmitter();
-
-    constructor(private appConfig: AppConfigService, private http: HttpClient) { }
+    @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean }> = new EventEmitter();
+    @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter();
 
     /**
      * Checks if download tab has to be display.
@@ -67,63 +64,16 @@ export class DownloadComponent {
         return dataset.config.download[format];
     }
 
-    /**
-     * Returns URL to download file for the given format.
-     *
-     * @param  {string} format - The file format to download.
-     *
-     * @return string
-     */
-    getUrl(format: string): string {
-        let query: string = `${getHost(this.appConfig.apiUrl)}/search/${this.datasetSelected}?a=${this.outputList.join(';')}`;
-        if (this.criteriaList.length > 0) {
-            query += `&c=${this.criteriaList.map(criterion => criterionToString(criterion)).join(';')}`;
-        }
-        if (this.coneSearch) {
-            query += `&cs=${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}`;
-        }
-        query += `&f=${format}`;
-        return query;
-    }
-
-    /**
-     * Returns URL to download archive.
-     *
-     * @return boolean
-     */
-    getUrlArchive(): string {
-        let query: string = `${getHost(this.appConfig.apiUrl)}/archive/${this.datasetSelected}?a=${this.outputList.join(';')}`;
-        if (this.criteriaList.length > 0) {
-            query += `&c=${this.criteriaList.map(criterion => criterionToString(criterion)).join(';')}`;
-        }
-        if (this.coneSearch) {
-            query += `&cs=${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}`;
-        }
-        return query;
-    }
-
-    /**
-     * Emits event to  action to broadcast data.
-     *
-     * @fires EventEmitter<string>
-     */
-    broadcastVotable(): void {
-        this.broadcast.emit(this.getUrl('votable'));
+    downloadResult(format: string) {
+        this.startTaskCreateResult.emit({
+            format,
+            selectedData: false
+        });
     }
 
-    /**
-     * Allows to download file.
-     */
-    click(event, href, extension): void {
-        event.preventDefault();
-
-        this.http.get(href, {responseType: "blob"}).subscribe(
-            data => {
-                let downloadLink = document.createElement('a');
-                downloadLink.href = window.URL.createObjectURL(data);
-                downloadLink.setAttribute('download', `${this.datasetSelected}.${extension}`);
-                downloadLink.click();
-            }
-        );
+    downloadArchive() {
+        this.startTaskCreateArchive.emit({
+            selectedData: false
+        });
     }
 }
diff --git a/client/src/app/instance/search/components/result/index.ts b/client/src/app/instance/search/components/result/index.ts
index fffbcf4e766b53c4b4788371155f5e08e7b22207..4fa8d96abe8417aebb5a490ea60e9744f7616cc6 100644
--- a/client/src/app/instance/search/components/result/index.ts
+++ b/client/src/app/instance/search/components/result/index.ts
@@ -5,6 +5,7 @@ import { SampComponent } from './samp.component';
 import { UrlDisplayComponent } from './url-display.component';
 import { DatatableComponent } from './datatable.component';
 import { DatatableActionsComponent } from './datatable-actions.component';
+import { DownloadFileTabComponent } from './download-file-tab.component';
 import { rendererComponents } from './renderer';
 
 export const resultComponents = [
@@ -15,5 +16,6 @@ export const resultComponents = [
     UrlDisplayComponent,
     DatatableComponent,
     DatatableActionsComponent,
+    DownloadFileTabComponent,
     rendererComponents
 ];
diff --git a/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts b/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts
index db6f92c8d0abe1335808f6017f972f99a488ae90..9cdb554f3312fcc61eb373a1068e359ac3f00adf 100644
--- a/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts
+++ b/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts
@@ -7,8 +7,7 @@
  * file that was distributed with this source code.
  */
 
-import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
-import { HttpClient } from '@angular/common/http';
+import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
 
 import { DownloadRendererConfig } from 'src/app/metamodel/models/renderers/download-renderer-config.model';
 import { getHost } from 'src/app/shared/utils';
@@ -28,8 +27,9 @@ export class DownloadRendererComponent {
     @Input() datasetName: string;
     @Input() datasetPublic: boolean;
     @Input() config: DownloadRendererConfig;
+    @Output() downloadFile: EventEmitter<{url: string, fileId: string, datasetName: string, filename: string}> = new EventEmitter();
 
-    constructor(private appConfig: AppConfigService, private http: HttpClient) { }
+    constructor(private appConfig: AppConfigService) { }
 
     /**
      * Returns link href.
@@ -50,21 +50,23 @@ export class DownloadRendererComponent {
     }
 
     /**
-     * Downloads file on click.
+     * Starts downloading file on click.
      */
     click(event): void {
         event.preventDefault();
+        
+        const url = this.getHref();
+        const filename = url.substring(url.lastIndexOf('/') + 1);
 
-        const href = this.getHref();
-        this.http.get(href, { responseType: "blob" }).subscribe(
-            data => {
-                const filename = href.substring(href.lastIndexOf('/') + 1);
+        const n = Math.floor(Math.random() * 11);
+        const k = Math.floor(Math.random() * 1000000);
+        const m = String.fromCharCode(n) + k;
 
-                let downloadLink = document.createElement('a');
-                downloadLink.href = window.URL.createObjectURL(data);
-                downloadLink.setAttribute('download', filename);
-                downloadLink.click();
-            }
-        );
+        this.downloadFile.emit({
+            url,
+            fileId: m,
+            datasetName: this.datasetName,
+            filename
+        });
     }
 }
diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html
index 6ab41d2cd824b5b049c07165a48da4f90bdd5428..d06e9f7f5d38fe71eb6878a08d112f32a7426ef1 100644
--- a/client/src/app/instance/search/containers/result.component.html
+++ b/client/src/app/instance/search/containers/result.component.html
@@ -25,6 +25,10 @@
                     selected with <span class="font-weight-bold">{{ dataLength | async }}</span> objects found.
                 </div>
             </div>
+            <app-download-file-tab
+                *ngIf="(downloadedFiles | async).length > 0"
+                [downloadedFiles]="downloadedFiles | async">
+            </app-download-file-tab>
             <app-download
                 [datasetSelected]="datasetSelected | async"
                 [datasetList]="datasetList | async"
@@ -33,7 +37,9 @@
                 [coneSearch]="coneSearch | async"
                 [dataLength]="dataLength | async"
                 [sampRegistered]="sampRegistered | async"
-                (broadcast)="broadcastVotable($event)">
+                (broadcast)="broadcastVotable($event)"
+                (startTaskCreateResult)="startTaskCreateResult($event)"
+                (startTaskCreateArchive)="startTaskCreateArchive($event)">
             </app-download>
             <app-reminder
                 [datasetSelected]="datasetSelected | async"
@@ -77,7 +83,10 @@
                 (retrieveData)="retrieveData($event)"
                 (addSelectedData)="addSearchData($event)"
                 (deleteSelectedData)="deleteSearchData($event)"
-                (broadcast)="broadcastVotable($event)">
+                (broadcast)="broadcastVotable($event)"
+                (startTaskCreateResult)="startTaskCreateResult($event)"
+                (startTaskCreateArchive)="startTaskCreateArchive($event)"
+                (downloadFile)="downloadFile($event)">
             </app-datatable-tab>
         </ng-container>
     </div>
diff --git a/client/src/app/instance/search/containers/result.component.ts b/client/src/app/instance/search/containers/result.component.ts
index 599e5cb56a342dc18cb8fae409fc70da7e8a2831..cc058ea450f4445bd295363a7fe2b2a25dee5f34 100644
--- a/client/src/app/instance/search/containers/result.component.ts
+++ b/client/src/app/instance/search/containers/result.component.ts
@@ -13,13 +13,15 @@ import { Store } from '@ngrx/store';
 import { Observable, Subscription } from 'rxjs';
 
 import { AbstractSearchComponent } from './abstract-search.component';
-import { Pagination } from '../../store/models';
+import { Pagination, DownloadFile } from '../../store/models';
 import { Instance } from 'src/app/metamodel/models';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
 import * as searchActions from '../../store/actions/search.actions';
 import * as searchSelector from '../../store/selectors/search.selector';
 import * as sampActions from '../../store/actions/samp.actions';
 import * as sampSelector from '../../store/selectors/samp.selector';
+import * as downloadFileActions from '../../store/actions/download-file.actions';
+import * as downloadFileSelector from '../../store/selectors/download-file.selector';
 
 /**
  * @class
@@ -42,6 +44,7 @@ export class ResultComponent extends AbstractSearchComponent {
     public dataIsLoaded: Observable<boolean>;
     public selectedData: Observable<any>;
     public sampRegistered: Observable<boolean>;
+    public downloadedFiles: Observable<DownloadFile[]>;
     public pristineSubscription: Subscription;
 
     constructor(protected store: Store<{ }>) {
@@ -55,6 +58,7 @@ export class ResultComponent extends AbstractSearchComponent {
         this.dataIsLoaded = this.store.select(searchSelector.selectDataIsLoaded);
         this.selectedData = this.store.select(searchSelector.selectSelectedData);
         this.sampRegistered = this.store.select(sampSelector.selectRegistered);
+        this.downloadedFiles = this.store.select(downloadFileSelector.selectDownloadedFiles);
     }
 
     ngOnInit(): void {
@@ -120,6 +124,32 @@ export class ResultComponent extends AbstractSearchComponent {
         this.store.dispatch(searchActions.deleteSelectedData({ id }));
     }
 
+    /**
+     * Dispatches action to starts downloading file.
+     *
+     * @param  {url: string, fileId: string, datasetName: string, filename: string} download - Info about file to download
+     */
+    downloadFile(download: {url: string, fileId: string, datasetName: string, filename: string}): void {
+        this.store.dispatch(downloadFileActions.downloadFile(download));
+    }
+
+    /**
+     * Dispatches action to starts task create result and download
+     *
+     * @param string format - Info about result format
+     */
+    startTaskCreateResult(event: { format: string, selectedData: boolean }) {
+        this.store.dispatch(downloadFileActions.startTaskCreateResult(event));
+    }
+
+    /**
+     * Dispatches action to starts task create archive and download
+     *
+     */
+    startTaskCreateArchive(event: { selectedData: boolean }) {
+        this.store.dispatch(downloadFileActions.startTaskCreateArchive(event));
+    }
+
     /**
      * Dispatches action to destroy search results.
      */
diff --git a/client/src/app/instance/search/services/download.service.ts b/client/src/app/instance/search/services/download.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2fa8241df91a05991141027afa35385b51ee01f8
--- /dev/null
+++ b/client/src/app/instance/search/services/download.service.ts
@@ -0,0 +1,13 @@
+import { HttpClient } from "@angular/common/http";
+import { Observable } from "rxjs";
+
+export class DownloadService {
+    constructor(private http: HttpClient) { }
+
+    download(url: string): Observable<Blob> {
+        return this.http.get(url, {
+            reportProgress: true,
+            responseType: 'blob'
+        });
+    }
+}
diff --git a/client/src/app/instance/store/actions/download-file.actions.ts b/client/src/app/instance/store/actions/download-file.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8368ed9207b145ecd1970f5d63569c06afd35270
--- /dev/null
+++ b/client/src/app/instance/store/actions/download-file.actions.ts
@@ -0,0 +1,28 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createAction, props } from '@ngrx/store';
+
+export const startTaskCreateResult = createAction('[File] Start Task Create Result', props<{ format: string, selectedData: boolean }>());
+export const startTaskCreateResultSuccess = createAction('[File] Start Task Create Result Success', props<{ fileId: string, datasetName: string, filename: string }>());
+export const startTaskCreateResultFail = createAction('[File] Start Task Create Result Fail');
+export const isResultAvailable = createAction('[File] Is Result Available', props<{ fileId: string, datasetName: string, filename: string }>());
+export const isResultAvailableFail = createAction('[File] Is Result Available Fail');
+
+export const startTaskCreateArchive = createAction('[File] Start Task Create Archive', props<{ selectedData: boolean }>());
+export const startTaskCreateArchiveSuccess = createAction('[File] Start Task Create Archive Success', props<{ fileId: string, datasetName: string, filename: string }>());
+export const startTaskCreateArchiveFail = createAction('[File] Start Task Create Archive Fail');
+export const isArchiveAvailable = createAction('[File] Is Archive Available', props<{ fileId: string, datasetName: string, filename: string }>());
+export const isArchiveAvailableFail = createAction('[File] Is Archive Available Fail');
+
+export const downloadFile = createAction('[File] Download File', props<{ url: string, fileId: string, datasetName: string, filename: string }>());
+
+export const startsDownloadingFile = createAction('[File] Starts Downloading File', props<{ url: string, filename: string, fileId: string }>());
+export const updateDownloadProgress = createAction('[File] Update Download Progress', props<{ progress: number, fileId: string }>());
+export const fileDownloaded = createAction('[File] File Downloaded', props<{ fileId: string }>());
diff --git a/client/src/app/instance/store/effects/detail.effects.ts b/client/src/app/instance/store/effects/detail.effects.ts
index 941f318d5ab5563666b0bbbc78334f47de607d15..b1f2aa5f2bfec3d2c09c3aa7055ee309655cafff 100644
--- a/client/src/app/instance/store/effects/detail.effects.ts
+++ b/client/src/app/instance/store/effects/detail.effects.ts
@@ -10,7 +10,7 @@
 import { Injectable } from '@angular/core';
 
 import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
-import { Store  } from '@ngrx/store';
+import { Store } from '@ngrx/store';
 import { of } from 'rxjs';
 import { map, tap, mergeMap, catchError } from 'rxjs/operators';
 import { ToastrService } from 'ngx-toastr';
diff --git a/client/src/app/instance/store/effects/download-file.effects.ts b/client/src/app/instance/store/effects/download-file.effects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7caa91165f2ff0a6bc6144714109e2b8c1fe140
--- /dev/null
+++ b/client/src/app/instance/store/effects/download-file.effects.ts
@@ -0,0 +1,259 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpEventType } from '@angular/common/http';
+
+import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
+import { Store } from '@ngrx/store';
+import { of, timer, mapTo, takeUntil, Subject } from 'rxjs';
+import { map, tap, mergeMap, switchMap, catchError } from 'rxjs/operators';
+import { ToastrService } from 'ngx-toastr';
+
+import { AppConfigService } from 'src/app/app-config.service';
+import { DownloadFileService } from '../services/download-file.service';
+import * as downloadFileActions from '../actions/download-file.actions';
+import * as searchSelector from '../selectors/search.selector';
+import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector';
+import * as coneSearchSelector from '../selectors/cone-search.selector';
+
+/**
+ * @class
+ * @classdesc File effects.
+ */
+@Injectable()
+export class DownloadFileEffects {
+    startTaskCreateResult$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startTaskCreateResult),
+            concatLatestFrom(() => [
+                this.store.select(searchSelector.selectCurrentDataset),
+                this.store.select(attributeSelector.selectAllAttributes),
+                this.store.select(searchSelector.selectCriteriaListByRoute),
+                this.store.select(coneSearchSelector.selectConeSearchByRoute),
+                this.store.select(searchSelector.selectOutputListByRoute),
+                this.store.select(searchSelector.selectSelectedData)
+            ]),
+            mergeMap(([action, currentDataset, attributeList, criteriaList, coneSearch, outputList, selectedData]) => {
+                const attributeId = attributeList.find(a => a.search_flag === 'ID');
+                let query: string = `${currentDataset}?a=${outputList}`;
+                if (criteriaList) {
+                    query += `&c=${criteriaList}`;
+                    if (selectedData && action.selectedData) {
+                        query += `;${attributeId.id}::in::${selectedData.join('|')}`;
+                    }
+                } else if (selectedData && action.selectedData) {
+                    query += `&c=${attributeId.id}::in::${selectedData.join('|')}`;
+                }
+                if (coneSearch) {
+                    query += `&cs=${coneSearch}`;
+                }
+                query += `&f=${action.format}`;
+                
+                return this.downloadFileService.startTaskCreateResult(query)
+                    .pipe(
+                        map((response) => downloadFileActions.startTaskCreateResultSuccess({
+                            fileId: response.file_id,
+                            filename: response.file_name,
+                            datasetName: currentDataset
+                        })),
+                        catchError(() => of(downloadFileActions.startTaskCreateResultFail()))
+                    )
+            })
+        )
+    );
+
+    startTaskCreateResultSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startTaskCreateResultSuccess),
+            tap(action => this.kill$[action.fileId] = new Subject()),
+            switchMap(action => timer(0, 1000)
+                .pipe(
+                    mapTo(downloadFileActions.isResultAvailable(action)),
+                    takeUntil(this.kill$[action.fileId])
+                )
+            )
+        )
+    );
+
+    startTaskCreateResultFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startTaskCreateResultFail),
+            tap(() => this.toastr.error('The creation of the result file failed', 'Start async task failed'))
+        ), { dispatch: false}
+    );
+
+    isResultAvailable$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.isResultAvailable),
+            switchMap(action => this.downloadFileService.isResultAvailable(action.fileId)
+                .pipe(
+                    map(result => {
+                        if (result.file_is_available) {
+                            this.kill$[action.fileId].next({});
+                            this.kill$[action.fileId].unsubscribe();
+                            return downloadFileActions.startsDownloadingFile({
+                                fileId: action.fileId,
+                                filename: action.filename,
+                                url: `${this.config.apiUrl}/download-result/${action.datasetName}/${action.fileId}`
+                            });
+                        } else {
+                            return { type: '[No Action] Is Result Available' };
+                        }
+                    }),
+                    catchError(() => of(downloadFileActions.isResultAvailableFail()))
+                )
+            )
+        )
+    );
+
+    isResultAvailableFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.isResultAvailableFail),
+            tap(() => this.toastr.error('The creation of the file has encountered a problem', 'File result download failed'))
+        ), { dispatch: false}
+    );
+
+    startTaskCreateArchive$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startTaskCreateArchive),
+            concatLatestFrom(() => [
+                this.store.select(searchSelector.selectCurrentDataset),
+                this.store.select(attributeSelector.selectAllAttributes),
+                this.store.select(searchSelector.selectCriteriaListByRoute),
+                this.store.select(coneSearchSelector.selectConeSearchByRoute),
+                this.store.select(searchSelector.selectOutputListByRoute),
+                this.store.select(searchSelector.selectSelectedData)
+            ]),
+            mergeMap(([action, currentDataset, attributeList, criteriaList, coneSearch, outputList, selectedData]) => {
+                const attributeId = attributeList.find(a => a.search_flag === 'ID');
+                let query: string = `${currentDataset}?a=${outputList}`;
+                if (criteriaList) {
+                    query += `&c=${criteriaList}`;
+                    if (selectedData && action.selectedData) {
+                        query += `;${attributeId.id}::in::${selectedData.join('|')}`;
+                    }
+                } else if (selectedData && action.selectedData) {
+                    query += `&c=${attributeId.id}::in::${selectedData.join('|')}`;
+                }
+                if (coneSearch) {
+                    query += `&cs=${coneSearch}`;
+                }
+                
+                return this.downloadFileService.startTaskCreateArchive(query)
+                    .pipe(
+                        map((response) => downloadFileActions.startTaskCreateArchiveSuccess({
+                            fileId: response.archive_id,
+                            filename: response.archive_name,
+                            datasetName: currentDataset
+                        })),
+                        catchError(() => of(downloadFileActions.startTaskCreateArchiveFail()))
+                    )
+            })
+        )
+    );
+
+    startTaskCreateArchiveSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startTaskCreateArchiveSuccess),
+            tap(action => this.kill$[action.fileId] = new Subject()),
+            switchMap(action => timer(0, 1000)
+                .pipe(
+                    mapTo(downloadFileActions.isArchiveAvailable(action)),
+                    takeUntil(this.kill$[action.fileId])
+                )
+            )
+        )
+    );
+
+    startTaskCreateArchiveFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startTaskCreateArchiveFail),
+            tap(() => this.toastr.error('The creation of the archive file failed', 'Start async task failed'))
+        ), { dispatch: false}
+    );
+
+    isArchiveAvailable$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.isArchiveAvailable),
+            switchMap(action => this.downloadFileService.isArchiveAvailable(action.fileId)
+                .pipe(
+                    map(result => {
+                        if (result.archive_is_available) {
+                            this.kill$[action.fileId].next({});
+                            this.kill$[action.fileId].unsubscribe();
+                            return downloadFileActions.startsDownloadingFile({
+                                fileId: action.fileId,
+                                filename: action.filename,
+                                url: `${this.config.apiUrl}/download-archive/${action.datasetName}/${action.fileId}`
+                            });
+                        } else {
+                            return { type: '[No Action] Is Archive Available' };
+                        }
+                    }),
+                    catchError(() => of(downloadFileActions.isArchiveAvailableFail()))
+                )
+            )
+        )
+    );
+
+    isArchiveAvailableFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.isArchiveAvailableFail),
+            tap(() => this.toastr.error('The creation of the archive has encountered a problem', 'Archive result download failed'))
+        ), { dispatch: false}
+    );
+
+    downloadFile$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.downloadFile),
+            map(action => downloadFileActions.startsDownloadingFile({
+                url: action.url,
+                fileId: action.fileId,
+                filename: action.filename
+            }))
+        )
+    );
+
+    /**
+     * Calls actions to retrieve object.
+     */
+    startsDownloadingFile$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(downloadFileActions.startsDownloadingFile),
+            mergeMap((action) => this.downloadFileService.startsDownloadingFile(action.url)
+                .pipe(
+                    map(event => {
+                        if (event.type === HttpEventType.DownloadProgress) {
+                            this.store.dispatch(downloadFileActions.updateDownloadProgress({ 
+                                progress: Math.round((100 * event.loaded) / event.total),
+                                fileId: action.fileId
+                            }));
+                        }
+
+                        if (event.type === HttpEventType.Response) {
+                            this.store.dispatch(downloadFileActions.fileDownloaded({ fileId: action.fileId }));
+                            this.downloadFileService.saveDownloadedFile(event.body as Blob, action.filename);
+                        }
+                    })
+                )
+            )
+        ), { dispatch: false }
+    );
+
+    private kill$ = [];
+
+    constructor(
+        private actions$: Actions,
+        private downloadFileService: DownloadFileService,
+        private store: Store<{ }>,
+        private toastr: ToastrService,
+        private config: AppConfigService
+    ) {}
+}
diff --git a/client/src/app/instance/store/effects/index.ts b/client/src/app/instance/store/effects/index.ts
index 69b9a3b26cc0cd09cc16e974cbb4ddc4102571b9..18cb99de4d7126d13194355146ddacf39b3c8ae5 100644
--- a/client/src/app/instance/store/effects/index.ts
+++ b/client/src/app/instance/store/effects/index.ts
@@ -4,6 +4,7 @@ import { SearchMultipleEffects } from './search-multiple.effects';
 import { ConeSearchEffects } from './cone-search.effects';
 import { DetailEffects } from './detail.effects';
 import { SvomJsonKwEffects } from './svom-json-kw.effects';
+import { DownloadFileEffects } from './download-file.effects';
 
 export const instanceEffects = [
     SampEffects,
@@ -11,5 +12,6 @@ export const instanceEffects = [
     SearchMultipleEffects,
     ConeSearchEffects,
     DetailEffects,
-    SvomJsonKwEffects
+    SvomJsonKwEffects,
+    DownloadFileEffects
 ];
diff --git a/client/src/app/instance/store/models/download-file.model.ts b/client/src/app/instance/store/models/download-file.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d21ff574b88548eb8067f65b2b617377bba56f9
--- /dev/null
+++ b/client/src/app/instance/store/models/download-file.model.ts
@@ -0,0 +1,16 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+export interface DownloadFile {
+    id: string;
+    datasetName: string;
+    fileName: string;
+    state: 'PENDING' | 'IN_PROGRESS' | 'DONE';
+    progress: number;
+}
diff --git a/client/src/app/instance/store/models/index.ts b/client/src/app/instance/store/models/index.ts
index 2570f1630cacd2de9afd9bda713f92230d6b8f22..3b2241bcfbdd3629726f8766f5220e0b74b9ccf1 100644
--- a/client/src/app/instance/store/models/index.ts
+++ b/client/src/app/instance/store/models/index.ts
@@ -7,4 +7,5 @@ export * from './cone-search.model';
 export * from './resolver.model';
 export * from './search-multiple-dataset-length';
 export * from './search-multiple-dataset-data';
-export * from './svom-keyword.model';
\ No newline at end of file
+export * from './svom-keyword.model';
+export * from './download-file.model';
diff --git a/client/src/app/instance/store/reducers/download-file.reducer.ts b/client/src/app/instance/store/reducers/download-file.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..64daf37e3bad68eede73d702c032511b9b7a43ae
--- /dev/null
+++ b/client/src/app/instance/store/reducers/download-file.reducer.ts
@@ -0,0 +1,83 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createReducer, on } from '@ngrx/store';
+
+import { DownloadFile } from '../models';
+import * as downloadFileActions from '../actions/download-file.actions';
+
+/**
+ * Interface for file state.
+ *
+ * @interface State
+ */
+export interface State {
+    downloadedFiles: DownloadFile[]
+}
+
+export const initialState: State = {
+    downloadedFiles: []
+};
+
+export const fileReducer = createReducer(
+    initialState,
+    on(downloadFileActions.startTaskCreateResultSuccess, (state, {fileId, datasetName, filename }) => ({
+        ...state,
+        downloadedFiles: [...state.downloadedFiles, {
+            id: fileId,
+            datasetName,
+            fileName: filename,
+            state: 'PENDING',
+            progress: 0
+        }]
+    })),
+    on(downloadFileActions.startTaskCreateArchiveSuccess, (state, {fileId, datasetName, filename }) => ({
+        ...state,
+        downloadedFiles: [...state.downloadedFiles, {
+            id: fileId,
+            datasetName,
+            fileName: filename,
+            state: 'PENDING',
+            progress: 0
+        }]
+    })),
+    on(downloadFileActions.downloadFile, (state, {fileId, datasetName, filename }) => ({
+        ...state,
+        downloadedFiles: [...state.downloadedFiles, {
+            id: fileId,
+            datasetName,
+            fileName: filename,
+            state: 'PENDING',
+            progress: 0
+        }]
+    })),
+    on(downloadFileActions.updateDownloadProgress, (state, { progress, fileId }) => ({
+        ...state,
+        downloadedFiles: [
+            ...state.downloadedFiles.filter(f => f.id !== fileId),
+            {
+                ...state.downloadedFiles.find(f => f.id === fileId),
+                progress: progress
+            }
+        ]
+    })),
+    on(downloadFileActions.fileDownloaded, (state, { fileId }) => ({
+        ...state,
+        downloadedFiles: [
+            ...state.downloadedFiles.filter(f => f.id !== fileId),
+            {
+                ...state.downloadedFiles.find(f => f.id === fileId),
+                state: 'DONE',
+                progress: 100
+            }
+        ]
+    }))
+);
+
+export const selectDownloadedFiles = (state: State) => state.downloadedFiles;
\ No newline at end of file
diff --git a/client/src/app/instance/store/selectors/download-file.selector.ts b/client/src/app/instance/store/selectors/download-file.selector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b955521d86de25bbf5c6eef631a4b77e4f2bd982
--- /dev/null
+++ b/client/src/app/instance/store/selectors/download-file.selector.ts
@@ -0,0 +1,23 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createSelector } from '@ngrx/store';
+
+import * as reducer from '../../instance.reducer';
+import * as fromDownloadFile from '../reducers/download-file.reducer';
+
+export const selectDownloadFileState = createSelector(
+    reducer.getInstanceState,
+    (state: reducer.State) => state.downloadFile
+);
+
+export const selectDownloadedFiles = createSelector(
+    selectDownloadFileState,
+    fromDownloadFile.selectDownloadedFiles
+);
diff --git a/client/src/app/instance/store/services/download-file.service.ts b/client/src/app/instance/store/services/download-file.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25c26182e9f69d10ee75b025fde4cbed781d3a91
--- /dev/null
+++ b/client/src/app/instance/store/services/download-file.service.ts
@@ -0,0 +1,61 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpRequest } from '@angular/common/http';
+
+import { AppConfigService } from 'src/app/app-config.service';
+
+@Injectable({providedIn: 'root'})
+export class DownloadFileService {
+    constructor(private http: HttpClient, private config: AppConfigService) { }
+
+    /**
+     * Returns file name and file ID
+     *
+     * @param  {string} query - The query.
+     *
+     * @return Observable<{"file_name": string, "file_id": string}>
+     */
+    startTaskCreateResult(query: string) {
+        return this.http.get<{"file_name": string, "file_id": string}>(`${this.config.apiUrl}/start-task-create-result/${query}`);
+    }
+
+    isResultAvailable(id: string) {
+        return this.http.get<{"file_is_available": boolean}>(`${this.config.apiUrl}/is-result-available/${id}`);
+    }
+
+    startTaskCreateArchive(query: string) {
+        return this.http.get<{"archive_name": string, "archive_id": string}>(`${this.config.apiUrl}/start-task-create-archive/${query}`);
+    }
+
+    isArchiveAvailable(id: string) {
+        return this.http.get<{"archive_is_available": boolean}>(`${this.config.apiUrl}/is-archive-available/${id}`);
+    }
+
+    startsDownloadingFile(url: string) {
+        const request = new HttpRequest(
+            "GET",
+            url,
+            {},
+            { reportProgress: true, responseType: 'blob' }
+        );
+
+        return this.http.request(request);
+    }
+
+    saveDownloadedFile(body: Blob, filename: string) {
+        let downloadLink = document.createElement('a');
+        const url = window.URL.createObjectURL(body);
+        downloadLink.href = url;
+        downloadLink.setAttribute('download', filename);
+        downloadLink.click();
+        window.URL.revokeObjectURL(url);
+    }
+}
\ No newline at end of file
diff --git a/client/src/app/instance/store/services/index.ts b/client/src/app/instance/store/services/index.ts
index 795315e994b19b011ba88241a4428380e6932c09..d70ddbf82870dc1059472f410111eb4979b00052 100644
--- a/client/src/app/instance/store/services/index.ts
+++ b/client/src/app/instance/store/services/index.ts
@@ -3,11 +3,13 @@ import { SampService } from './samp.service';
 import { ConeSearchService } from './cone-search.service';
 import { DetailService } from './detail.service';
 import { SvomJsonKwService } from './svom-json-kw.service';
+import { DownloadFileService } from './download-file.service';
 
 export const instanceServices = [
     SearchService,
     SampService,
     ConeSearchService,
     DetailService,
-    SvomJsonKwService
+    SvomJsonKwService,
+    DownloadFileService
 ];
diff --git a/client/src/app/instance/store/services/samp.service.ts b/client/src/app/instance/store/services/samp.service.ts
index ebc95cbed6a0e343ba7bd671ba38256c39b96ea3..3dea465b139563c25674109a46b0d6cc5b179324 100644
--- a/client/src/app/instance/store/services/samp.service.ts
+++ b/client/src/app/instance/store/services/samp.service.ts
@@ -24,7 +24,10 @@ export class SampService {
     private connector = null;
     
     constructor(private config: AppConfigService) {
-        const baseUrl = `${window.location.protocol}//${window.location.host}${this.config.baseHref}`;
+        let baseUrl = `${window.location.protocol}//${window.location.host}`;
+        if (this.config.baseHref !== '/') {
+            baseUrl += this.config.baseHref;
+        }
         const meta = {
             "samp.name": "ANIS",
             "samp.description.text": "AstroNomical Information System",
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index bfb8594e449030fea934eab9a1143c8ead325adb..5455dacaf03013492277fba8166c698db0219f76 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -20,6 +20,7 @@ import { TooltipModule } from 'ngx-bootstrap/tooltip';
 import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
 import { TabsModule } from 'ngx-bootstrap/tabs';
 import { PaginationModule } from 'ngx-bootstrap/pagination';
+import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
 import { NgSelectModule } from '@ng-select/ng-select';
 import { NgxJsonViewerModule } from 'ngx-json-viewer';
 
@@ -48,6 +49,7 @@ import { sharedPipes } from './pipes';
         BsDatepickerModule.forRoot(),
         TabsModule.forRoot(),
         PaginationModule.forRoot(),
+        ProgressbarModule.forRoot(),
         NgSelectModule,
         NgxJsonViewerModule
     ],
@@ -63,6 +65,7 @@ import { sharedPipes } from './pipes';
         BsDatepickerModule,
         TabsModule,
         PaginationModule,
+        ProgressbarModule,
         NgSelectModule,
         NgxJsonViewerModule,
         sharedComponents,
diff --git a/conf-dev/dev-php.ini b/conf-dev/dev-php.ini
index b874d37d810aa500eff9bafac028c145ddbac783..df289dd8b59301ac4f8ad6f4147b2a102e3e4a41 100644
--- a/conf-dev/dev-php.ini
+++ b/conf-dev/dev-php.ini
@@ -12,7 +12,7 @@ max_execution_time = 30
 max_input_time = 60
 
 [xdebug]
-zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20200930/xdebug.so
+zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20210902/xdebug.so
 xdebug.mode=debug
 xdebug.client_host=127.0.0.1
 xdebug.client_port="9003"
diff --git a/docker-compose.yml b/docker-compose.yml
index c9609ca0e93056d9ca5801bb0646fa50c8321637..463a5f3c4884b73bbc4071b189d108caa672c1c7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -37,6 +37,10 @@ services:
             TOKEN_ENABLED: 0
             TOKEN_JWKS_URL: "http://keycloak:8180/auth/realms/anis/protocol/openid-connect/certs"
             TOKEN_ADMIN_ROLES: anis_admin,superuser
+            RMQ_HOST: rmq
+            RMQ_PORT: 5672
+            RMQ_USER: admin
+            RMQ_PASSWORD: admin
         ports:
             - 8080:80
         volumes:
@@ -59,6 +63,24 @@ services:
             - ./services:/project
             - ./data:/data
         command: /bin/bash -c "source /project/venv/bin/activate && exec python src/anis_services/app.py"
+    tasks:
+        image: python:3.8
+        # stdin_open: true
+        # tty: true
+        # command: sleep infinity
+        environment:
+            PYTHONPATH: "/project/src"
+            DATA_PATH: /data
+            SERVER_URL: http://server
+            RMQ_HOST: rmq
+            RMQ_PORT: 5672
+            RMQ_USER: admin
+            RMQ_PASSWORD: admin
+        working_dir: /project
+        volumes:
+            - ./tasks:/project
+            - ./data:/data
+        command: /bin/bash -c "source /project/venv/bin/activate && exec python src/anis_tasks/app.py"
     keycloak:
         image: jboss/keycloak
         environment:
@@ -84,6 +106,13 @@ services:
             - pgdata:/var/lib/postgresql/data
             - ./conf-dev/data_test.sql:/sql/data_test.sql
             - ./conf-dev/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
+    rmq:
+        image: rabbitmq:3-management
+        environment:
+            RABBITMQ_DEFAULT_USER: admin
+            RABBITMQ_DEFAULT_PASS: admin
+        ports:
+            - 15672:15672
     mailer:
         image: djfarrelly/maildev
         ports:
diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev
index a8d54a7eb2f5ed8a2ad4649f6c0509f76a3350cb..d770328e0b8bb0879c8936be5777582e0d68d75c 100644
--- a/server/Dockerfile.dev
+++ b/server/Dockerfile.dev
@@ -3,7 +3,7 @@ FROM php:8.1.3-apache
 # Install modules
 RUN apt-get update \ 
     && apt-get install -y zlib1g zlib1g-dev libpq-dev libpq5 libzip-dev zip unzip jq \
-    && docker-php-ext-install pgsql pdo_pgsql zip bcmath
+    && docker-php-ext-install pgsql pdo_pgsql zip bcmath sockets
     
 # Install pecl modules
 RUN pecl install xdebug && rm -rf /tmp/pear
diff --git a/server/app/dependencies.php b/server/app/dependencies.php
index 9fe363d692e79627b5dc6aea9e37acf9bce6371c..c8d86647e3a01033a1a103d15181b68dc6a72539 100644
--- a/server/app/dependencies.php
+++ b/server/app/dependencies.php
@@ -51,6 +51,17 @@ $container->set('logger', function (ContainerInterface $c) {
     return $logger;
 });
 
+// RabbitMQ connection factory
+$container->set('rmq', function (ContainerInterface $c) {
+    $rmqSettings = $c->get('settings')['rmq'];
+    return new \PhpAmqpLib\Connection\AMQPStreamConnection(
+        $rmqSettings['host'],
+        $rmqSettings['port'],
+        $rmqSettings['user'],
+        $rmqSettings['password']
+    );
+});
+
 // Actions
 $container->set('App\Action\RootAction', function () {
     return new App\Action\RootAction();
@@ -200,22 +211,28 @@ $container->set('App\Action\SearchAction', function (ContainerInterface $c) {
     );
 });
 
-$container->set('App\Action\ArchiveAction', function (ContainerInterface $c) {
-    $anisQueryBuilder = (new App\Search\Query\AnisQueryBuilder())
-        ->addQueryPart(new App\Search\Query\From())
-        ->addQueryPart(new App\Search\Query\Select())
-        ->addQueryPart(new App\Search\Query\ConeSearch())
-        ->addQueryPart(new App\Search\Query\Where(new App\Search\Query\Operator\OperatorFactory()))
-        ->addQueryPart(new App\Search\Query\Order())
-        ->addQueryPart(new App\Search\Query\Limit());
+$container->set('App\Action\StartTaskCreateResultAction', function (ContainerInterface $c) {
+    return new App\Action\StartTaskCreateResultAction($c->get('em'), $c->get('rmq'), $c->get(SETTINGS)['token']);
+});
 
-    return new App\Action\ArchiveAction(
-        $c->get('em'),
-        $c->get('settings')['data_path'],
-        new App\Search\DBALConnectionFactory(),
-        $anisQueryBuilder,
-        $c->get(SETTINGS)['token']
-    );
+$container->set('App\Action\IsResultAvailableAction', function (ContainerInterface $c) {
+    return new App\Action\IsResultAvailableAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']);
+});
+
+$container->set('App\Action\DownloadResultAction', function (ContainerInterface $c) {
+    return new App\Action\DownloadResultAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']);
+});
+
+$container->set('App\Action\StartTaskCreateArchiveAction', function (ContainerInterface $c) {
+    return new App\Action\StartTaskCreateArchiveAction($c->get('em'), $c->get('rmq'), $c->get(SETTINGS)['token']);
+});
+
+$container->set('App\Action\IsArchiveAvailableAction', function (ContainerInterface $c) {
+    return new App\Action\IsArchiveAvailableAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']);
+});
+
+$container->set('App\Action\DownloadArchiveAction', function (ContainerInterface $c) {
+    return new App\Action\DownloadArchiveAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']);
 });
 
 $container->set('App\Action\DatasetFileExplorerAction', function (ContainerInterface $c) {
diff --git a/server/app/routes.php b/server/app/routes.php
index 75d7a4fe2173c45f1d8aa8707587883a4dbb1e9f..a8aa5536e8b9fdb2cbf044ff00082091a3b224e0 100644
--- a/server/app/routes.php
+++ b/server/app/routes.php
@@ -15,6 +15,7 @@ use Slim\Routing\RouteCollectorProxy;
 $app->get('/', App\Action\RootAction::class);
 $app->get('/client-settings', App\Action\ClientSettingsAction::class);
 
+// Metamodel actions
 $app->group('', function (RouteCollectorProxy $group) {
     $group->map([OPTIONS, GET, POST], '/select', App\Action\SelectListAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/select/{name}', App\Action\SelectAction::class);
@@ -30,6 +31,7 @@ $app->group('', function (RouteCollectorProxy $group) {
     explode(',', $container->get(SETTINGS)['token']['admin_roles'])
 ));
 
+// Metamodel actions
 $app->group('', function (RouteCollectorProxy $group) {
     $group->map([OPTIONS, GET, POST], '/survey', App\Action\SurveyListAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/survey/{name}', App\Action\SurveyAction::class);
@@ -67,8 +69,19 @@ $app->group('', function (RouteCollectorProxy $group) {
     explode(',', $container->get(SETTINGS)['token']['admin_roles'])
 ));
 
+// Search actions
 $app->get('/search/{dname}', App\Action\SearchAction::class);
-$app->get('/archive/{dname}', App\Action\ArchiveAction::class);
+
+$app->get('/start-task-create-result/{dname}', App\Action\StartTaskCreateResultAction::class);
+$app->get('/is-result-available/{id}', App\Action\IsResultAvailableAction::class);
+$app->get('/download-result/{dname}/{id}', App\Action\DownloadResultAction::class);
+
+// Archive actions
+$app->get('/start-task-create-archive/{dname}', App\Action\StartTaskCreateArchiveAction::class);
+$app->get('/is-archive-available/{id}', App\Action\IsArchiveAvailableAction::class);
+$app->get('/download-archive/{dname}/{id}', App\Action\DownloadArchiveAction::class);
+
+// Explore and download individual files
 $app->get('/dataset-file-explorer/{dname}[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class);
 $app->get('/download-instance-file/{iname}/[{fpath:.*}]', App\Action\DownloadInstanceFileAction::class);
 $app->get('/download-file/{dname}/[{fpath:.*}]', App\Action\DownloadFileAction::class);
diff --git a/server/app/settings.php b/server/app/settings.php
index 879fe9dba2e308f1718d682d40e43fe2b5198d1f..b48111067021ea61b843264f4d3f18a5a3e2926f 100644
--- a/server/app/settings.php
+++ b/server/app/settings.php
@@ -42,5 +42,11 @@ return [
         'enabled' => getenv('TOKEN_ENABLED'),
         'jwks_url' => getenv('TOKEN_JWKS_URL'),
         'admin_roles' => getenv('TOKEN_ADMIN_ROLES')
+    ],
+    'rmq' => [
+        'host' => getenv('RMQ_HOST'),
+        'port' => getenv('RMQ_PORT'),
+        'user' => getenv('RMQ_USER'),
+        'password' => getenv('RMQ_PASSWORD')
     ]
 ];
diff --git a/server/composer.json b/server/composer.json
index 714fa1091c7e8b5c96f8f213cf815b628e80b5ec..97ffeff07e9b3a987862f1426fd48902bc287e09 100644
--- a/server/composer.json
+++ b/server/composer.json
@@ -26,7 +26,8 @@
         "doctrine/orm": "^2.11",
         "doctrine/annotations": "^1.13",
         "symfony/cache": "^6.0",
-        "firebase/php-jwt": "^5.5"
+        "firebase/php-jwt": "^5.5",
+        "php-amqplib/php-amqplib": "^3.1"
     },
     "require-dev": {
         "phpunit/phpunit": "^9.5"
diff --git a/server/composer.lock b/server/composer.lock
index a7b99df9c6b59e4f00ed6284b553bc5c31b583d7..7ad0d075d61012c6b4c58df93d14064ad59e7c53 100644
--- a/server/composer.lock
+++ b/server/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "72ae7c376a55611a0ac3fdd5ba134695",
+    "content-hash": "59a7af8c3869283b682223ccfc159cb8",
     "packages": [
         {
             "name": "doctrine/annotations",
@@ -1421,6 +1421,204 @@
             },
             "time": "2022-01-27T09:35:39+00:00"
         },
+        {
+            "name": "paragonie/constant_time_encoding",
+            "version": "v2.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/constant_time_encoding.git",
+                "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9229e15f2e6ba772f0c55dd6986c563b937170a8",
+                "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7|^8"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6|^7|^8|^9",
+                "vimeo/psalm": "^1|^2|^3|^4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "ParagonIE\\ConstantTime\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "Steve 'Sc00bz' Thomas",
+                    "email": "steve@tobtu.com",
+                    "homepage": "https://www.tobtu.com",
+                    "role": "Original Developer"
+                }
+            ],
+            "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
+            "keywords": [
+                "base16",
+                "base32",
+                "base32_decode",
+                "base32_encode",
+                "base64",
+                "base64_decode",
+                "base64_encode",
+                "bin2hex",
+                "encoding",
+                "hex",
+                "hex2bin",
+                "rfc4648"
+            ],
+            "support": {
+                "email": "info@paragonie.com",
+                "issues": "https://github.com/paragonie/constant_time_encoding/issues",
+                "source": "https://github.com/paragonie/constant_time_encoding"
+            },
+            "time": "2022-01-17T05:32:27+00:00"
+        },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v9.99.100",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+                "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">= 7"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*",
+                "vimeo/psalm": "^1"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "polyfill",
+                "pseudorandom",
+                "random"
+            ],
+            "support": {
+                "email": "info@paragonie.com",
+                "issues": "https://github.com/paragonie/random_compat/issues",
+                "source": "https://github.com/paragonie/random_compat"
+            },
+            "time": "2020-10-15T08:29:30+00:00"
+        },
+        {
+            "name": "php-amqplib/php-amqplib",
+            "version": "v3.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-amqplib/php-amqplib.git",
+                "reference": "e8aba06c4e1c467612f2d99304f672f2660e8492"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/e8aba06c4e1c467612f2d99304f672f2660e8492",
+                "reference": "e8aba06c4e1c467612f2d99304f672f2660e8492",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "ext-sockets": "*",
+                "php": "^7.1||^8.0",
+                "phpseclib/phpseclib": "^2.0|^3.0"
+            },
+            "conflict": {
+                "php": "7.4.0 - 7.4.1"
+            },
+            "replace": {
+                "videlalvaro/php-amqplib": "self.version"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "nategood/httpful": "^0.2.20",
+                "phpunit/phpunit": "^7.5|^9.5",
+                "squizlabs/php_codesniffer": "^3.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpAmqpLib\\": "PhpAmqpLib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Alvaro Videla",
+                    "role": "Original Maintainer"
+                },
+                {
+                    "name": "Raúl Araya",
+                    "email": "nubeiro@gmail.com",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "Luke Bakken",
+                    "email": "luke@bakken.io",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "Ramūnas Dronga",
+                    "email": "github@ramuno.lt",
+                    "role": "Maintainer"
+                }
+            ],
+            "description": "Formerly videlalvaro/php-amqplib.  This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.",
+            "homepage": "https://github.com/php-amqplib/php-amqplib/",
+            "keywords": [
+                "message",
+                "queue",
+                "rabbitmq"
+            ],
+            "support": {
+                "issues": "https://github.com/php-amqplib/php-amqplib/issues",
+                "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.1.2"
+            },
+            "time": "2022-01-18T17:08:01+00:00"
+        },
         {
             "name": "php-di/invoker",
             "version": "2.3.3",
@@ -1648,6 +1846,117 @@
             },
             "time": "2015-12-19T14:08:53+00:00"
         },
+        {
+            "name": "phpseclib/phpseclib",
+            "version": "3.0.13",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpseclib/phpseclib.git",
+                "reference": "1443ab79364eea48665fa8c09ac67f37d1025f7e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/1443ab79364eea48665fa8c09ac67f37d1025f7e",
+                "reference": "1443ab79364eea48665fa8c09ac67f37d1025f7e",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/constant_time_encoding": "^1|^2",
+                "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+                "php": ">=5.6.1"
+            },
+            "require-dev": {
+                "phing/phing": "~2.7",
+                "phpunit/phpunit": "^5.7|^6.0|^9.4",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "suggest": {
+                "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
+                "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
+                "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
+                "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "phpseclib/bootstrap.php"
+                ],
+                "psr-4": {
+                    "phpseclib3\\": "phpseclib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jim Wigginton",
+                    "email": "terrafrost@php.net",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Patrick Monnerat",
+                    "email": "pm@datasphere.ch",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Andreas Fischer",
+                    "email": "bantu@phpbb.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Hans-Jürgen Petrich",
+                    "email": "petrich@tronic-media.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "graham@alt-three.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
+            "homepage": "http://phpseclib.sourceforge.net",
+            "keywords": [
+                "BigInteger",
+                "aes",
+                "asn.1",
+                "asn1",
+                "blowfish",
+                "crypto",
+                "cryptography",
+                "encryption",
+                "rsa",
+                "security",
+                "sftp",
+                "signature",
+                "signing",
+                "ssh",
+                "twofish",
+                "x.509",
+                "x509"
+            ],
+            "support": {
+                "issues": "https://github.com/phpseclib/phpseclib/issues",
+                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.13"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/terrafrost",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.patreon.com/phpseclib",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-01-30T08:50:05+00:00"
+        },
         {
             "name": "psr/cache",
             "version": "3.0.0",
diff --git a/server/src/Action/AbstractAction.php b/server/src/Action/AbstractAction.php
index 894633a79f047e80993c76798eaed7fd30dc81a0..97d759652de9587d40658990d75d8678c91766cf 100644
--- a/server/src/Action/AbstractAction.php
+++ b/server/src/Action/AbstractAction.php
@@ -82,7 +82,7 @@ abstract class AbstractAction
         }
     }
 
-    private function isAdmin(array $adminRoles, $roles)
+    protected function isAdmin(array $adminRoles, $roles)
     {
         $admin = false;
         for ($i = 0; $i < count($adminRoles); $i++) {
diff --git a/server/src/Action/ArchiveAction.php b/server/src/Action/ArchiveAction.php
deleted file mode 100644
index b1c8548733f2a75f9efcb98e05bb223e9fae808d..0000000000000000000000000000000000000000
--- a/server/src/Action/ArchiveAction.php
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Action;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Slim\Exception\HttpBadRequestException;
-use Slim\Exception\HttpNotFoundException;
-use Slim\Exception\HttpInternalServerErrorException;
-use Doctrine\ORM\EntityManagerInterface;
-use Nyholm\Psr7\Factory\Psr17Factory;
-use App\Search\DBALConnectionFactory;
-use App\Search\Query\AnisQueryBuilder;
-use App\Search\Response\IResponseFactory;
-use App\Search\SearchException;
-
-/**
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action
- */
-final class ArchiveAction extends AbstractAction
-{
-    /**
-     * Contains anis-server data path
-     *
-     * @var string
-     */
-    private $dataPath;
-
-    /**
-     * @var DBALConnectionFactory
-     */
-    private $connectionFactory;
-
-    /**
-     * @var AnisQueryBuilder
-     */
-    private $anisQueryBuilder;
-
-    /**
-     * Contains settings to handle Json Web Token (app/settings.php)
-     *
-     * @var array
-     */
-    private $settings;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param EntityManagerInterface $em Doctrine       Entity Manager Interface
-     * @param DBALConnectionFactory  $connectionFactory Factory used to construct connection to business database
-     * @param AnisQueryBuilder       $anisQueryBuilder  Object used to wrap the Doctrine DBAL Query Builder
-     * @param IResponseFactory       $responseFactory   Contains the factory used to return formatted response
-     * @param array                  $settings          Settings about token
-     */
-    public function __construct(
-        EntityManagerInterface $em,
-        string $dataPath,
-        DBALConnectionFactory $connectionFactory,
-        AnisQueryBuilder $anisQueryBuilder,
-        array $settings
-    ) {
-        parent::__construct($em);
-        $this->dataPath = $dataPath;
-        $this->connectionFactory = $connectionFactory;
-        $this->anisQueryBuilder = $anisQueryBuilder;
-        $this->settings = $settings;
-    }
-
-    /**
-     * `GET` Returns the file found
-     *
-     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
-     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
-     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        if ($request->getMethod() === OPTIONS) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        // Search the correct dataset with primary key
-        $datasetName = $args['dname'];
-        $dataset = $this->em->find('App\Entity\Dataset', $datasetName);
-
-        // If dataset is not found 404
-        if (is_null($dataset)) {
-            throw new HttpNotFoundException(
-                $request,
-                'Dataset with name ' . $datasetName . ' is not found'
-            );
-        }
-
-        // If dataset is private and authorization enabled
-        if (!$dataset->getPublic() && boolval($this->settings['enabled'])) {
-            $this->verifyDatasetAuthorization(
-                $request,
-                $dataset->getName(),
-                explode(',', $this->settings['admin_roles'])
-            );
-        }
-
-        $queryParams = $request->getQueryParams();
-
-        // The parameter "a" is mandatory
-        if (!array_key_exists('a', $queryParams)) {
-            throw new HttpBadRequestException(
-                $request,
-                'Param a is required for this request'
-            );
-        }
-
-        try {
-            // Configure the Anis Query Builder
-            $connection = $this->connectionFactory->create($dataset->getSurvey()->getDatabase());
-            $this->anisQueryBuilder->setDoctrineQueryBuilder($connection->createQueryBuilder());
-            $this->anisQueryBuilder->setDatasetSelected($dataset);
-
-            // Build the SQL request
-            $this->anisQueryBuilder->build($queryParams);
-        } catch (SearchException $e) {
-            throw new HttpBadRequestException(
-                $request,
-                $e->getMessage()
-            );
-        }
-
-        $zipFile = '/tmp/archive_' . $dataset->getName() . '_' . (new \DateTime())->format('Y-m-d\TH:i:s') . '.zip';
-        $zip = new \ZipArchive();
-
-        if ($zip->open($zipFile, \ZipArchive::CREATE) !== true) {
-            throw new HttpInternalServerErrorException(
-                $request,
-                'Unable to open the file ' . $zipFile
-            );
-        }
-
-        // Attributes with search_flag = File
-        $attributesSelected = $this->anisQueryBuilder->getAttributesSelected();
-        $stmt = $this->anisQueryBuilder->getDoctrineQueryBuilder()->execute();
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
-            foreach ($attributesSelected as $attribute) {
-                $attributeLabel = $attribute->getLabel();
-                $filePath = $this->dataPath . $dataset->getFullDataPath() . DIRECTORY_SEPARATOR . $row[$attributeLabel];
-                if (file_exists($filePath) && is_file($filePath)) {
-                    $zip->addFile($filePath, $row[$attributeLabel]);
-                }
-            }
-        }
-
-        $zip->close();
-
-        // Stream created ZIP file
-        $psr17Factory = new Psr17Factory();
-        $stream = $psr17Factory->createStreamFromFile($zipFile, 'r');
-
-        return $response->withBody($stream)
-            ->withHeader('Content-Disposition', 'attachment; filename=' . basename($zipFile) . ';')
-            ->withHeader('Content-Type', mime_content_type($zipFile))
-            ->withHeader('Content-Length', filesize($zipFile));
-    }
-}
diff --git a/server/src/Action/DatasetListByInstanceAction.php b/server/src/Action/DatasetListByInstanceAction.php
index 127477909c668ea636c056f05ea4a085809695b8..efa9f06a45fd57b7bc3bcd167d2630bd5cfe8625 100644
--- a/server/src/Action/DatasetListByInstanceAction.php
+++ b/server/src/Action/DatasetListByInstanceAction.php
@@ -81,8 +81,9 @@ final class DatasetListByInstanceAction extends AbstractAction
                     // If user is not connected return public datasets
                     $qb->andWhere($qb->expr()->eq('d.public', 'true'));
                 } else {
+                    $adminRoles = explode(',', $this->settings['admin_roles']);
                     $roles = $token->realm_access->roles;
-                    if (!in_array($this->settings['admin_roles'], $roles)) {
+                    if (!$this->isAdmin($adminRoles, $roles)) {
                         // If user is not an admin return public datasets
                         // And returns datasets from user's groups
                         $qb->andWhere($qb->expr()->eq('d.public', 'true'));
diff --git a/server/src/Action/DownloadArchiveAction.php b/server/src/Action/DownloadArchiveAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..b514baae074461c85c34952de4be6ea0fc573f51
--- /dev/null
+++ b/server/src/Action/DownloadArchiveAction.php
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Doctrine\ORM\EntityManagerInterface;
+use Slim\Exception\HttpNotFoundException;
+use Nyholm\Psr7\Factory\Psr17Factory;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class DownloadArchiveAction extends AbstractAction
+{
+    /**
+     * Contains anis-server data path
+     *
+     * @var string
+     */
+    private $dataPath;
+
+    /**
+     * Contains settings to handle Json Web Token
+     *
+     * @var array
+     */
+    private $settings;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em       Doctrine Entity Manager Interface
+     * @param string                 $dataPath Contains anis-server data path
+     * @param array                  $settings Settings about token
+     */
+    public function __construct(EntityManagerInterface $em, string $dataPath, array $settings)
+    {
+        parent::__construct($em);
+        $this->dataPath = $dataPath;
+        $this->settings = $settings;
+    }
+
+    /**
+     * `GET` Returns the file found
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        // Search the correct dataset with primary key
+        $dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
+
+        // If dataset is not found 404
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['dname'] . ' is not found'
+            );
+        }
+
+        // If dataset is private and authorization enabled
+        if (!$dataset->getPublic() && boolval($this->settings['enabled'])) {
+            $this->verifyDatasetAuthorization(
+                $request,
+                $dataset->getName(),
+                explode(',', $this->settings['admin_roles'])
+            );
+        }
+
+        // Search the file
+        $archiveId = $args['id'];
+        $filePath = $this->dataPath . '/ARCHIVE/' . $archiveId . '.zip';
+
+        // If the file not found 404
+        if (!file_exists($filePath)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Archive file with name ' . $archiveId . '.zip is not found'
+            );
+        }
+
+        // If the file found so stream it
+        $psr17Factory = new Psr17Factory();
+        $stream = $psr17Factory->createStreamFromFile($filePath, 'r');
+
+        return $response->withBody($stream)
+            ->withHeader('Content-Disposition', 'attachment; filename=' . basename($filePath) . ';')
+            ->withHeader('Content-Type', mime_content_type($filePath))
+            ->withHeader('Content-Length', filesize($filePath));
+    }
+}
diff --git a/server/src/Action/DownloadResultAction.php b/server/src/Action/DownloadResultAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..2413fa29744c139ec1be2e0097ff3d6335ac4581
--- /dev/null
+++ b/server/src/Action/DownloadResultAction.php
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Doctrine\ORM\EntityManagerInterface;
+use Slim\Exception\HttpNotFoundException;
+use Nyholm\Psr7\Factory\Psr17Factory;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class DownloadResultAction extends AbstractAction
+{
+    /**
+     * Contains anis-server data path
+     *
+     * @var string
+     */
+    private $dataPath;
+
+    /**
+     * Contains settings to handle Json Web Token
+     *
+     * @var array
+     */
+    private $settings;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em       Doctrine Entity Manager Interface
+     * @param string                 $dataPath Contains anis-server data path
+     * @param array                  $settings Settings about token
+     */
+    public function __construct(EntityManagerInterface $em, string $dataPath, array $settings)
+    {
+        parent::__construct($em);
+        $this->dataPath = $dataPath;
+        $this->settings = $settings;
+    }
+
+    /**
+     * `GET` Returns the file found
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        // Search the correct dataset with primary key
+        $dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
+
+        // If dataset is not found 404
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['dname'] . ' is not found'
+            );
+        }
+
+        // If dataset is private and authorization enabled
+        if (!$dataset->getPublic() && boolval($this->settings['enabled'])) {
+            $this->verifyDatasetAuthorization(
+                $request,
+                $dataset->getName(),
+                explode(',', $this->settings['admin_roles'])
+            );
+        }
+
+        // Search the file
+        $fileId = $args['id'];
+        $filePath = $this->dataPath . '/RESULT/' . $fileId;
+
+        // If the file not found 404
+        if (!file_exists($filePath)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Result file with name ' . $fileId . ' is not found'
+            );
+        }
+
+        // If the file found so stream it
+        $psr17Factory = new Psr17Factory();
+        $stream = $psr17Factory->createStreamFromFile($filePath, 'r');
+
+        return $response->withBody($stream)
+            ->withHeader('Content-Disposition', 'attachment; filename=' . basename($filePath) . ';')
+            ->withHeader('Content-Type', mime_content_type($filePath))
+            ->withHeader('Content-Length', filesize($filePath));
+    }
+}
diff --git a/server/src/Action/IsArchiveAvailableAction.php b/server/src/Action/IsArchiveAvailableAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..b993e1d59446f3bee241a096da1b867c516dc11b
--- /dev/null
+++ b/server/src/Action/IsArchiveAvailableAction.php
@@ -0,0 +1,84 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Doctrine\ORM\EntityManagerInterface;
+use Slim\Exception\HttpNotFoundException;
+use Nyholm\Psr7\Factory\Psr17Factory;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class IsArchiveAvailableAction extends AbstractAction
+{
+    /**
+     * Contains anis-server data path
+     *
+     * @var string
+     */
+    private $dataPath;
+
+    /**
+     * Contains settings to handle Json Web Token
+     *
+     * @var array
+     */
+    private $settings;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em       Doctrine Entity Manager Interface
+     * @param string                 $dataPath Contains anis-server data path
+     * @param array                  $settings Settings about token
+     */
+    public function __construct(EntityManagerInterface $em, string $dataPath, array $settings)
+    {
+        parent::__construct($em);
+        $this->dataPath = $dataPath;
+        $this->settings = $settings;
+    }
+
+    /**
+     * `GET` Returns the file found
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        $archiveId = $args['id'];
+
+        // Search the file
+        $filePath = $this->dataPath . '/ARCHIVE/' . $archiveId . '.zip';
+
+        $isAvailable = false;
+        if (file_exists($filePath)) {
+            $isAvailable = true;
+        }
+
+        $payload = json_encode(array('archive_is_available' => $isAvailable));
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/server/src/Action/IsResultAvailableAction.php b/server/src/Action/IsResultAvailableAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..64bba6e7ca1a12e3e023bd655397d7ab3ab036e0
--- /dev/null
+++ b/server/src/Action/IsResultAvailableAction.php
@@ -0,0 +1,84 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Doctrine\ORM\EntityManagerInterface;
+use Slim\Exception\HttpNotFoundException;
+use Nyholm\Psr7\Factory\Psr17Factory;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class IsResultAvailableAction extends AbstractAction
+{
+    /**
+     * Contains anis-server data path
+     *
+     * @var string
+     */
+    private $dataPath;
+
+    /**
+     * Contains settings to handle Json Web Token
+     *
+     * @var array
+     */
+    private $settings;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em       Doctrine Entity Manager Interface
+     * @param string                 $dataPath Contains anis-server data path
+     * @param array                  $settings Settings about token
+     */
+    public function __construct(EntityManagerInterface $em, string $dataPath, array $settings)
+    {
+        parent::__construct($em);
+        $this->dataPath = $dataPath;
+        $this->settings = $settings;
+    }
+
+    /**
+     * `GET` Returns the file found
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        $fileId = $args['id'];
+
+        // Search the file
+        $filePath = $this->dataPath . '/RESULT/' . $fileId;
+
+        $isAvailable = false;
+        if (file_exists($filePath)) {
+            $isAvailable = true;
+        }
+
+        $payload = json_encode(array('file_is_available' => $isAvailable));
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/server/src/Action/StartTaskCreateArchiveAction.php b/server/src/Action/StartTaskCreateArchiveAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd2d02711ada3bd236484a3f23b41ea0d5fbec2a
--- /dev/null
+++ b/server/src/Action/StartTaskCreateArchiveAction.php
@@ -0,0 +1,136 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpInternalServerErrorException;
+use Doctrine\ORM\EntityManagerInterface;
+use Nyholm\Psr7\Factory\Psr17Factory;
+use App\Search\DBALConnectionFactory;
+use App\Search\Query\AnisQueryBuilder;
+use App\Search\Response\IResponseFactory;
+use App\Search\SearchException;
+use PhpAmqpLib\Connection\AbstractConnection;
+use PhpAmqpLib\Connection\AMQPStreamConnection;
+use PhpAmqpLib\Message\AMQPMessage;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class StartTaskCreateArchiveAction extends AbstractAction
+{
+    /**
+     * Contains RabbitMQ connection socket
+     *
+     * @var AbstractConnection
+     */
+    private $rmq;
+
+    /**
+     * Contains settings to handle Json Web Token (app/settings.php)
+     *
+     * @var array
+     */
+    private $settings;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em       Doctrine Entity Manager Interface
+     * @param AbstractConnection     $rmq      RabbitMQ connection socket
+     * @param array                  $settings Settings about token
+     */
+    public function __construct(
+        EntityManagerInterface $em,
+        AbstractConnection $rmq,
+        array $settings
+    ) {
+        parent::__construct($em);
+        $this->rmq = $rmq;
+        $this->settings = $settings;
+    }
+
+    /**
+     * `GET` Starts an asynchronous task, through rabbitmq, to build an archive
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        // Search the correct dataset with primary key
+        $datasetName = $args['dname'];
+        $dataset = $this->em->find('App\Entity\Dataset', $datasetName);
+
+        // If dataset is not found 404
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $datasetName . ' is not found'
+            );
+        }
+
+        // If dataset is private and authorization enabled
+        $token = '';
+        if (!$dataset->getPublic() && boolval($this->settings['enabled'])) {
+            $this->verifyDatasetAuthorization(
+                $request,
+                $dataset->getName(),
+                explode(',', $this->settings['admin_roles'])
+            );
+            $token = $request->getHeader('Authorization')[0];
+        }
+
+        $queryParams = $request->getQueryParams();
+
+        // The parameter "a" is mandatory
+        if (!array_key_exists('a', $queryParams)) {
+            throw new HttpBadRequestException(
+                $request,
+                'Param a is required for this request'
+            );
+        }
+
+        // Create the name of the future archive
+        $archiveName = 'archive_' . $dataset->getName() . '_' . (new \DateTime())->format('Y-m-d\TH:i:s') . '.zip';
+        $archiveId = uniqid();
+
+        // Publish message in the archive queue
+        $channel = $this->rmq->channel();
+        $channel->queue_declare('archive', false, false, false, false);
+        $msg = new AMQPMessage(json_encode(array(
+            'archive_id' => $archiveId,
+            'dataset_name' => $datasetName,
+            'query' => $request->getUri()->getQuery(),
+            'param_a' => $queryParams['a'],
+            'token' => $token
+        )));
+        $channel->basic_publish($msg, '', 'archive');
+
+        // Just returns the future archive name
+        $payload = json_encode(array('archive_name' => $archiveName, 'archive_id' => $archiveId));
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/server/src/Action/StartTaskCreateResultAction.php b/server/src/Action/StartTaskCreateResultAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..329e00c0331453c1352f824eabf5c8086663b050
--- /dev/null
+++ b/server/src/Action/StartTaskCreateResultAction.php
@@ -0,0 +1,147 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpInternalServerErrorException;
+use Doctrine\ORM\EntityManagerInterface;
+use Nyholm\Psr7\Factory\Psr17Factory;
+use App\Search\DBALConnectionFactory;
+use App\Search\Query\AnisQueryBuilder;
+use App\Search\Response\IResponseFactory;
+use App\Search\SearchException;
+use PhpAmqpLib\Connection\AbstractConnection;
+use PhpAmqpLib\Connection\AMQPStreamConnection;
+use PhpAmqpLib\Message\AMQPMessage;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class StartTaskCreateResultAction extends AbstractAction
+{
+    /**
+     * Contains RabbitMQ connection socket
+     *
+     * @var AbstractConnection
+     */
+    private $rmq;
+
+    /**
+     * Contains settings to handle Json Web Token (app/settings.php)
+     *
+     * @var array
+     */
+    private $settings;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em       Doctrine Entity Manager Interface
+     * @param AbstractConnection     $rmq      RabbitMQ connection socket
+     * @param array                  $settings Settings about token
+     */
+    public function __construct(
+        EntityManagerInterface $em,
+        AbstractConnection $rmq,
+        array $settings
+    ) {
+        parent::__construct($em);
+        $this->rmq = $rmq;
+        $this->settings = $settings;
+    }
+
+    /**
+     * `GET` Starts an asynchronous task, through rabbitmq, to build an archive
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        // Search the correct dataset with primary key
+        $datasetName = $args['dname'];
+        $dataset = $this->em->find('App\Entity\Dataset', $datasetName);
+
+        // If dataset is not found 404
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $datasetName . ' is not found'
+            );
+        }
+
+        // If dataset is private and authorization enabled
+        $token = '';
+        if (!$dataset->getPublic() && boolval($this->settings['enabled'])) {
+            $this->verifyDatasetAuthorization(
+                $request,
+                $dataset->getName(),
+                explode(',', $this->settings['admin_roles'])
+            );
+            $token = $request->getHeader('Authorization')[0];
+        }
+
+        $queryParams = $request->getQueryParams();
+
+        // The parameter "a" is mandatory
+        if (!array_key_exists('a', $queryParams)) {
+            throw new HttpBadRequestException(
+                $request,
+                'Param a is required for this request'
+            );
+        }
+
+        // Search extension
+        if ($queryParams['f'] === 'csv') {
+            $extension = '.csv';
+        } elseif ($queryParams['f'] === 'ascii') {
+            $extension = '.txt';
+        } elseif ($queryParams['f'] === 'votable') {
+            $extension = '.xml';
+        } else {
+            $extension = '.json';
+        }
+
+        // Create the name of the future archive
+        $fileName = 'result_' . $dataset->getName() . '_' . (new \DateTime())->format('Y-m-d\TH:i:s') . $extension;
+        $fileId = uniqid();
+
+        // Publish message in the archive queue
+        $channel = $this->rmq->channel();
+        $channel->queue_declare('archive', false, false, false, false);
+
+        $msg = new AMQPMessage(json_encode(array(
+            'file_id' => $fileId,
+            'dataset_name' => $datasetName,
+            'query' => $request->getUri()->getQuery(),
+            'token' => $token
+        )));
+        $channel->basic_publish($msg, '', 'result');
+
+        // Just returns the future archive name
+        $payload = json_encode(array('file_name' => $fileName, 'file_id' => $fileId));
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/tasks/.gitignore b/tasks/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9bb092ad02e650dc5a3f7f8c1dc8a05b0b262470
--- /dev/null
+++ b/tasks/.gitignore
@@ -0,0 +1,58 @@
+# compiled output
+dist
+/tmp
+/out-tsc
+var
+coverage
+data
+
+# Only exists if Bazel was run
+/bazel-out
+
+# dependencies
+node_modules
+
+# profiling files
+chrome-profiler-events*.json
+speed-measure-plugin*.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
+
+# Python stuff #
+################
+env
+eggs/
+.eggs/
+*.egg-info/
+*.egg
+__pycache__/
+*.pyc
diff --git a/tasks/Dockerfile b/tasks/Dockerfile
new file mode 100755
index 0000000000000000000000000000000000000000..848cfb600d1e9337dce1f480d0a738decaa49ab6
--- /dev/null
+++ b/tasks/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.8
+
+WORKDIR /project
+
+COPY requirements.txt ./
+COPY src src
+RUN pip install --no-cache-dir -r requirements.txt
+
+CMD ["python3.8", "src/anis_tasks/app.py"]
diff --git a/tasks/requirements.txt b/tasks/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1bb63938e72440bceb9408c5496f18a608ae2b71
--- /dev/null
+++ b/tasks/requirements.txt
@@ -0,0 +1,9 @@
+certifi==2021.10.8
+charset-normalizer==2.0.12
+decorator==5.1.1
+idna==3.3
+pika==1.2.0
+py==1.11.0
+requests==2.27.1
+retry==0.9.2
+urllib3==1.26.8
diff --git a/tasks/src/anis_tasks/app.py b/tasks/src/anis_tasks/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..5dcd1b275d1b211f412bc2350b1b630993ccc4e0
--- /dev/null
+++ b/tasks/src/anis_tasks/app.py
@@ -0,0 +1,44 @@
+# Standard library imports
+import logging, sys
+
+# Third party imports
+import pika
+from retry import retry
+
+# Local application imports
+from anis_tasks import utils, archive, result
+
+@retry(pika.exceptions.AMQPConnectionError, delay=5, jitter=(1, 3))
+def run():
+    try:
+        # Check config variables
+        utils.check_config()
+
+        # Connect to the rabbitMQ server
+        credentials = pika.PlainCredentials(utils.get_rmq_user(), utils.get_rmq_password())
+        connection = pika.BlockingConnection(pika.ConnectionParameters(host=utils.get_rmq_host(), port=utils.get_rmq_port(), credentials=credentials))
+        channel = connection.channel()
+
+        # Add archive task handler
+        channel.queue_declare(queue='archive')
+        channel.basic_consume(queue='archive', on_message_callback=archive.archive_handler, auto_ack=True)
+
+        # Add result task handler
+        channel.queue_declare(queue='result')
+        channel.basic_consume(queue='result', on_message_callback=result.result_handler, auto_ack=True)
+
+        # Start
+        logging.info("ANIS tasks started")
+        channel.start_consuming()
+    except utils.ConfigKeyNotFound as e:
+        logging.error("Config error")
+        logging.error(e)
+        return
+
+if __name__ == '__main__':
+    logging.basicConfig(
+        stream=sys.stderr,
+        level=logging.INFO,
+        format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s'
+    )
+    run()
\ No newline at end of file
diff --git a/tasks/src/anis_tasks/archive.py b/tasks/src/anis_tasks/archive.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e93e38974915857c1a2037c2814f2bf269cccc9
--- /dev/null
+++ b/tasks/src/anis_tasks/archive.py
@@ -0,0 +1,53 @@
+# Standard library imports
+import logging, json, os
+from zipfile import ZipFile
+
+# Local application imports
+from anis_tasks import utils
+
+def archive_handler(ch, method, properties, body):
+    logging.info("Processing a new archive message")
+
+    # Decode JSON
+    message = json.loads(body)
+
+    # Retrieve metadata information
+    dataset = utils.get_dataset(message["dataset_name"])
+    attributes = utils.get_attributes(message["dataset_name"])
+    attributes_selected = get_attributes_selected(attributes, message["param_a"])
+
+    # Retrieve data
+    data = utils.search_data(message["dataset_name"], message["query"], message["token"])
+
+    # create a ZipFile object
+    data_path = utils.get_data_path()
+    zip_path = data_path + "/ARCHIVE/" + message["archive_id"] + ".zip"
+    zip = ZipFile(zip_path + ".tmp", 'w')
+
+    # Search files
+    for row in data.json():
+        files_added = []
+        for attribute in attributes_selected:
+            attribute_label = attribute["label"]
+            file_path = utils.get_data_path() + dataset["full_data_path"] + "/" + str(row[attribute_label])
+            if (os.path.exists(file_path) and os.path.isfile(file_path) and file_path not in files_added):
+                # Adds file to the zip archive
+                files_added.append(file_path)
+                zip.write(file_path, row[attribute_label])
+
+    # close the Zip File
+    zip.close()
+
+    # Rename the tmp zip file with the correct archive name
+    os.rename(zip_path + ".tmp", zip_path)
+
+    logging.info("Zip created: " + zip_path)
+
+def get_attributes_selected(attributes, param_a):
+    attributes_selected = []
+    ids = param_a.split(";")
+    for attribute in attributes:
+        if (str(attribute["id"]) in ids):
+            attributes_selected.append(attribute)
+    
+    return attributes_selected
\ No newline at end of file
diff --git a/tasks/src/anis_tasks/result.py b/tasks/src/anis_tasks/result.py
new file mode 100644
index 0000000000000000000000000000000000000000..27ac73dfe913594e8aea32f5feb13ccc532c7a70
--- /dev/null
+++ b/tasks/src/anis_tasks/result.py
@@ -0,0 +1,31 @@
+# Standard library imports
+import logging, json, os
+
+# Local application imports
+from anis_tasks import utils
+
+def result_handler(ch, method, properties, body):
+    logging.info("Processing a new result message")
+
+    # Decode JSON
+    message = json.loads(body)
+
+    # Retrieve data
+    logging.info(message["token"])
+    data = utils.search_data(message["dataset_name"], message["query"], message["token"])
+
+    # create a File object
+    data_path = utils.get_data_path()
+    file_path = data_path + "/RESULT/" + message["file_id"]
+    file = open(file_path + ".tmp", "w")
+
+    # Write data
+    file.write(data.text)
+
+    # close the File
+    file.close()
+
+    # Rename the tmp file with the correct filename (id)
+    os.rename(file_path + ".tmp", file_path)
+
+    logging.info("File created: " + file_path)
diff --git a/tasks/src/anis_tasks/utils.py b/tasks/src/anis_tasks/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae43a76b3cce6464f101e372e553cd505916312d
--- /dev/null
+++ b/tasks/src/anis_tasks/utils.py
@@ -0,0 +1,125 @@
+# Standard library imports
+import os
+
+# Third party imports
+import requests
+
+def check_config():
+    """
+    Check Config variables
+    """
+    check_keys = {
+        'DATA_PATH',
+        'SERVER_URL',
+        'RMQ_HOST',
+        'RMQ_PORT',
+        'RMQ_USER',
+        'RMQ_PASSWORD'
+    }
+    for value in check_keys:
+        if value not in os.environ.keys():
+            raise ConfigKeyNotFound(value)
+
+def get_data_path():
+    """
+    Get ANIS data path
+    """
+    return os.environ['DATA_PATH']
+
+def get_server_url():
+    """
+    Get ANIS server URL
+    """
+    return os.environ['SERVER_URL']
+
+def get_rmq_host():
+    """
+    Get RabbitMQ hostname
+    """
+    return os.environ["RMQ_HOST"]
+
+def get_rmq_port():
+    """
+    Get RabbitMQ port
+    """
+    return os.environ["RMQ_PORT"]
+
+def get_rmq_user():
+    """
+    Get RabbitMQ user
+    """
+    return os.environ["RMQ_USER"]
+
+def get_rmq_password():
+    """
+    Get RabbitMQ password
+    """
+    return os.environ["RMQ_PASSWORD"]
+
+def get_dataset(dname):
+    server_url = os.environ["SERVER_URL"]
+
+    r = requests.get(server_url + "/dataset/" + dname)
+
+    if (r.status_code == 404):
+        raise DatasetNotFound(dname)
+    if (r.status_code == 500):
+        raise AnisServerError(r.json()["message"])
+
+    return r.json()
+
+def get_attributes(dname):
+    server_url = os.environ["SERVER_URL"]
+
+    r = requests.get(server_url + "/dataset/" + dname + "/attribute")
+
+    if (r.status_code == 404):
+        raise DatasetNotFound(dname)
+    if (r.status_code == 500):
+        raise AnisServerError(r.json()["message"])
+
+    return r.json()
+
+def search_data(dname, query, token):
+    server_url = os.environ["SERVER_URL"]
+
+    headers = {}
+
+    if (token):
+        headers = { "Authorization": token }
+
+    r = requests.get(server_url + "/search/" + dname + "?" + query, headers=headers)
+
+    if (r.status_code == 404):
+        raise DatasetNotFound(dname)
+    if (r.status_code == 500):
+        raise AnisServerError(r.json()["message"])
+
+    return r
+
+class ConfigKeyNotFound(Exception):
+    """
+    Config Key Not Found
+    """
+    def __init__(self, value):
+        Exception.__init__(self, value)
+        self.value = value
+
+    def __str__(self):
+        return f"{self.value} was not found in the environment variables"
+
+class DatasetNotFound(Exception):
+    def __init__(self, dname):
+        Exception.__init__(self, dname)
+        self.dname = dname
+
+    def __str__(self):
+        return f"Dataset {self.dname} was not found"
+
+class AnisServerError(Exception):
+    def __init__(self, message):
+        Exception.__init__(self, message)
+        self.message = message
+
+    def __str__(self):
+        return f"Anis-server error: {self.message}"
\ No newline at end of file