diff --git a/docs/content-services/services/audit.service.md b/docs/content-services/services/audit.service.md
new file mode 100644
index 0000000000..ab34a4fed7
--- /dev/null
+++ b/docs/content-services/services/audit.service.md
@@ -0,0 +1,57 @@
+---
+Title: Audit Service
+Added: v3.9.0
+Status: Active
+Last reviewed: 2020-08-12
+---
+
+# [Audit Service](../../../lib/content-services/src/lib/audit/audit.service.ts "Defined in audit.service.ts")
+
+Manages Audit apps and entries.
+
+## Class members
+
+### Methods
+
+- **getAuditApps**(opts?: `any`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditAppPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditAppPaging.md)`>`
+ List audit applications.
+ - _opts:_ `any` - (Optional) Options.
+ - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditAppPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditAppPaging.md)`>` - Target Audit Apps.
+- **getAuditApp**(auditApplicationId: `string`, opts?: `any`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditAppEntry`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditAppEntry.md)`>`
+ Get audit application info.
+ - _auditApplicationId:_ `string` - The identifier of an audit application.
+ - _opts:_ `any` - (Optional) Options.
+ - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditAppEntry`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditAppEntry.md)`>` - Target Audit App.
+- **updateAuditApp**(auditApplicationId: `string`, auditAppBodyUpdate: `boolean`, opts?: `any`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditApp`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditApp.md)` | {}>`
+ Update audit application info.
+ - _auditApplicationId:_ `string` - The identifier of an audit application.
+ - _auditAppBodyUpdate:_ `boolean` - The audit application to update.
+ - _opts:_ `any` - (Optional) Options.
+ - **Returns** - Information about audit application that were updated
+- **getAuditEntries**(auditApplicationId: `string`, opts?: `any`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditEntryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditEntryPaging.md)`>`
+ List audit entries for an audit application.
+ - _auditApplicationId:_ `string` - The identifier of an audit application.
+ - _opts:_ `any` - (Optional) Options.
+ - **Returns** - A list of audit entries for audit application.
+- **getAuditEntry**(auditApplicationId: `string`, auditEntryId: `string`, opts?: `any`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditEntryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditEntryEntry.md)`>`
+ Get audit entry.
+ - _auditApplicationId:_ `string` - The identifier of an audit application.
+ - _auditEntryId:_ `string` - The identifier of an audit entry.
+ - _opts:_ `any` - (Optional) Options.
+ - **Returns** - Targe audit entry.
+- **getAuditEntriesForNode**(nodeId: `string`, opts?: `any`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditEntryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditEntryPaging.md)`>`
+ List audit entries for a node.
+ - _nodeId:_ `string` - The identifier of a node.
+ - _opts:_ `any` - (Optional) Options.
+ - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AuditEntryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/AuditEntryPaging.md)`>` - A list of audit entries for node.
+- **deleteAuditEntries**(auditApplicationId: `string`, where: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)``
+ Permanently delete audit entries for an audit application.
+ - _auditApplicationId:_ `string` - The identifier of an audit application.
+ - _where:_ `string` - Audit entries to permanently delete for an audit application, given an inclusive time period or range of ids.
+ - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`` - empty response body
+- **deleteAuditEntry**(auditApplicationId: `string`, auditEntryId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)``
+ Permanently delete an audit entry.
+ - _auditApplicationId:_ `string` - The identifier of an audit application.
+ - _auditEntryId:_ `string` - The identifier of an audit entry.
+ - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`` - empty response body
+
diff --git a/lib/content-services/src/lib/audit/audit.service.spec.ts b/lib/content-services/src/lib/audit/audit.service.spec.ts
new file mode 100644
index 0000000000..fbddf227d3
--- /dev/null
+++ b/lib/content-services/src/lib/audit/audit.service.spec.ts
@@ -0,0 +1,203 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { AuditService } from './audit.service';
+import { AppConfigService, setupTestBed } from '@alfresco/adf-core';
+import { TranslateModule } from '@ngx-translate/core';
+import { ContentTestingModule } from '../testing/content.testing.module';
+import { TestBed } from '@angular/core/testing';
+
+declare let jasmine: any;
+
+describe('AuditService', () => {
+ let service: AuditService;
+
+ setupTestBed({
+ imports: [
+ TranslateModule.forRoot(),
+ ContentTestingModule
+ ]
+ });
+
+ beforeEach(() => {
+ const appConfig: AppConfigService = TestBed.inject(AppConfigService);
+ appConfig.config = {
+ ecmHost: 'http://localhost:9876/ecm',
+ files: {
+ excluded: ['.DS_Store', 'desktop.ini', '.git', '*.git']
+ }
+ };
+ service = TestBed.inject(AuditService);
+ jasmine.Ajax.install();
+ });
+
+ afterEach(() => {
+ jasmine.Ajax.uninstall();
+ });
+
+ it('Should get Audit Applications', (done) => {
+ service.getAuditApps().subscribe((data) => {
+ expect(data.list.pagination.count).toBe(3);
+ done();
+ });
+
+ jasmine.Ajax.requests.mostRecent().respondWith({
+ status: 200,
+ contentType: 'json',
+ responseText: {
+ 'list': {
+ 'pagination': {
+ 'count': 3,
+ 'hasMoreItems': false,
+ 'totalItems': 3,
+ 'skipCount': 0,
+ 'maxItems': 100
+ },
+ 'entries': [
+ {
+ 'entry': {
+ 'isEnabled': true,
+ 'name': 'Alfresco Tagging Service',
+ 'id': 'tagging'
+ }
+ },
+ {
+ 'entry': {
+ 'isEnabled': true,
+ 'name': 'ShareSiteAccess',
+ 'id': 'share-site-access'
+ }
+ },
+ {
+ 'entry': {
+ 'isEnabled': true,
+ 'name': 'alfresco-access',
+ 'id': 'alfresco-access'
+ }
+ }
+ ]
+ }
+ }
+ });
+ });
+
+ it('Should get an Audit Application', (done) => {
+ service.getAuditApp('alfresco-access').subscribe((data) => {
+ expect(data.entry.id).toBe('alfresco-access');
+ expect(data.entry.name).toBe('alfresco-access');
+ done();
+ });
+
+ jasmine.Ajax.requests.mostRecent().respondWith({
+ status: 200,
+ contentType: 'json',
+ responseText: {
+ 'entry': {
+ 'id': 'alfresco-access',
+ 'name': 'alfresco-access',
+ 'isEnabled': true
+ }
+ }
+ });
+ });
+
+ it('Should get Audit Entries', (done) => {
+ service.getAuditEntries('alfresco-access').subscribe((data) => {
+ expect(data.list.pagination.count).toBe(3);
+ done();
+ });
+
+ jasmine.Ajax.requests.mostRecent().respondWith({
+ status: 200,
+ contentType: 'json',
+ responseText: {
+ 'list': {
+ 'pagination': {
+ 'count': 3,
+ 'hasMoreItems': false,
+ 'totalItems': 3,
+ 'skipCount': 0,
+ 'maxItems': 100
+ },
+ 'entries': [
+ {
+ 'entry': {
+ 'id': '1',
+ 'auditApplicationId': 'alfresco-access',
+ 'createdByUser': {
+ 'displayName': 'admin',
+ 'id': 'admin'
+ },
+ 'createdAt': '2020-08-11T13:11:59.141Z',
+ 'values': {}
+ }
+ },
+ {
+ 'entry': {
+ 'id': '2',
+ 'auditApplicationId': 'alfresco-access',
+ 'createdByUser': {
+ 'displayName': 'admin',
+ 'id': 'admin'
+ },
+ 'createdAt': '2020-08-11T13:11:59.141Z',
+ 'values': {}
+ }
+ },
+ {
+ 'entry': {
+ 'id': '3',
+ 'auditApplicationId': 'alfresco-access',
+ 'createdByUser': {
+ 'displayName': 'admin',
+ 'id': 'admin'
+ },
+ 'createdAt': '2020-08-11T13:11:59.141Z',
+ 'values': {}
+ }
+ }
+ ]
+ }
+ }
+ });
+ });
+
+ it('Should get an Audit Entry', (done) => {
+ service.getAuditEntry('alfresco-access', '1').subscribe((data) => {
+ expect(data.entry.id).toBe('1');
+ expect(data.entry.auditApplicationId).toBe('alfresco-access');
+ done();
+ });
+
+ jasmine.Ajax.requests.mostRecent().respondWith({
+ status: 200,
+ contentType: 'json',
+ responseText: {
+ 'entry': {
+ 'id': '1',
+ 'auditApplicationId': 'alfresco-access',
+ 'createdByUser': {
+ 'displayName': 'admin',
+ 'id': 'admin'
+ },
+ 'createdAt': '2020-08-11T13:11:59.148Z',
+ 'values': {}
+ }
+ }
+ });
+ });
+});
diff --git a/lib/content-services/src/lib/audit/audit.service.ts b/lib/content-services/src/lib/audit/audit.service.ts
new file mode 100644
index 0000000000..39304fbc71
--- /dev/null
+++ b/lib/content-services/src/lib/audit/audit.service.ts
@@ -0,0 +1,117 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { Observable, from, throwError } from 'rxjs';
+import { AlfrescoApiService } from '../../../../core/services';
+import { AuditApi, AuditAppPaging, AuditAppEntry, AuditApp, AuditBodyUpdate, AuditEntryPaging, AuditEntryEntry } from '@alfresco/js-api';
+import { catchError } from 'rxjs/operators';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuditService {
+
+ auditApi: AuditApi;
+
+ constructor(
+ private apiService: AlfrescoApiService) {
+ this.auditApi = new AuditApi(this.apiService.getInstance());
+ }
+
+ getAuditApps(opts?: any): Observable {
+ const defaultOptions = {
+ skipCount: 0
+ };
+ const queryOptions = Object.assign({}, defaultOptions, opts);
+ return from(this.auditApi.listAuditApps(queryOptions))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ getAuditApp(auditApplicationId: string, opts?: any): Observable {
+ const defaultOptions = {
+ auditApplicationId: auditApplicationId
+ };
+ const queryOptions = Object.assign({}, defaultOptions, opts);
+ return from(this.auditApi.getAuditApp(queryOptions))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ updateAuditApp(auditApplicationId: string, auditAppBodyUpdate: boolean, opts?: any): Observable {
+ const defaultOptions = {};
+ const queryOptions = Object.assign({}, defaultOptions, opts);
+ return from(this.auditApi.updateAuditApp(auditApplicationId, new AuditBodyUpdate({ isEnabled: auditAppBodyUpdate}), queryOptions))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ getAuditEntries(auditApplicationId: string, opts?: any): Observable {
+ const defaultOptions = {
+ skipCount: 0,
+ maxItems: 100
+ };
+ const queryOptions = Object.assign({}, defaultOptions, opts);
+ return from(this.auditApi.listAuditEntriesForAuditApp(auditApplicationId, queryOptions))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ getAuditEntry(auditApplicationId: string, auditEntryId: string, opts?: any): Observable {
+ const defaultOptions = {};
+ const queryOptions = Object.assign({}, defaultOptions, opts);
+ return from(this.auditApi.getAuditEntry(auditApplicationId, auditEntryId, queryOptions))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ getAuditEntriesForNode(nodeId: string, opts?: any): Observable {
+ const defaultOptions = {
+ nodeId: nodeId
+ };
+ const queryOptions = Object.assign({}, defaultOptions, opts);
+ return from(this.auditApi.listAuditEntriesForNode(queryOptions))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ deleteAuditEntries(auditApplicationId: string, where: string): Observable {
+ return from(this.auditApi.deleteAuditEntriesForAuditApp(auditApplicationId, where))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ deleteAuditEntry(auditApplicationId: string, auditEntryId: string): Observable {
+ return from(this.auditApi.deleteAuditEntry(auditApplicationId, auditEntryId))
+ .pipe(
+ catchError((err: any) => this.handleError(err))
+ );
+ }
+
+ private handleError(error: any): any {
+ console.error(error);
+ return throwError(error || 'Server error');
+ }
+}