diff --git a/api/docker-compose.yml b/api/docker-compose.yml
index dee08d3392e474fc1124072cc287d6c2f342d72d..5a4acef400c6e73f17bde060650479a23a6a4318 100644
--- a/api/docker-compose.yml
+++ b/api/docker-compose.yml
@@ -1,11 +1,11 @@
 services:
   db:
-    image:  postgres
+    image:  postgres:14-alpine
     restart: always 
     ports:
       - "5454:5432"
     environment:
-       POSTGRES_PASSWORD: pass123
+      POSTGRES_PASSWORD: pass123
     volumes:
       - db-volume:/var/lib/postgres
     networks:
diff --git a/api/src/auth/auth.resolver.spec.ts b/api/src/auth/auth.resolver.spec.ts
index ca6c620bddc48e94292a126f95d39452caedc3a6..c109dbe8712230015317033214cd8f2870350c7e 100644
--- a/api/src/auth/auth.resolver.spec.ts
+++ b/api/src/auth/auth.resolver.spec.ts
@@ -2,11 +2,14 @@ import { getMockRes } from '@jest-mock/express';
 import { Test, TestingModule } from '@nestjs/testing';
 import { MockFunctionMetadata, ModuleMocker } from 'jest-mock';
 import LocalService from '../engine/connectors/local/main.connector';
-import { ENGINE_SERVICE } from '../engine/engine.constants';
+import {
+  ENGINE_MODULE_OPTIONS,
+  ENGINE_SERVICE,
+} from '../engine/engine.constants';
+import { User } from '../users/models/user.model';
 import { authConstants } from './auth-constants';
 import { AuthResolver } from './auth.resolver';
 import { AuthService } from './auth.service';
-import { User } from '../users/models/user.model';
 
 const moduleMocker = new ModuleMocker(global);
 
@@ -40,6 +43,12 @@ describe('AuthResolver', () => {
           provide: ENGINE_SERVICE,
           useClass: LocalService,
         },
+        {
+          provide: ENGINE_MODULE_OPTIONS,
+          useValue: {
+            type: 'DummyConnector',
+          },
+        },
         AuthResolver,
       ],
     })
@@ -71,7 +80,8 @@ describe('AuthResolver', () => {
   });
 
   it('logout', () => {
-    resolver.logout(res, user);
+    const request: any = jest.fn();
+    resolver.logout(request, res, user);
 
     expect(mockClearCookie.mock.calls[0][0]).toBe(authConstants.cookie.name);
   });
diff --git a/api/src/auth/auth.resolver.ts b/api/src/auth/auth.resolver.ts
index 228943e65bc8582c5a209e5d815523f1716b98ad..cfa8e714c43004d503eaf016e2f00a11503990d7 100644
--- a/api/src/auth/auth.resolver.ts
+++ b/api/src/auth/auth.resolver.ts
@@ -6,11 +6,15 @@ import {
 } from '@nestjs/common';
 import { ConfigService } from '@nestjs/config';
 import { Args, Mutation, Resolver } from '@nestjs/graphql';
-import { Response } from 'express';
+import { Response, Request } from 'express';
+import { GQLRequest } from '../common/decorators/gql-request.decoractor';
 import { GQLResponse } from '../common/decorators/gql-response.decoractor';
 import { parseToBoolean } from '../common/utilities';
-import { ENGINE_SERVICE } from '../engine/engine.constants';
-import { IEngineService } from '../engine/engine.interfaces';
+import {
+  ENGINE_MODULE_OPTIONS,
+  ENGINE_SERVICE,
+} from '../engine/engine.constants';
+import { IEngineOptions, IEngineService } from '../engine/engine.interfaces';
 import { User } from '../users/models/user.model';
 import { authConstants } from './auth-constants';
 import { AuthService } from './auth.service';
@@ -29,6 +33,8 @@ export class AuthResolver {
 
   constructor(
     @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService,
+    @Inject(ENGINE_MODULE_OPTIONS)
+    private readonly engineOptions: IEngineOptions,
     private readonly authService: AuthService,
     private readonly configService: ConfigService,
   ) {}
@@ -68,11 +74,23 @@ export class AuthResolver {
 
   @Mutation(() => Boolean)
   @UseGuards(JwtAuthGuard)
-  logout(@GQLResponse() res: Response, @CurrentUser() user: User): boolean {
-    if (user) this.logger.verbose(`${user.username} logged out`);
+  logout(
+    @GQLRequest() req: Request,
+    @GQLResponse() res: Response,
+    @CurrentUser() user: User,
+  ): boolean {
+    if (user) {
+      this.logger.verbose(`${user.username} logged out`);
+      try {
+        this.engineService.logout?.(req);
+      } catch (e) {
+        this.logger.debug(
+          `Service ${this.engineOptions.type} produce an error when logging out ${user.username}`,
+        );
+      }
+    }
 
     res.clearCookie(authConstants.cookie.name);
-    this.engineService.logout?.();
 
     return true;
   }
diff --git a/api/src/config/matomo.config.ts b/api/src/config/matomo.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e7fa269a4913509846da9d367fc607954c7ee52
--- /dev/null
+++ b/api/src/config/matomo.config.ts
@@ -0,0 +1,10 @@
+import { registerAs } from '@nestjs/config';
+import { parseToBoolean } from 'src/common/utilities';
+
+export default registerAs('matomo', () => {
+  return {
+    enabled: parseToBoolean(process.env.MATOMO_ENABLED, false),
+    urlBase: process.env.MATOMO_URL || undefined,
+    siteId: process.env.MATOMO_SITE_ID || undefined,
+  };
+});
diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts
index 454654e8e774617de4ef68b5e99f9e15a8b1918c..ad0e132cda9a53cb5075f101aef35ab04263802d 100644
--- a/api/src/engine/connectors/datashield/main.connector.ts
+++ b/api/src/engine/connectors/datashield/main.connector.ts
@@ -27,7 +27,7 @@ import { ListExperiments } from 'src/engine/models/experiment/list-experiments.m
 import { RawResult } from 'src/engine/models/result/raw-result.model';
 import {
   TableResult,
-  ThemeType,
+  TableStyle,
 } from 'src/engine/models/result/table-result.model';
 import { User } from 'src/users/models/user.model';
 import {
@@ -141,7 +141,7 @@ export default class DataShieldService implements IEngineService {
     const table = transformToTable.evaluate(data);
     return {
       ...table,
-      theme: ThemeType.NORMAL,
+      tableStyle: TableStyle.NORMAL,
     };
   }
 
@@ -205,6 +205,21 @@ export default class DataShieldService implements IEngineService {
     throw new NotImplementedException();
   }
 
+  async logout(request: Request): Promise<void> {
+    const user = request.user as User;
+    const cookie = [`sid=${user.extraFields['sid']}`, `user=${user.id}`].join(
+      ';',
+    );
+
+    const path = new URL('/logout', this.options.baseurl).href;
+
+    this.httpService.get(path, {
+      headers: {
+        cookie,
+      },
+    });
+  }
+
   async editExperient(
     id: string,
     expriment: ExperimentEditInput,
diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts
index 5ca4b79db61a06924af99aebb4e0ca44670de630..56f35ddcd2876a5cd6435cd3454c6fb414034923 100644
--- a/api/src/engine/connectors/exareme/converters.ts
+++ b/api/src/engine/connectors/exareme/converters.ts
@@ -227,6 +227,14 @@ export const dataToExperiment = (
       status: 'error',
       variables: [],
       domain: data['domain'] ?? '',
+      results: [
+        {
+          rawdata: {
+            type: 'text/plain+error',
+            data: 'Error when parsing experiment data from the Engine',
+          },
+        },
+      ],
       datasets: [],
       algorithm: {
         id: 'unknown',
diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts
index 9aa0115d8daa56a9ab36233472c86aa1c48025b9..a7af56efcc46540e8565e6130e5d7c70e420ee8e 100644
--- a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts
+++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts
@@ -1,7 +1,7 @@
 import { Experiment } from '../../../../models/experiment/experiment.model';
 import { HeatMapResult } from '../../../../models/result/heat-map-result.model';
 import handlers from '..';
-import { BarChartResult } from 'src/engine/models/result/bar-chart-result.model';
+import { BarChartResult } from '../../../../models/result/bar-chart-result.model';
 
 const createExperiment = (): Experiment => ({
   id: 'dummy-id',
@@ -84,7 +84,10 @@ describe('PCA result handler', () => {
     exp.results.forEach((it) => {
       if (it['matrix']) {
         const heatmap = it as HeatMapResult;
-        expect(heatmap.matrix).toEqual(data.eigen_vecs);
+        const matrix = data.eigen_vecs[0].map(
+          (_, i) => data.eigen_vecs.map((row) => row[i]), // reverse matrix as we want row-major order
+        );
+        expect(heatmap.matrix).toEqual(matrix);
         expect(heatmap.yAxis.categories).toEqual(exp.variables);
       }
     });
diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts
index 3278962199e512fa04c3a2b7c55bd1339447b6c5..64ff970285259fd69147fe2ff8fc0b3ca9eab668 100644
--- a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts
+++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts
@@ -42,7 +42,13 @@ export default class PCAHandler extends BaseHandler {
       },
     };
 
-    if (matrix) exp.results.push(heatMapChart);
+    if (matrix && matrix.length > 0) {
+      heatMapChart.matrix = matrix[0].map(
+        (_, i) => matrix.map((row) => row[i]), // reverse matrix as we want row-major order
+      );
+    }
+
+    if (heatMapChart.matrix) exp.results.push(heatMapChart);
 
     this.next?.handle(exp, data);
   }
diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c4cd6dcf6a9f3cd8c1d837b8a46b4d310842b874
--- /dev/null
+++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts
@@ -0,0 +1,159 @@
+import handlers from '..';
+import { Experiment } from '../../../../models/experiment/experiment.model';
+import AnovaOneWayHandler from './anova-one-way.handler';
+
+const createExperiment = (): Experiment => ({
+  id: 'dummy-id',
+  name: 'Testing purpose',
+  algorithm: {
+    id: 'Anova_OnEway',
+  },
+  datasets: ['desd-synthdata'],
+  domain: 'dementia',
+  variables: ['rightcerebralwhitematter'],
+  coVariables: ['ppmicategory'],
+  results: [],
+});
+
+describe('Anova oneway result handler', () => {
+  const anovaHandler = new AnovaOneWayHandler();
+  const data = {
+    x_label: 'Variable X',
+    y_label: 'Variable Y',
+    df_residual: 1424.0,
+    df_explained: 3.0,
+    ss_residual: 1941.1517872154072,
+    ss_explained: 23.52938815624377,
+    ms_residual: 1.3631683898984601,
+    ms_explained: 7.843129385414589,
+    p_value: 0.0006542139533101455,
+    f_stat: 5.753602741623733,
+    tuckey_test: [
+      {
+        groupA: 'GENPD',
+        groupB: 'HC',
+        meanA: 10.200898765432095,
+        meanB: 10.50253333333334,
+        diff: -0.3016345679012442,
+        se: 0.11017769051976001,
+        t_stat: -2.737710025308137,
+        p_tuckey: 0.03178790563153744,
+      },
+      {
+        groupA: 'GENPD',
+        groupB: 'PD',
+        meanA: 10.200898765432095,
+        meanB: 10.530083456790125,
+        diff: -0.3291846913580301,
+        se: 0.10048653456497285,
+        t_stat: -3.2759084864767125,
+        p_tuckey: 0.005936908999390811,
+      },
+      {
+        groupA: 'GENPD',
+        groupB: 'PRODROMA',
+        meanA: 10.200898765432095,
+        meanB: 10.161453333333334,
+        diff: 0.039445432098760946,
+        se: 0.1534957169892615,
+        t_stat: 0.2569806693793321,
+        p_tuckey: 0.9,
+      },
+      {
+        groupA: 'HC',
+        groupB: 'PD',
+        meanA: 10.50253333333334,
+        meanB: 10.530083456790125,
+        diff: -0.02755012345678587,
+        se: 0.07353521425604895,
+        t_stat: -0.37465211375949203,
+        p_tuckey: 0.9,
+      },
+      {
+        groupA: 'HC',
+        groupB: 'PRODROMA',
+        meanA: 10.50253333333334,
+        meanB: 10.161453333333334,
+        diff: 0.34108000000000516,
+        se: 0.13737110045731235,
+        t_stat: 2.4829094246500176,
+        p_tuckey: 0.0630887851749381,
+      },
+      {
+        groupA: 'PD',
+        groupB: 'PRODROMA',
+        meanA: 10.530083456790125,
+        meanB: 10.161453333333334,
+        diff: 0.368630123456791,
+        se: 0.1297275582960786,
+        t_stat: 2.8415714309172655,
+        p_tuckey: 0.02355122851783331,
+      },
+    ],
+    min_per_group: [
+      {
+        GENPD: 7.2276,
+        HC: 7.2107,
+        PD: 7.0258,
+        PRODROMA: 6.3771,
+      },
+    ],
+    max_per_group: [
+      {
+        GENPD: 13.7312,
+        HC: 14.52,
+        PD: 14.4812,
+        PRODROMA: 12.3572,
+      },
+    ],
+    ci_info: {
+      sample_stds: {
+        GENPD: 1.2338388511229372,
+        HC: 1.1276421260632183,
+        PD: 1.16245855322075,
+        PRODROMA: 1.197046185656396,
+      },
+      means: {
+        GENPD: 10.200898765432095,
+        HC: 10.50253333333334,
+        PD: 10.530083456790125,
+        PRODROMA: 10.161453333333334,
+      },
+      'm-s': {
+        GENPD: 8.967059914309157,
+        HC: 9.374891207270121,
+        PD: 9.367624903569375,
+        PRODROMA: 8.964407147676939,
+      },
+      'm+s': {
+        GENPD: 11.434737616555033,
+        HC: 11.630175459396558,
+        PD: 11.692542010010875,
+        PRODROMA: 11.35849951898973,
+      },
+    },
+  };
+
+  it('Test anova 1 way handler', () => {
+    const exp = createExperiment();
+    const table1 = anovaHandler.getSummaryTable(data, exp.coVariables[0]);
+    const table2 = anovaHandler.getTuckeyTable(data);
+    const meanPlot = anovaHandler.getMeanPlot(data);
+
+    handlers(exp, data);
+
+    expect(exp.results.length).toBeGreaterThanOrEqual(3);
+    expect(exp.results).toContainEqual(table1);
+    expect(exp.results).toContainEqual(table2);
+    expect(exp.results).toContainEqual(meanPlot);
+
+    expect(table1.data[0].length).toEqual(6);
+    expect(table2.headers.length).toEqual(8);
+    expect(table2.data).toBeTruthy();
+
+    expect(meanPlot.pointCIs.length).toBeGreaterThan(1);
+    expect(meanPlot.name).toEqual(
+      `Mean Plot: ${data.y_label} ~ ${data.x_label}`,
+    );
+  });
+});
diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c8d9a6750785d57df2f2637572d306cd313935ee
--- /dev/null
+++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts
@@ -0,0 +1,114 @@
+import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata'
+import { MeanChartResult } from 'src/engine/models/result/means-chart-result.model';
+import { Experiment } from '../../../../models/experiment/experiment.model';
+import {
+  TableResult,
+  TableStyle,
+} from '../../../../models/result/table-result.model';
+import BaseHandler from '../base.handler';
+
+export default class AnovaOneWayHandler extends BaseHandler {
+  private static readonly tuckeyTransform = jsonata(`
+    {
+        "name": 'Tuckey Honest Significant Differences',
+        "headers": [
+            {"name": 'A', "type": 'string'},
+            {"name": 'B', "type": 'string'},
+            {"name": 'Mean A', "type": 'string'},
+            {"name": 'Mean B', "type": 'string'},
+            {"name": 'Diff', "type": 'string'},
+            {"name": 'Standard error', "type": 'string'},
+            {"name": 'T value', "type": 'string'},
+            {"name": 'P value', "type": 'string'}     
+        ],
+        "data": tuckey_test.[$.groupA, $.groupB, $.meanA, $.meanB, $.diff, $.se, $.t_stat, $.p_tuckey]
+    }
+  `);
+
+  private static readonly meanPlotTransform = jsonata(`
+  (
+    $cats:= $keys(ci_info.means);
+    {
+    "name": "Mean Plot: " & y_label & ' ~ ' & x_label,
+    "xAxis": {
+        "label": x_label,
+        "categories": $cats
+    },
+    "yAxis": {
+        "label": '95% CI: ' & y_label
+    },
+    "pointCIs": $cats.[{
+        "min": $lookup($$.ci_info.'m-s', $),
+        "mean": $lookup($$.ci_info.means, $),
+        "max": $lookup($$.ci_info.'m+s', $)
+    }]
+  })
+  `);
+
+  canHandle(algorithm: string): boolean {
+    return algorithm.toLocaleLowerCase() === 'anova_oneway';
+  }
+
+  getTuckeyTable(data: unknown): TableResult | undefined {
+    const tableData = AnovaOneWayHandler.tuckeyTransform.evaluate(data);
+
+    if (!tableData) return undefined;
+
+    const tableResult: TableResult = {
+      ...tableData,
+      tableStyle: TableStyle.NORMAL,
+    } as unknown as TableResult;
+
+    return tableResult;
+  }
+
+  getSummaryTable(data: unknown, varname: string): TableResult | undefined {
+    const tableSummary: TableResult = {
+      name: 'Annova summary',
+      tableStyle: TableStyle.NORMAL,
+      headers: ['', 'DF', 'SS', 'MS', 'F ratio', 'P value'].map((name) => ({
+        name,
+        type: 'string',
+      })),
+      data: [
+        [
+          varname,
+          data['df_explained'],
+          data['ss_explained'],
+          data['ms_explained'],
+          data['p_value'],
+          data['f_stat'],
+        ],
+        [
+          'Residual',
+          data['df_residual'],
+          data['ss_residual'],
+          data['ms_residual'],
+          '',
+          '',
+        ],
+      ],
+    };
+
+    return tableSummary;
+  }
+
+  getMeanPlot(data: unknown): MeanChartResult {
+    return AnovaOneWayHandler.meanPlotTransform.evaluate(data);
+  }
+
+  handle(exp: Experiment, data: unknown): void {
+    if (!this.canHandle(exp.algorithm.id)) return super.handle(exp, data);
+
+    const summaryTable = this.getSummaryTable(data, exp.coVariables[0]);
+    if (summaryTable) exp.results.push(summaryTable);
+
+    const tuckeyTable = this.getTuckeyTable(data);
+    if (tuckeyTable) exp.results.push(tuckeyTable);
+
+    const meanPlot = this.getMeanPlot(data);
+    if (meanPlot && meanPlot.pointCIs) exp.results.push(meanPlot);
+
+    super.handle(exp, data); // continue request
+  }
+}
diff --git a/api/src/engine/connectors/exareme/handlers/index.ts b/api/src/engine/connectors/exareme/handlers/index.ts
index f9c6c47a8a5dd4dd9b12a817fe77054baf9e85d5..2d7d607d5dac7ebc553536c30f1a3b4c2acd6514 100644
--- a/api/src/engine/connectors/exareme/handlers/index.ts
+++ b/api/src/engine/connectors/exareme/handlers/index.ts
@@ -1,4 +1,5 @@
 import { Experiment } from '../../../../engine/models/experiment/experiment.model';
+import AnovaOneWayHandler from './algorithms/anova-one-way.handler';
 import AreaHandler from './algorithms/area.handler';
 import DescriptiveHandler from './algorithms/descriptive.handler';
 import HeatMapHandler from './algorithms/heat-map.handler';
@@ -12,6 +13,7 @@ start
   .setNext(new AreaHandler())
   .setNext(new DescriptiveHandler())
   .setNext(new HeatMapHandler())
+  .setNext(new AnovaOneWayHandler())
   .setNext(new PCAHandler())
   .setNext(new RawHandler()); // should be last handler as it works as a fallback (if other handlers could not process the results)
 
diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts
index 8d25f9ffd2b3eba957ee2bcbe73a7f704841b38a..02f28ae18223e74286b62b83a5e1aca03ce577d7 100644
--- a/api/src/engine/connectors/exareme/main.connector.ts
+++ b/api/src/engine/connectors/exareme/main.connector.ts
@@ -5,6 +5,7 @@ import {
   HttpStatus,
   Inject,
   Injectable,
+  InternalServerErrorException,
 } from '@nestjs/common';
 import { AxiosRequestConfig } from 'axios';
 import { Request } from 'express';
@@ -45,7 +46,6 @@ type Headers = Record<string, string>;
 
 @Injectable()
 export default class ExaremeService implements IEngineService {
-  headers = {};
   constructor(
     @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions,
     private readonly httpService: HttpService,
@@ -75,9 +75,7 @@ export default class ExaremeService implements IEngineService {
       this.options.baseurl + `experiments${isTransient ? '/transient' : ''}`;
 
     const resultAPI = await firstValueFrom(
-      this.post<ExperimentData>(request, path, form, {
-        headers: this.headers,
-      }),
+      this.post<ExperimentData>(request, path, form),
     );
 
     return dataToExperiment(resultAPI.data);
@@ -141,11 +139,7 @@ export default class ExaremeService implements IEngineService {
     const path = this.options.baseurl + `experiments/${id}`;
 
     try {
-      await firstValueFrom(
-        this.delete(request, path, {
-          headers: this.headers,
-        }),
-      );
+      await firstValueFrom(this.delete(request, path));
       return {
         id: id,
       };
@@ -158,11 +152,7 @@ export default class ExaremeService implements IEngineService {
     const path = this.options.baseurl + 'pathologies';
 
     try {
-      const data = await firstValueFrom(
-        this.get<Pathology[]>(request, path, {
-          headers: this.headers,
-        }),
-      );
+      const data = await firstValueFrom(this.get<Pathology[]>(request, path));
 
       return (
         data?.data
@@ -192,20 +182,30 @@ export default class ExaremeService implements IEngineService {
 
   async getActiveUser(request: Request): Promise<User> {
     const path = this.options.baseurl + 'activeUser';
-
     const response = await firstValueFrom(this.get<string>(request, path));
 
-    return transformToUser.evaluate(response.data);
+    try {
+      return transformToUser.evaluate(response.data);
+    } catch (e) {
+      new InternalServerErrorException('Cannot parse user data from Engine', e);
+    }
   }
 
   async updateUser(request: Request): Promise<User> {
     const path = this.options.baseurl + 'activeUser/agreeNDA';
-
-    this.post<string>(request, path, request.body).pipe(
-      map((response) => response.data),
+    const response = await firstValueFrom(
+      this.post<string>(request, path, {
+        agreeNDA: true,
+      }),
     );
 
-    return this.getActiveUser(request);
+    try {
+      return transformToUser.evaluate(response.data);
+    } catch (e) {
+      throw new InternalServerErrorException(
+        'Error when trying to parse user data from the engine',
+      );
+    }
   }
 
   getAlgorithmsREST(request: Request): Observable<string> {
@@ -256,7 +256,7 @@ export default class ExaremeService implements IEngineService {
   };
 
   private getHeadersFromRequest(request: Request): Headers {
-    if (!request || request.headers) return {};
+    if (!request || !request.headers) return {};
 
     return request.headers as Headers;
   }
diff --git a/api/src/engine/connectors/local/main.connector.ts b/api/src/engine/connectors/local/main.connector.ts
index d46e1ae80105b5ca1e8a1d073707862631152543..600fcc9824046a54a017b76ca27033a0b15912de 100644
--- a/api/src/engine/connectors/local/main.connector.ts
+++ b/api/src/engine/connectors/local/main.connector.ts
@@ -11,7 +11,7 @@ import { ListExperiments } from 'src/engine/models/experiment/list-experiments.m
 import { User } from 'src/users/models/user.model';
 
 export default class LocalService implements IEngineService {
-  login(): User | Promise<User> {
+  async login(): Promise<User> {
     return {
       id: '1',
       username: 'LocalServiceUser',
diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts
index b8b21e9ab52e82ed911ee044e080ec401146f861..1a93078d70be9ded4272022f0dff5b85d2017e33 100644
--- a/api/src/engine/engine.interfaces.ts
+++ b/api/src/engine/engine.interfaces.ts
@@ -75,10 +75,7 @@ export interface IEngineService {
    * @param password
    * @returns User object or empty if user not found
    */
-  login?(
-    username: string,
-    password: string,
-  ): Promise<User | undefined> | User | undefined;
+  login?(username: string, password: string): Promise<User | undefined>;
 
   getPassthrough?(suffix: string, req?: Request): Observable<string> | string;
 }
diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts
index 2ea02a19025029f551ea684fa756ca7b842e82d6..6260111702f3268881409f80f5db91cc197c1cba 100644
--- a/api/src/engine/engine.resolver.ts
+++ b/api/src/engine/engine.resolver.ts
@@ -41,18 +41,20 @@ export class EngineResolver {
   @Public()
   configuration(): Configuration {
     const config = this.engineService.getConfiguration?.();
+    const matomo = this.configSerivce.get('matomo');
 
     const data = {
       ...(config ?? {}),
-      skipAuth: parseToBoolean(
-        this.configSerivce.get(authConstants.skipAuth),
-        true,
-      ),
+      connectorId: this.engineOptions.type,
       skipTos: parseToBoolean(this.configSerivce.get(ENGINE_SKIP_TOS)),
       enableSSO: parseToBoolean(
         this.configSerivce.get(authConstants.enableSSO),
       ),
-      connectorId: this.engineOptions.type,
+      skipAuth: parseToBoolean(
+        this.configSerivce.get(authConstants.skipAuth),
+        true,
+      ),
+      matomo,
     };
 
     const version = Md5.hashStr(JSON.stringify(data));
diff --git a/api/src/engine/models/configuration.model.ts b/api/src/engine/models/configuration.model.ts
index d2d390dc67e6bbd120932a382dc324a0d1e1f677..6ffe779cc55962c0dc0c20fd0e3d47c9e4d73348 100644
--- a/api/src/engine/models/configuration.model.ts
+++ b/api/src/engine/models/configuration.model.ts
@@ -1,4 +1,5 @@
 import { Field, ObjectType } from '@nestjs/graphql';
+import { Matomo } from './configuration/matomo.model';
 @ObjectType()
 export class Configuration {
   @Field()
@@ -21,4 +22,7 @@ export class Configuration {
 
   @Field({ nullable: true, defaultValue: true })
   enableSSO?: boolean;
+
+  @Field(() => Matomo, { nullable: true })
+  matomo?: Matomo;
 }
diff --git a/api/src/engine/models/configuration/matomo.model.ts b/api/src/engine/models/configuration/matomo.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62782ce3f4b63bfdee7cbf8821578dd04d9a6db5
--- /dev/null
+++ b/api/src/engine/models/configuration/matomo.model.ts
@@ -0,0 +1,13 @@
+import { Field, ObjectType } from '@nestjs/graphql';
+
+@ObjectType()
+export class Matomo {
+  @Field({ nullable: true, defaultValue: false })
+  enabled?: boolean;
+
+  @Field({ nullable: true })
+  siteId?: string;
+
+  @Field({ nullable: true })
+  urlBase?: string;
+}
diff --git a/api/src/engine/models/result/common/result-union.model.ts b/api/src/engine/models/result/common/result-union.model.ts
index 5c2ffc69597c6b1afc39ea624972586285d6d3e4..0e385962665d0ad0708c84b4597273b4678f55d8 100644
--- a/api/src/engine/models/result/common/result-union.model.ts
+++ b/api/src/engine/models/result/common/result-union.model.ts
@@ -1,10 +1,11 @@
 import { createUnionType } from '@nestjs/graphql';
-import { BarChartResult } from '../bar-chart-result.model';
 import { GroupsResult } from '../groups-result.model';
 import { HeatMapResult } from '../heat-map-result.model';
 import { LineChartResult } from '../line-chart-result.model';
 import { RawResult } from '../raw-result.model';
 import { TableResult } from '../table-result.model';
+import { BarChartResult } from '../bar-chart-result.model';
+import { MeanChartResult } from '../means-chart-result.model';
 
 export const ResultUnion = createUnionType({
   name: 'ResultUnion',
@@ -15,6 +16,7 @@ export const ResultUnion = createUnionType({
     HeatMapResult,
     LineChartResult,
     BarChartResult,
+    MeanChartResult,
   ],
   resolveType(value) {
     if (value.headers) {
@@ -37,6 +39,10 @@ export const ResultUnion = createUnionType({
       return BarChartResult;
     }
 
+    if (value.pointCIs) {
+      return MeanChartResult;
+    }
+
     return RawResult;
   },
 });
diff --git a/api/src/engine/models/result/means-chart-result.model.ts b/api/src/engine/models/result/means-chart-result.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..12dd97416c4ab90afd28d3c26556ba66a8c9ebe5
--- /dev/null
+++ b/api/src/engine/models/result/means-chart-result.model.ts
@@ -0,0 +1,33 @@
+import { Field, ObjectType } from '@nestjs/graphql';
+import { ChartAxis } from './common/chart-axis.model';
+import { Result } from './common/result.model';
+
+@ObjectType()
+export class PointCI {
+  @Field({ nullable: true })
+  min?: number;
+
+  @Field()
+  mean: number;
+
+  @Field({ nullable: true })
+  max?: number;
+}
+
+@ObjectType()
+export class MeanChartResult extends Result {
+  @Field()
+  name: string;
+
+  @Field(() => ChartAxis, { nullable: true })
+  xAxis?: ChartAxis;
+
+  @Field(() => ChartAxis, { nullable: true })
+  yAxis?: ChartAxis;
+
+  @Field(() => [PointCI], {
+    description: 'List of points with confidence information: min, mean, max',
+    defaultValue: [],
+  })
+  pointCIs: PointCI[];
+}
diff --git a/api/src/engine/models/result/table-result.model.ts b/api/src/engine/models/result/table-result.model.ts
index a733683d607f6f8bfbaa9d6cec5884620a76cceb..85fa6eeba8c7f2eba961fb9773159190f313923a 100644
--- a/api/src/engine/models/result/table-result.model.ts
+++ b/api/src/engine/models/result/table-result.model.ts
@@ -2,13 +2,13 @@ import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
 import { Header } from './common/header.model';
 import { Result } from './common/result.model';
 
-export enum ThemeType {
+export enum TableStyle {
   DEFAULT,
   NORMAL,
 }
 
-registerEnumType(ThemeType, {
-  name: 'ThemeType',
+registerEnumType(TableStyle, {
+  name: 'TableStyle',
 });
 
 @ObjectType()
@@ -22,6 +22,6 @@ export class TableResult extends Result {
   @Field(() => [Header])
   headers: Header[];
 
-  @Field(() => ThemeType, { defaultValue: ThemeType.DEFAULT, nullable: true })
-  theme?: ThemeType;
+  @Field(() => TableStyle, { defaultValue: TableStyle.DEFAULT, nullable: true })
+  tableStyle?: TableStyle;
 }
diff --git a/api/src/files/files.controller.ts b/api/src/files/files.controller.ts
index 91f3f8549eaf05822ed51d456b29dc03e49a5a99..8ca4ba83f0632dcf4c8f9150dbd8ace0469450b6 100644
--- a/api/src/files/files.controller.ts
+++ b/api/src/files/files.controller.ts
@@ -20,9 +20,10 @@ export class FilesController {
     @Res() response: Response,
     @Param('name') filename: string,
   ) {
+    const proto = request.headers['x-forwarded-proto'] ?? request.protocol;
     if (filename.endsWith('.md')) {
       const baseurl =
-        request.protocol +
+        proto +
         '://' +
         join(request.get('host'), process.env.BASE_URL_CONTEXT ?? '', 'assets'); // not full url, should consider "/services"
       const text = this.filesService.getMarkdown(filename, baseurl);
diff --git a/api/src/main/app.module.ts b/api/src/main/app.module.ts
index 56ef2df6fe32a0198a818a8b035418c44e54ad76..ebc1be97cb1be3d02a48c3ec4d561280df249b03 100644
--- a/api/src/main/app.module.ts
+++ b/api/src/main/app.module.ts
@@ -7,6 +7,7 @@ import { GraphQLError } from 'graphql';
 import { join } from 'path';
 import { AuthModule } from 'src/auth/auth.module';
 import dbConfig from 'src/config/db.config';
+import matomoConfig from 'src/config/matomo.config';
 import { EngineModule } from 'src/engine/engine.module';
 import { FilesModule } from 'src/files/files.module';
 import { UsersModule } from 'src/users/users.module';
@@ -18,7 +19,7 @@ import { AppService } from './app.service';
     ConfigModule.forRoot({
       isGlobal: true,
       envFilePath: ['.env', '.env.defaults'],
-      load: [dbConfig],
+      load: [dbConfig, matomoConfig],
     }),
     GraphQLModule.forRoot<ApolloDriverConfig>({
       driver: ApolloDriver,
@@ -53,7 +54,6 @@ import { AppService } from './app.service';
         migrations: ['dist/migrations/*{.ts,.js}'],
         migrationsRun: process.env.NODE_ENV !== 'dev',
         synchronize: process.env.NODE_ENV === 'dev',
-        loggerLevel: 'debug',
         autoLoadEntities: true,
       }),
     }),
diff --git a/api/src/schema.gql b/api/src/schema.gql
index 8a0e2400bf297c9d066ef68dc77ec2cee3b7672f..8f5b0feba8b4b7d1c700ddf1d9e02563d532fe83 100644
--- a/api/src/schema.gql
+++ b/api/src/schema.gql
@@ -14,6 +14,12 @@ type AuthenticationOutput {
   accessToken: String!
 }
 
+type Matomo {
+  enabled: Boolean
+  siteId: String
+  urlBase: String
+}
+
 type Configuration {
   connectorId: String!
   hasGalaxy: Boolean
@@ -22,6 +28,7 @@ type Configuration {
   skipAuth: Boolean
   skipTos: Boolean
   enableSSO: Boolean
+  matomo: Matomo
 }
 
 type Dataset {
@@ -85,30 +92,22 @@ type Algorithm {
   description: String
 }
 
-type ChartAxis {
-  """label of the Axis"""
-  label: String
-
-  """label of each element on this Axis"""
-  categories: [String!]
-}
-
 type GroupResult {
   name: String!
   description: String
   results: [ResultUnion!]!
 }
 
-union ResultUnion = TableResult | RawResult | GroupsResult | HeatMapResult | LineChartResult | BarChartResult
+union ResultUnion = TableResult | RawResult | GroupsResult | HeatMapResult | LineChartResult | BarChartResult | MeanChartResult
 
 type TableResult {
   name: String!
   data: [[String!]!]!
   headers: [Header!]!
-  theme: ThemeType
+  tableStyle: TableStyle
 }
 
-enum ThemeType {
+enum TableStyle {
   DEFAULT
   NORMAL
 }
@@ -157,6 +156,23 @@ type BarChartResult {
   hasConnectedBars: Boolean
 }
 
+type MeanChartResult {
+  name: String!
+  xAxis: ChartAxis
+  yAxis: ChartAxis
+
+  """List of points with confidence information: min, mean, max"""
+  pointCIs: [PointCI!]!
+}
+
+type ChartAxis {
+  """label of the Axis"""
+  label: String
+
+  """label of each element on this Axis"""
+  categories: [String!]
+}
+
 type ExtraLineInfo {
   label: String!
   values: [String!]!
@@ -180,6 +196,12 @@ type Header {
   type: String!
 }
 
+type PointCI {
+  min: Float
+  mean: Float!
+  max: Float
+}
+
 type Author {
   username: String
   fullname: String
diff --git a/api/src/users/users.resolver.ts b/api/src/users/users.resolver.ts
index 13ec63ae73dc528d0b9c1dd1a0425f7854045f4e..0b78a0fb5af9b0dfb7de5a8d727e418954697de0 100644
--- a/api/src/users/users.resolver.ts
+++ b/api/src/users/users.resolver.ts
@@ -76,7 +76,11 @@ export class UsersResolver {
     @CurrentUser() user?: User,
   ) {
     if (this.engineService.updateUser)
-      return this.engineService.updateUser(request, user?.id, updateUserInput);
+      return await this.engineService.updateUser(
+        request,
+        user?.id,
+        updateUserInput,
+      );
 
     await this.usersService.update(user.id, updateUserInput);
 
diff --git a/for-developers/authentication.md b/for-developers/authentication.md
new file mode 100644
index 0000000000000000000000000000000000000000..bed3b7e54346a681b603aa11ad401be6dce03a65
--- /dev/null
+++ b/for-developers/authentication.md
@@ -0,0 +1,92 @@
+# Authentication
+
+The authentication implementation is based on [passport.js](https://www.passportjs.org) it allows a flexible way to implement different strategies inside the gateway.&#x20;
+
+For now the authentication system is quite simple and only use JWT. The real implementation of  authorization and authentication is left to the connector.&#x20;
+
+#### How it works ?
+
+The communication between the frontend and the gateway is handled by JWT token who contains user information such as his username.
+
+![](<../.gitbook/assets/image (2).png>)
+
+The gateway will handle the authentication process with the frontend in a unique fashion always using a JWT token. This token can contains information specific to some connector. For that purpose the user model contains a field `extraFields` which basically a dictionary.&#x20;
+
+{% code title="user.model.ts" %}
+```typescript
+import { Field, ObjectType } from '@nestjs/graphql';
+import { Entity, PrimaryColumn, Column } from 'typeorm';
+
+@Entity({ name: 'user' })
+@ObjectType()
+export class User {
+  @PrimaryColumn()
+  @Field()
+  id: string;
+
+  @Field()
+  username: string;
+
+  @Field({ nullable: true })
+  fullname?: string;
+
+  @Field({ nullable: true })
+  email?: string;
+
+  @Column({ nullable: true, default: false })
+  @Field({ nullable: true })
+  agreeNDA?: boolean;
+
+  extraFields?: Record<string, any>;
+}
+```
+{% endcode %}
+
+This field can be used by the connector to store information related to the user as other token for engine API endpoints.
+
+#### Login&#x20;
+
+The real login system is delegated to the connector by using the `login` method in the interface.
+
+{% code title="engine.interface.ts" %}
+```typescript
+export interface IEngineService {
+  // ...
+  
+  /**
+   * Method that login a user with username and password
+   * @param username
+   * @param password
+   * @returns User object or empty if user not found
+   */
+  login?(
+    username: string,
+    password: string,
+  ): Promise<User | undefined>;
+
+  // ...
+}
+```
+{% endcode %}
+
+This method can be optional as the authentication can be made by a 3rd party system under the same domain as this is the case for `exareme`.
+
+When the login is performed, this function should return a `User` object and can feed the `extraFields` attribute with data needed to perform future request to the engine.
+
+#### Logout
+
+The same mechanism is applied to the logout system using the method logout from the engine.
+
+{% code title="engine.interface.ts" %}
+```typescript
+export interface IEngineService {
+  // ...
+  
+  logout?(req: Request
+  ): Promise<void>;
+
+  // ...
+}
+```
+{% endcode %}
+