Repository: gini/gini-mobile-ios
Pull request #788: feat(GiniHealthSDK): Delete bulk documents 
Author: login: razvancapra
Created: 2025-03-03T08:51:32Z | Merged: 2025-03-17T09:02:00Z

IPC-572


Diff:
diff --git a/.github/workflows/health-sdk.publish.example.apps.firebase.yml b/.github/workflows/health-sdk.publish.example.apps.firebase.yml
index 12a3709e29..161ab2b227 100644
--- a/.github/workflows/health-sdk.publish.example.apps.firebase.yml
+++ b/.github/workflows/health-sdk.publish.example.apps.firebase.yml
@@ -14,13 +14,7 @@ concurrency:
   cancel-in-progress: ${{ !contains(github.ref, 'refs/tags/')}}
 
 jobs:
-  check:
-    uses: ./.github/workflows/health-sdk.check.yml
-    secrets:
-      GINI_MOBILE_TEST_CLIENT_SECRET: ${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}
-
   upload-version:
-    needs: check
     runs-on: macos-latest    
     steps:
       - name: Checkout repository
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/GiniError.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/GiniError.swift
index 213bb131ae..8c9cc9e8d9 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/GiniError.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/GiniError.swift
@@ -7,13 +7,44 @@
 
 import Foundation
 
+/**
+ A protocol representing errors that may occur when interacting with the Gini API.
+
+ This protocol defines three properties:
+ - `message`: A descriptive message explaining the error.
+ - `response`: The associated `HTTPURLResponse`, if available.
+ - `data`: The response data received, if any.
+ */
+
 public protocol GiniErrorProtocol {
     var message: String { get }
     var response: HTTPURLResponse? { get }
     var data: Data? { get }
 }
 
-public enum GiniError: Error, GiniErrorProtocol, Equatable {
+/**
+ A protocol representing custom errors that may occur when interacting with the Gini API.
+
+ This protocol defines three properties:
+ - `unauthorizedDocuments`: An array of documents that could not be deleted due to insufficient permissions.  
+ - `notFoundDocuments`: An array of documents that were not found during a bulk deletion attempt.
+ - `missingCompositeDocuments`: An array of composite documents that are missing when attempting to perform a bulk deletion.
+ */
+
+public protocol GiniCustomErrorProtocol {
+    var unauthorizedDocuments: [String]? { get }
+    var notFoundDocuments: [String]? { get }
+    var missingCompositeDocuments: [String]? { get }
+}
+
+struct GiniCustomError: GiniCustomErrorProtocol, Codable {
+    var message: String?
+    var unauthorizedDocuments: [String]?
+    var notFoundDocuments: [String]?
+    var missingCompositeDocuments: [String]?
+}
+
+public enum GiniError: Error, GiniErrorProtocol, GiniCustomErrorProtocol, Equatable {
     case badRequest(response: HTTPURLResponse? = nil, data: Data? = nil)
     case notAcceptable(response: HTTPURLResponse? = nil, data: Data? = nil)
     case notFound(response: HTTPURLResponse? = nil, data: Data? = nil)
@@ -22,8 +53,9 @@ public enum GiniError: Error, GiniErrorProtocol, Equatable {
     case requestCancelled
     case tooManyRequests(response: HTTPURLResponse? = nil, data: Data? = nil)
     case unauthorized(response: HTTPURLResponse? = nil, data: Data? = nil)
+    case customError(response: HTTPURLResponse? = nil, data: Data? = nil)
     case unknown(response: HTTPURLResponse? = nil, data: Data? = nil)
-    
+
     public var message: String {
         switch self {
         case .badRequest:
@@ -44,9 +76,14 @@ public enum GiniError: Error, GiniErrorProtocol, Equatable {
             return "Unauthorized"
         case .unknown:
             return "Unknown"
+        case .customError(_, _):
+            if let message = customError?.message {
+                return message
+            }
+            return getCustomErrorMessage() ?? localizedDescription
         }
     }
-    
+
     public var response: HTTPURLResponse? {
         switch self {
         case .badRequest(let response, _),
@@ -55,13 +92,14 @@ public enum GiniError: Error, GiniErrorProtocol, Equatable {
              .parseError(_, let response, _),
              .tooManyRequests(let response, _),
              .unauthorized(let response, _),
-             .unknown(let response, _):
+             .unknown(let response, _),
+             .customError(let response, _):
             return response
         default:
             return nil
         }
     }
-    
+
     public var data: Data? {
         switch self {
         case .badRequest(_, let data),
@@ -70,10 +108,42 @@ public enum GiniError: Error, GiniErrorProtocol, Equatable {
              .parseError(_, _, let data),
              .tooManyRequests(_, let data),
              .unauthorized(_, let data),
-             .unknown(_, let data):
+             .unknown(_, let data),
+             .customError(_, let data):
             return data
         default:
             return nil
         }
     }
+
+    var customError: GiniCustomError? {
+        guard let data, let customErrorDecoded = try? JSONDecoder().decode(GiniCustomError.self, from: data) else {
+            return nil
+        }
+        return customErrorDecoded
+    }
+
+    public var unauthorizedDocuments: [String]? {
+        return customError?.unauthorizedDocuments
+    }
+
+    public var notFoundDocuments: [String]? {
+        return customError?.notFoundDocuments
+    }
+
+    public var missingCompositeDocuments: [String]? {
+        return customError?.missingCompositeDocuments
+    }
+
+    /// Helper Function to Get Custom Document Errors Message
+    private func getCustomErrorMessage() -> String? {
+        if let unauthorizedDocuments = customError?.unauthorizedDocuments {
+            return "Unauthorized documents: \(unauthorizedDocuments.joined(separator: ", "))"
+        } else if let notFoundDocuments = customError?.notFoundDocuments {
+            return "Not found documents: \(notFoundDocuments.joined(separator: ", "))"
+        } else if let missingCompositeDocuments = customError?.missingCompositeDocuments {
+            return "Missing composite documents: \(missingCompositeDocuments.joined(separator: ", "))"
+        }
+        return nil
+    }
 }
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift
index 2fbc1c8685..1618100a8a 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift
@@ -317,12 +317,18 @@ private extension SessionManager {
                 return
             }
 
-            guard let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
-                  errorInfo["error"] == "invalid_grant" else {
-                completion(.failure(.badRequest(response: response, data: data)))
+            if let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
+                  errorInfo["error"] == "invalid_grant" {
+                completion(.failure(.unauthorized(response: response, data: data)))
                 return
             }
-            completion(.failure(.unauthorized(response: response, data: data)))
+
+            if (try? JSONSerialization.jsonObject(with: responseData) as? [String: Any]) != nil {
+                completion(.failure(.customError(response: response, data: responseData)))
+                return
+            }
+
+            completion(.failure(.badRequest(response: response, data: data)))
         case 401:
             completion(.failure(.unauthorized(response: response, data: data)))
         case 404:
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift
index 0aa2c5dace..235d1a2cf0 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift
@@ -301,6 +301,18 @@ public final class DefaultDocumentService: DefaultDocumentServiceProtocol {
     public func file(urlString: String, completion: @escaping CompletionResult<Data>){
         file(urlString: urlString, resourceHandler: sessionManager.download, completion: completion)
     }
+
+    /**
+     *  Deletes a batch of documents
+     *
+     * - Parameter documentIds:         An array of document ids to be deleted
+     * - Parameter completion:          An action for deleting a batch of documents. Result is a value that represents either a success or a failure, including an associated value in each case.
+                                        In success it includes a success message
+                                        In case of failure error from the server side.
+     */
+    public func deleteDocuments(_ documentIds: [String], completion: @escaping CompletionResult<String>) {
+        deleteDocuments(resourceHandler: sessionManager.data, with: documentIds, completion: completion)
+    }
 }
 
 extension DefaultDocumentService {
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift
index b4400eaba7..9f6b759c24 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift
@@ -94,6 +94,16 @@ public protocol DocumentService: AnyObject {
      */
     
     func file(urlString: String, completion: @escaping CompletionResult<Data>)
+
+    /**
+     *  Deletes a batch of documents
+     *
+     * - Parameter documentIds:         An array of document ids to be deleted
+     * - Parameter completion:          An action for deleting a batch of documents. Result is a value that represents either a success or a failure, including an associated value in each case.
+                                        In success it includes a success message
+                                        In case of failure error from the server side.
+     */
+    func deleteDocuments(_ documentIds: [String], completion: @escaping CompletionResult<String>)
 }
 
 protocol V2DocumentService: AnyObject {
@@ -149,7 +159,31 @@ extension DocumentService {
             }
         })
     }
-    
+
+    func deleteDocuments(resourceHandler: ResourceDataHandler<APIResource<String>>,
+                         with ids: [String],
+                         completion: @escaping CompletionResult<String>) {
+        guard let json = try? JSONEncoder().encode(ids) else {
+            assertionFailure("The document ids provided cannot be encoded")
+            return
+        }
+
+        let resource = APIResource<String>(method: .documents(limit: nil, offset: nil),
+                                           apiDomain: apiDomain,
+                                           apiVersion: apiVersion,
+                                           httpMethod: .delete,
+                                           body: json)
+
+        resourceHandler(resource, { result in
+            switch result {
+            case .success(let string):
+                completion(.success(string))
+            case .failure(let error):
+                completion(.failure(error))
+            }
+        })
+    }
+
     func extractions(resourceHandler: @escaping CancellableResourceDataHandler<APIResource<ExtractionsContainer>>,
                      documentResourceHandler: @escaping CancellableResourceDataHandler<APIResource<Document>>,
                      for document: Document,
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md b/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md
index 0a53c89476..a3b17af851 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md
@@ -125,6 +125,15 @@ The method returns `true` in the success case if the `contains_multiple_docs` fi
 
 > - Recommendation: Use this check in a specific order. First, call the `checkIfDocumentIsPayable` method, and then call `checkIfDocumentContainsMultipleInvoices` method.
 
+## Delete a batch of documents
+
+GiniHealth provides a method to delete multiple documents at once:
+
+```swift
+healthSDK.deleteDocuments(documentIds: [String], completion: @escaping (Result<String, GiniError>) -> Void)
+```
+
+This method enables clients to delete multiple documents simultaneously by passing an array of document IDs. Upon success, it returns an array of successfully deleted documents. In case of an error, a specific error message is provided.
 
 ## Integrate the Payment flow
 
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/DocumentService.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/DocumentService.swift
index 819c7b8a3b..6e34699572 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/DocumentService.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/DocumentService.swift
@@ -294,4 +294,15 @@ public final class DefaultDocumentService {
                 }
             })
     }
+
+    public func deleteDocuments(_ documentIds: [String], completion: @escaping CompletionResult<String>) {
+        docService.deleteDocuments(documentIds) { result in
+            switch result {
+            case .success(let data):
+                completion(.success(data))
+            case .failure(let error):
+                completion(.failure(GiniError.decorator(error)))
+            }
+        }
+    }
 }
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/GiniError.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/GiniError.swift
index 3dd9da0f8f..beffee6c6a 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/GiniError.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/GiniError.swift
@@ -14,7 +14,8 @@ public protocol GiniErrorProtocol {
     var data: Data? { get }
 }
 
-public enum GiniError: Error, GiniErrorProtocol, Equatable {
+/// An enumeration representing errors that can occur when interacting with the Gini API.
+public enum GiniError: Error, GiniErrorProtocol, GiniCustomErrorProtocol, Equatable {
     case decorator(GiniHealthAPILibrary.GiniError)
 
     public var message: String {
@@ -37,4 +38,25 @@ public enum GiniError: Error, GiniErrorProtocol, Equatable {
             return giniError.data
         }
     }
+
+    public var unauthorizedDocuments: [String]? {
+        switch self {
+        case .decorator(let giniError):
+            return giniError.unauthorizedDocuments
+        }
+    }
+
+    public var notFoundDocuments: [String]? {
+        switch self {
+        case .decorator(let giniError):
+            return giniError.notFoundDocuments
+        }
+    }
+
+    public var missingCompositeDocuments: [String]? {
+        switch self {
+        case .decorator(let giniError):
+            return giniError.missingCompositeDocuments
+        }
+    }
 }
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
index e856f3f812..a5b6b7de93 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
@@ -557,6 +557,29 @@ public struct DataForReview {
         }
     }
 
+    /**
+     Delete a batch of documents
+
+     - Parameters:
+        - documentIds: An array of document ids to be deleted
+        - completion: An action for deleting a batch of documents. Result is a value that represents either a success or a failure, including an associated value in each case.
+        In success it includes a success message
+        In case of failure error from the server side.
+
+     */
+    public func deleteDocuments(documentIds: [String], completion: @escaping (Result<String, GiniError>) -> Void) {
+        documentService.deleteDocuments(documentIds) { result in
+            DispatchQueue.main.async {
+                switch result {
+                case let .success(message):
+                    completion(.success(message))
+                case let .failure(error):
+                    completion(.failure(error))
+                }
+            }
+        }
+    }
+
     /// A static string representing the current version of the Gini Health SDK.
     public static var versionString: String {
         return GiniHealthSDKVersion
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthDocumentHandlingTests.swift b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthDocumentHandlingTests.swift
new file mode 100644
index 0000000000..272f492c54
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthDocumentHandlingTests.swift
@@ -0,0 +1,243 @@
+import XCTest
+@testable import GiniHealthSDK
+@testable import GiniHealthAPILibrary
+@testable import GiniInternalPaymentSDK
+@testable import GiniUtilites
+
+final class GiniHealthDocumentHandlingTests: XCTestCase {
+
+    var giniHealthAPI: GiniHealthAPI!
+    var giniHealth: GiniHealth!
+    private let versionAPI = 4
+
+    override func setUp() {
+        let sessionManagerMock = MockSessionManager()
+        let documentService = DefaultDocumentService(sessionManager: sessionManagerMock, apiVersion: versionAPI)
+        let paymentService = PaymentService(sessionManager: sessionManagerMock, apiVersion: versionAPI)
+        let clientConfigurationService = ClientConfigurationService(sessionManager: sessionManagerMock, apiVersion: versionAPI)
+        GiniHealthConfiguration.shared.clientConfiguration = nil
+        giniHealthAPI = GiniHealthAPI(documentService: documentService,
+                                      paymentService: paymentService,
+                                      clientConfigurationService: clientConfigurationService)
+        giniHealth = GiniHealth(giniApiLib: giniHealthAPI)
+    }
+
+    override func tearDown() {
+        giniHealthAPI = nil
+        giniHealth = nil
+        super.tearDown()
+    }
+
+    func testPollDocumentSuccess() {
+        // Given
+        let healthDocument: GiniHealthAPILibrary.Document = GiniHealthSDKTests.load(fromFile: "document1")!
+        let expectedDocument: GiniHealthSDK.Document? = GiniHealthSDK.Document(healthDocument: healthDocument)
+
+        // When
+        let expectation = self.expectation(description: "Polling document")
+        var receivedDocument: GiniHealthSDK.Document?
+        giniHealth.pollDocument(docId: MockSessionManager.payableDocumentID) { result in
+            switch result {
+            case .success(let document):
+                receivedDocument = document
+            case .failure(_):
+                receivedDocument = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedDocument)
+        XCTAssertEqual(receivedDocument, expectedDocument)
+    }
+    
+    func testPollDocumentFailure() {
+        // When
+        let expectation = self.expectation(description: "Polling failure document")
+        var receivedDocument: GiniHealthSDK.Document?
+        giniHealth.pollDocument(docId: MockSessionManager.missingDocumentID) { result in
+            switch result {
+            case .success(let document):
+                receivedDocument = document
+            case .failure(_):
+                receivedDocument = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNil(receivedDocument)
+    }
+
+    func testSetDocumentForReviewSuccess() {
+        // Given
+        let fileName = "extractionsWithPayment"
+        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let expectedExtractionContainer else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).payment?.first ?? []
+
+        // When
+        let expectation = self.expectation(description: "Setting document for review")
+        var receivedExtractions: [GiniHealthSDK.Extraction]?
+        giniHealth.setDocumentForReview(documentId: MockSessionManager.extractionsWithPaymentDocumentID) { result in
+            switch result {
+            case .success(let extractions):
+                receivedExtractions = extractions
+            case .failure(_):
+                receivedExtractions = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedExtractions)
+        XCTAssertEqual(receivedExtractions?.count, expectedExtractions.count)
+    }
+    
+    func testFetchDataForReviewSuccess() {
+        // Given
+        let fileName = "extractionsWithPayment"
+        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let expectedExtractionContainer else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).payment?.first ?? []
+        let documentFileName = "document4"
+
+        let healthDocument: GiniHealthAPILibrary.Document = GiniHealthSDKTests.load(fromFile: documentFileName)!
+        let expectedDocument: GiniHealthSDK.Document? = GiniHealthSDK.Document(healthDocument: healthDocument)
+
+        guard let expectedDocument else {
+            XCTFail("Error loading file: `\(documentFileName).json`")
+            return
+        }
+        let expectedDatForReview = DataForReview(document: expectedDocument, extractions: expectedExtractions)
+
+        // When
+        let expectation = self.expectation(description: "Fetching data for review")
+        var receivedDataForReview: DataForReview?
+        giniHealth.fetchDataForReview(documentId: MockSessionManager.extractionsWithPaymentDocumentID) { result in
+            switch result {
+            case .success(let dataForReview):
+                receivedDataForReview = dataForReview
+            case .failure(_):
+                receivedDataForReview = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedDataForReview)
+        XCTAssertEqual(receivedDataForReview?.document, expectedDatForReview.document)
+        XCTAssertEqual(receivedDataForReview?.extractions.count, expectedDatForReview.extractions.count)
+    }
+    
+    func testFetchDataForReviewFailure() {
+        // When
+        let expectation = self.expectation(description: "Failure fetching data for review")
+        var receivedError: GiniHealthError?
+        giniHealth.fetchDataForReview(documentId: MockSessionManager.missingDocumentID) { result in
+            switch result {
+            case .success(_):
+                receivedError = nil
+            case .failure(let error):
+                receivedError = error
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedError)
+    }
+
+    // MARK: - Delete Batch Of Documents Tests
+    /// Enum for Delete  Batch Document Types
+    private enum DeleteBatchDocumentType: String {
+        case unauthorizedDocuments
+        case notFoundDocuments
+        case missingCompositeDocuments
+        case success = ""
+        case failure
+
+        /// Expected failure response if applicable
+        var expectedFailure: [String]? {
+            switch self {
+            case .unauthorizedDocuments, .notFoundDocuments, .missingCompositeDocuments:
+                return [self.rawValue]
+            default:
+                return nil
+            }
+        }
+
+        /// Expected success response if applicable
+        var expectedSuccess: String? {
+            self == .success ? "" : nil
+        }
+    }
+
+    func testDeleteBatchDocumentsSuccess() {
+        performDeleteBatchDocumentsTest(
+            documentType: .success,
+            description: "Deleting Batch Of Documents with Success"
+        )
+    }
+
+    func testDeleteBatchDocumentsFailure() {
+        performDeleteBatchDocumentsTest(
+            documentType: .failure,
+            description: "Deleting Batch Of Documents with Failure"
+        )
+    }
+
+    func testDeleteBatchDocumentsErrorUnauthorizedDocuments() {
+        performDeleteBatchDocumentsTest(
+            documentType: .unauthorizedDocuments,
+            description: "Deleting Batch Of Documents with Error of unauthorized documents"
+        )
+    }
+
+    func testDeleteBatchDocumentsErrorNotFoundDocuments() {
+        performDeleteBatchDocumentsTest(
+            documentType: .notFoundDocuments,
+            description: "Deleting Batch Of Documents with Error of not found documents"
+        )
+    }
+
+    func testDeleteBatchDocumentsErrorMissingCompositeDocuments() {
+        performDeleteBatchDocumentsTest(
+            documentType: .missingCompositeDocuments,
+            description: "Deleting Batch Of Documents with Error of missing composite documents"
+        )
+    }
+
+    /// Helper Function for Delete Batch Documents Tests
+    private func performDeleteBatchDocumentsTest(
+        documentType: DeleteBatchDocumentType,
+        description: String
+    ) {
+        let expectation = self.expectation(description: description)
+        
+        let documentIds = documentType == .failure ? [] : [documentType.rawValue]
+        giniHealth.deleteDocuments(documentIds: documentIds) { result in
+            switch result {
+            case .success(let responseMessage):
+                XCTAssertEqual(responseMessage, documentType.expectedSuccess)
+            case .failure(let error):
+                let receivedError = error.unauthorizedDocuments ?? error.notFoundDocuments ?? error.missingCompositeDocuments
+                XCTAssertEqual(receivedError, documentType.expectedFailure)
+            }
+            expectation.fulfill()
+        }
+
+        waitForExpectations(timeout: 1, handler: nil)
+    }
+}
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthExtractionsHandlingTests.swift b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthExtractionsHandlingTests.swift
new file mode 100644
index 0000000000..7e9db6213e
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthExtractionsHandlingTests.swift
@@ -0,0 +1,311 @@
+import XCTest
+@testable import GiniHealthSDK
+@testable import GiniHealthAPILibrary
+@testable import GiniInternalPaymentSDK
+@testable import GiniUtilites
+
+final class GiniHealthExtractionsHandlingTests: XCTestCase {
+
+    var giniHealthAPI: GiniHealthAPI!
+    var giniHealth: GiniHealth!
+    private let versionAPI = 4
+
+    override func setUp() {
+        let sessionManagerMock = MockSessionManager()
+        let documentService = DefaultDocumentService(sessionManager: sessionManagerMock, apiVersion: versionAPI)
+        let paymentService = PaymentService(sessionManager: sessionManagerMock, apiVersion: versionAPI)
+        let clientConfigurationService = ClientConfigurationService(sessionManager: sessionManagerMock, apiVersion: versionAPI)
+        GiniHealthConfiguration.shared.clientConfiguration = nil
+        giniHealthAPI = GiniHealthAPI(documentService: documentService,
+                                      paymentService: paymentService,
+                                      clientConfigurationService: clientConfigurationService)
+        giniHealth = GiniHealth(giniApiLib: giniHealthAPI)
+    }
+
+    override func tearDown() {
+        giniHealthAPI = nil
+        giniHealth = nil
+        super.tearDown()
+    }
+
+    func testDocumentIsPayable() {
+        // When
+        let fileName = "extractionResultWithIBAN"
+        let extractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let extractions else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let extractionsResult = ExtractionResult(extractionsContainer: extractions)
+        let isPayable = extractionsResult.extractions.first(where: { $0.name == ExtractionType.paymentState.rawValue })?.value == GiniHealthSDK.PaymentState.payable.rawValue
+        // Then
+        XCTAssertEqual(isPayable, true)
+    }
+    
+    func testDocumentIsNotPayableSuccess() {
+        // When
+        let fileName = "extractionResultWithoutIBAN"
+        let extractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let extractions else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let extractionsResult = ExtractionResult(extractionsContainer: extractions)
+        let isPayable = extractionsResult.extractions.first(where: { $0.name == ExtractionType.paymentState.rawValue })?.value == GiniHealthSDK.PaymentState.payable.rawValue
+        // Then
+        XCTAssertEqual(isPayable, false)
+    }
+
+    func testCheckIfDocumentIsPayableSuccess() {
+        // Given
+        let fileName = "extractionResultWithIBAN"
+        let expectedExtractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let expectedExtractions else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let expectedExtractionsResult = GiniHealthSDK.ExtractionResult(extractionsContainer: expectedExtractions)
+        let expectedIsPayable = expectedExtractionsResult.extractions.first(where: { $0.name == "iban" })?.value.isNotEmpty
+
+        // When
+        let expectation = self.expectation(description: "Checking if document is payable")
+        var isDocumentPayable: Bool?
+        giniHealth.checkIfDocumentIsPayable(docId: MockSessionManager.payableDocumentID) { result in
+            switch result {
+            case .success(let isPayable):
+                isDocumentPayable = isPayable
+            case .failure(_):
+                isDocumentPayable = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertEqual(expectedIsPayable, isDocumentPayable)
+    }
+
+    func testCheckIfDocumentIsNotPayableSuccess() {
+        // Given
+        let fileName = "extractionResultWithIBAN"
+        let expectedExtractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let expectedExtractions else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let expectedExtractionsResult = ExtractionResult(extractionsContainer: expectedExtractions)
+        let expectedIsPayable = expectedExtractionsResult.extractions.first(where: { $0.name == "iban" })?.value.isEmpty
+
+        // When
+        let expectation = self.expectation(description: "Checking if document is not payable")
+        var isDocumentPayable: Bool?
+        giniHealth.checkIfDocumentIsPayable(docId: MockSessionManager.notPayableDocumentID) { result in
+            switch result {
+            case .success(let isPayable):
+                isDocumentPayable = isPayable
+            case .failure(_):
+                isDocumentPayable = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertEqual(expectedIsPayable, isDocumentPayable)
+    }
+
+    func testCheckIfDocumentIsPayableFailure() {
+        // When
+        let expectation = self.expectation(description: "Checking if request fails")
+        var isDocumentPayable: Bool?
+        giniHealth.checkIfDocumentIsPayable(docId: MockSessionManager.failurePayableDocumentID) { result in
+            switch result {
+            case .success(let isPayable):
+                isDocumentPayable = isPayable
+            case .failure(_):
+                isDocumentPayable = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNil(isDocumentPayable)
+    }
+
+    func testCheckIfDocumentContainMultipleInvoicesSuccess() {
+        // When
+        let expectation = self.expectation(description: "Checking if document contains multiple invoices")
+        var hasMultipleInvoices: Bool?
+        giniHealth.checkIfDocumentContainsMultipleInvoices(docId: MockSessionManager.notPayableDocumentID) { result in
+            switch result {
+            case .success(let containsMultipleDocs):
+                hasMultipleInvoices = containsMultipleDocs
+            case .failure(_):
+                hasMultipleInvoices = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 5, handler: nil)
+
+        // Then
+        XCTAssertEqual(true, hasMultipleInvoices)
+    }
+
+    func testCheckIfDocumentDontContainMultipleInvoicesSuccess() {
+        // When
+        let expectation = self.expectation(description: "Checking if document don't contain multiple invoices")
+        var hasMultipleInvoices: Bool?
+        giniHealth.checkIfDocumentContainsMultipleInvoices(docId: MockSessionManager.payableDocumentID) { result in
+            switch result {
+            case .success(let containsMultipleDocs):
+                    hasMultipleInvoices = containsMultipleDocs
+            case .failure(_):
+                    hasMultipleInvoices = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertEqual(false, hasMultipleInvoices)
+    }
+
+    func testCheckIfDocumentContainMultipleInvoicesFailure() {
+        // When
+        let expectation = self.expectation(description: "Checking if request fails")
+        var hasMultipleInvoices: Bool?
+        giniHealth.checkIfDocumentContainsMultipleInvoices(docId: MockSessionManager.failurePayableDocumentID) { result in
+            switch result {
+            case .success(let containsMultipleDocs):
+                hasMultipleInvoices = containsMultipleDocs
+            case .failure(_):
+                hasMultipleInvoices = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNil(hasMultipleInvoices)
+    }
+    
+    func testGetExtractionsSuccess() {
+        // Given
+        let fileName = "extractionsWithPayment"
+        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let expectedExtractionContainer else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).payment?.first ?? []
+
+        // When
+        let expectation = self.expectation(description: "Getting extractions")
+        var receivedExtractions: [GiniHealthSDK.Extraction]?
+        giniHealth.getExtractions(docId: MockSessionManager.extractionsWithPaymentDocumentID) { result in
+            switch result {
+            case .success(let extractions):
+                receivedExtractions = extractions
+            case .failure(_):
+                receivedExtractions = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedExtractions)
+        XCTAssertEqual(receivedExtractions?.count, expectedExtractions.count)
+    }
+    
+    func testGetExtractionsFailure() {
+        // When
+        let expectation = self.expectation(description: "Extraction failure")
+        var receivedExtractions: [GiniHealthSDK.Extraction]?
+        giniHealth.getExtractions(docId: MockSessionManager.failurePayableDocumentID) { result in
+            switch result {
+            case .success(let extractions):
+                receivedExtractions = extractions
+            case .failure(_):
+                receivedExtractions = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNil(receivedExtractions)
+    }
+
+    func testGetAllExtractionsSuccess() {
+        // Given
+        let fileName = "test_doctorsname"
+        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
+        guard let expectedExtractionContainer else {
+            XCTFail("Error loading file: `\(fileName).json`")
+            return
+        }
+        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).extractions
+
+        // When
+        let expectation = self.expectation(description: "Getting all extractions")
+        var receivedExtractions: [GiniHealthSDK.Extraction]?
+        giniHealth.getAllExtractions(docId: MockSessionManager.doctorsNameDocumentID) { result in
+            switch result {
+            case .success(let extractions):
+                receivedExtractions = extractions
+            case .failure(_):
+                receivedExtractions = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedExtractions)
+        XCTAssertEqual(receivedExtractions?.count, expectedExtractions.count)
+    }
+
+    func testGetAllExtractionsFailure() {
+        // When
+        let expectation = self.expectation(description: "Extraction failure")
+        var receivedExtractions: [GiniHealthSDK.Extraction]?
+        giniHealth.getAllExtractions(docId: MockSessionManager.failurePayableDocumentID) { result in
+            switch result {
+            case .success(let extractions):
+                receivedExtractions = extractions
+            case .failure(_):
+                receivedExtractions = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNil(receivedExtractions)
+    }
+
+    func testGetDoctorsNameExtractionsSuccess() {
+        // Given
+        let expectedDoctorName = "DR. SOMMER TEAM"
+
+        // When
+        let expectation = self.expectation(description: "Getting doctor name extractions")
+        var receivedDoctorExtraction: GiniHealthSDK.Extraction?
+        giniHealth.getAllExtractions(docId: MockSessionManager.doctorsNameDocumentID) { result in
+            switch result {
+            case .success(let extractions):
+                receivedDoctorExtraction = extractions.first(where: { $0.name == ExtractionType.doctorName.rawValue })
+            case .failure(_):
+                receivedDoctorExtraction = nil
+            }
+            expectation.fulfill()
+        }
+        waitForExpectations(timeout: 1, handler: nil)
+
+        // Then
+        XCTAssertNotNil(receivedDoctorExtraction)
+        XCTAssertEqual(receivedDoctorExtraction?.value, expectedDoctorName)
+    }
+}
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthTests.swift b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthTests.swift
index b448d0a4f8..c3ab48e9d8 100644
--- a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthTests.swift
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthTests.swift
@@ -23,6 +23,7 @@ final class GiniHealthTests: XCTestCase {
     }
 
     override func tearDown() {
+        giniHealthAPI = nil
         giniHealth = nil
         super.tearDown()
     }
@@ -38,7 +39,7 @@ final class GiniHealthTests: XCTestCase {
         XCTAssertEqual(GiniHealthConfiguration.shared, configuration)
     }
     
-    func testFetchBankingApps_Success() {
+    func testFetchBankingAppsSuccess() {
         // Given
         let expectedProviders: [GiniHealthSDK.PaymentProvider]? = loadProviders(fileName: "providers")
 
@@ -61,261 +62,8 @@ final class GiniHealthTests: XCTestCase {
         XCTAssertEqual(receivedProviders?.count, expectedProviders?.count)
         XCTAssertEqual(receivedProviders, expectedProviders)
     }
-
-    func testDocumentIsPayable() {
-        // When
-        let fileName = "extractionResultWithIBAN"
-        let extractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let extractions else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let extractionsResult = ExtractionResult(extractionsContainer: extractions)
-        let isPayable = extractionsResult.extractions.first(where: { $0.name == ExtractionType.paymentState.rawValue })?.value == GiniHealthSDK.PaymentState.payable.rawValue
-        // Then
-        XCTAssertEqual(isPayable, true)
-    }
-    
-    func testDocumentIsNotPayable_Success() {
-        // When
-        let fileName = "extractionResultWithoutIBAN"
-        let extractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let extractions else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let extractionsResult = ExtractionResult(extractionsContainer: extractions)
-        let isPayable = extractionsResult.extractions.first(where: { $0.name == ExtractionType.paymentState.rawValue })?.value == GiniHealthSDK.PaymentState.payable.rawValue
-        // Then
-        XCTAssertEqual(isPayable, false)
-    }
-
-    func testCheckIfDocumentIsPayable_Success() {
-        // Given
-        let fileName = "extractionResultWithIBAN"
-        let expectedExtractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let expectedExtractions else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let expectedExtractionsResult = GiniHealthSDK.ExtractionResult(extractionsContainer: expectedExtractions)
-        let expectedIsPayable = expectedExtractionsResult.extractions.first(where: { $0.name == "iban" })?.value.isNotEmpty
-
-        // When
-        let expectation = self.expectation(description: "Checking if document is payable")
-        var isDocumentPayable: Bool?
-        giniHealth.checkIfDocumentIsPayable(docId: MockSessionManager.payableDocumentID) { result in
-            switch result {
-            case .success(let isPayable):
-                isDocumentPayable = isPayable
-            case .failure(_):
-                isDocumentPayable = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertEqual(expectedIsPayable, isDocumentPayable)
-    }
-
-    func testCheckIfDocumentIsNotPayable_Success() {
-        // Given
-        let fileName = "extractionResultWithIBAN"
-        let expectedExtractions: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let expectedExtractions else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let expectedExtractionsResult = ExtractionResult(extractionsContainer: expectedExtractions)
-        let expectedIsPayable = expectedExtractionsResult.extractions.first(where: { $0.name == "iban" })?.value.isEmpty
-
-        // When
-        let expectation = self.expectation(description: "Checking if document is not payable")
-        var isDocumentPayable: Bool?
-        giniHealth.checkIfDocumentIsPayable(docId: MockSessionManager.notPayableDocumentID) { result in
-            switch result {
-            case .success(let isPayable):
-                isDocumentPayable = isPayable
-            case .failure(_):
-                isDocumentPayable = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertEqual(expectedIsPayable, isDocumentPayable)
-    }
-
-    func testCheckIfDocumentIsPayable_Failure() {
-        // When
-        let expectation = self.expectation(description: "Checking if request fails")
-        var isDocumentPayable: Bool?
-        giniHealth.checkIfDocumentIsPayable(docId: MockSessionManager.failurePayableDocumentID) { result in
-            switch result {
-            case .success(let isPayable):
-                isDocumentPayable = isPayable
-            case .failure(_):
-                isDocumentPayable = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNil(isDocumentPayable)
-    }
-
-    func testCheckIfDocumentContainMultipleInvoices_Success() {
-        // When
-        let expectation = self.expectation(description: "Checking if document contains multiple invoices")
-        var hasMultipleInvoices: Bool?
-        giniHealth.checkIfDocumentContainsMultipleInvoices(docId: MockSessionManager.notPayableDocumentID) { result in
-            switch result {
-            case .success(let containsMultipleDocs):
-                hasMultipleInvoices = containsMultipleDocs
-            case .failure(_):
-                hasMultipleInvoices = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 5, handler: nil)
-
-        // Then
-        XCTAssertEqual(true, hasMultipleInvoices)
-    }
-
-    func testCheckIfDocumentDontContainMultipleInvoices_Success() {
-        // When
-        let expectation = self.expectation(description: "Checking if document don't contain multiple invoices")
-        var hasMultipleInvoices: Bool?
-        giniHealth.checkIfDocumentContainsMultipleInvoices(docId: MockSessionManager.payableDocumentID) { result in
-            switch result {
-            case .success(let containsMultipleDocs):
-                    hasMultipleInvoices = containsMultipleDocs
-            case .failure(_):
-                    hasMultipleInvoices = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertEqual(false, hasMultipleInvoices)
-    }
-
-    func testCheckIfDocumentContainMultipleInvoices_Failure() {
-        // When
-        let expectation = self.expectation(description: "Checking if request fails")
-        var hasMultipleInvoices: Bool?
-        giniHealth.checkIfDocumentContainsMultipleInvoices(docId: MockSessionManager.failurePayableDocumentID) { result in
-            switch result {
-            case .success(let containsMultipleDocs):
-                hasMultipleInvoices = containsMultipleDocs
-            case .failure(_):
-                hasMultipleInvoices = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNil(hasMultipleInvoices)
-    }
-
-    func testPollDocument_Success() {
-        // Given
-        let healthDocument: GiniHealthAPILibrary.Document = GiniHealthSDKTests.load(fromFile: "document1")!
-        let expectedDocument: GiniHealthSDK.Document? = GiniHealthSDK.Document(healthDocument: healthDocument)
-
-        // When
-        let expectation = self.expectation(description: "Polling document")
-        var receivedDocument: GiniHealthSDK.Document?
-        giniHealth.pollDocument(docId: MockSessionManager.payableDocumentID) { result in
-            switch result {
-            case .success(let document):
-                receivedDocument = document
-            case .failure(_):
-                receivedDocument = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedDocument)
-        XCTAssertEqual(receivedDocument, expectedDocument)
-    }
-    
-    func testPollDocument_Failure() {
-        // When
-        let expectation = self.expectation(description: "Polling failure document")
-        var receivedDocument: GiniHealthSDK.Document?
-        giniHealth.pollDocument(docId: MockSessionManager.missingDocumentID) { result in
-            switch result {
-            case .success(let document):
-                receivedDocument = document
-            case .failure(_):
-                receivedDocument = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNil(receivedDocument)
-    }
     
-    func testGetExtractions_Success() {
-        // Given
-        let fileName = "extractionsWithPayment"
-        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let expectedExtractionContainer else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).payment?.first ?? []
-
-        // When
-        let expectation = self.expectation(description: "Getting extractions")
-        var receivedExtractions: [GiniHealthSDK.Extraction]?
-        giniHealth.getExtractions(docId: MockSessionManager.extractionsWithPaymentDocumentID) { result in
-            switch result {
-            case .success(let extractions):
-                receivedExtractions = extractions
-            case .failure(_):
-                receivedExtractions = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedExtractions)
-        XCTAssertEqual(receivedExtractions?.count, expectedExtractions.count)
-    }
-    
-    func testGetExtractions_Failure() {
-        // When
-        let expectation = self.expectation(description: "Extraction failure")
-        var receivedExtractions: [GiniHealthSDK.Extraction]?
-        giniHealth.getExtractions(docId: MockSessionManager.failurePayableDocumentID) { result in
-            switch result {
-            case .success(let extractions):
-                receivedExtractions = extractions
-            case .failure(_):
-                receivedExtractions = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNil(receivedExtractions)
-    }
-    
-    func testCreatePaymentRequest_Success() {
+    func testCreatePaymentRequestSuccess() {
         // Given
         let expectedPaymentRequestID = MockSessionManager.paymentRequestId
 
@@ -339,7 +87,7 @@ final class GiniHealthTests: XCTestCase {
         XCTAssertEqual(receivedRequestId, expectedPaymentRequestID)
     }
     
-    func testOpenLink_Success() {
+    func testOpenLinkSuccess() {
         let mockUIApplication = MockUIApplication(canOpen: true)
         let urlOpener = URLOpener(mockUIApplication)
         let waitForWebsiteOpen = expectation(description: "Link was opened")
@@ -352,7 +100,7 @@ final class GiniHealthTests: XCTestCase {
         waitForExpectations(timeout: 0.1, handler: nil)
     }
     
-    func testOpenLink_Failure() {
+    func testOpenLinkFailure() {
         let mockUIApplication = MockUIApplication(canOpen: false)
         let urlOpener = URLOpener(mockUIApplication)
         let waitForWebsiteOpen = expectation(description: "Link was not opened")
@@ -364,165 +112,6 @@ final class GiniHealthTests: XCTestCase {
 
         waitForExpectations(timeout: 0.1, handler: nil)
     }
-    
-    func testSetDocumentForReview_Success() {
-        // Given
-        let fileName = "extractionsWithPayment"
-        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let expectedExtractionContainer else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).payment?.first ?? []
-
-        // When
-        let expectation = self.expectation(description: "Setting document for review")
-        var receivedExtractions: [GiniHealthSDK.Extraction]?
-        giniHealth.setDocumentForReview(documentId: MockSessionManager.extractionsWithPaymentDocumentID) { result in
-            switch result {
-            case .success(let extractions):
-                receivedExtractions = extractions
-            case .failure(_):
-                receivedExtractions = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedExtractions)
-        XCTAssertEqual(receivedExtractions?.count, expectedExtractions.count)
-    }
-    
-    func testFetchDataForReview_Success() {
-        // Given
-        let fileName = "extractionsWithPayment"
-        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let expectedExtractionContainer else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).payment?.first ?? []
-        let documentFileName = "document4"
-
-        let healthDocument: GiniHealthAPILibrary.Document = GiniHealthSDKTests.load(fromFile: documentFileName)!
-        let expectedDocument: GiniHealthSDK.Document? = GiniHealthSDK.Document(healthDocument: healthDocument)
-
-        guard let expectedDocument else {
-            XCTFail("Error loading file: `\(documentFileName).json`")
-            return
-        }
-        let expectedDatForReview = DataForReview(document: expectedDocument, extractions: expectedExtractions)
-
-        // When
-        let expectation = self.expectation(description: "Fetching data for review")
-        var receivedDataForReview: DataForReview?
-        giniHealth.fetchDataForReview(documentId: MockSessionManager.extractionsWithPaymentDocumentID) { result in
-            switch result {
-            case .success(let dataForReview):
-                receivedDataForReview = dataForReview
-            case .failure(_):
-                receivedDataForReview = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedDataForReview)
-        XCTAssertEqual(receivedDataForReview?.document, expectedDatForReview.document)
-        XCTAssertEqual(receivedDataForReview?.extractions.count, expectedDatForReview.extractions.count)
-    }
-    
-    func testFetchDataForReview_Failure() {
-        // When
-        let expectation = self.expectation(description: "Failure fetching data for review")
-        var receivedError: GiniHealthError?
-        giniHealth.fetchDataForReview(documentId: MockSessionManager.missingDocumentID) { result in
-            switch result {
-            case .success(_):
-                receivedError = nil
-            case .failure(let error):
-                receivedError = error
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedError)
-    }
-
-    func testGetAllExtractions_Success() {
-        // Given
-        let fileName = "test_doctorsname"
-        let expectedExtractionContainer: GiniHealthSDK.ExtractionsContainer? = GiniHealthSDKTests.load(fromFile: fileName)
-        guard let expectedExtractionContainer else {
-            XCTFail("Error loading file: `\(fileName).json`")
-            return
-        }
-        let expectedExtractions: [GiniHealthSDK.Extraction] = ExtractionResult(extractionsContainer: expectedExtractionContainer).extractions
-
-        // When
-        let expectation = self.expectation(description: "Getting all extractions")
-        var receivedExtractions: [GiniHealthSDK.Extraction]?
-        giniHealth.getAllExtractions(docId: MockSessionManager.doctorsNameDocumentID) { result in
-            switch result {
-            case .success(let extractions):
-                receivedExtractions = extractions
-            case .failure(_):
-                receivedExtractions = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedExtractions)
-        XCTAssertEqual(receivedExtractions?.count, expectedExtractions.count)
-    }
-
-    func testGetAlllExtractions_Failure() {
-        // When
-        let expectation = self.expectation(description: "Extraction failure")
-        var receivedExtractions: [GiniHealthSDK.Extraction]?
-        giniHealth.getAllExtractions(docId: MockSessionManager.failurePayableDocumentID) { result in
-            switch result {
-            case .success(let extractions):
-                receivedExtractions = extractions
-            case .failure(_):
-                receivedExtractions = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNil(receivedExtractions)
-    }
-
-    func testGetDoctorsNameExtractions_Success() {
-        // Given
-        let expectedDoctorName = "DR. SOMMER TEAM"
-
-        // When
-        let expectation = self.expectation(description: "Getting doctor name extractions")
-        var receivedDoctorExtraction: GiniHealthSDK.Extraction?
-        giniHealth.getAllExtractions(docId: MockSessionManager.doctorsNameDocumentID) { result in
-            switch result {
-            case .success(let extractions):
-                receivedDoctorExtraction = extractions.first(where: { $0.name == ExtractionType.doctorName.rawValue })
-            case .failure(_):
-                receivedDoctorExtraction = nil
-            }
-            expectation.fulfill()
-        }
-        waitForExpectations(timeout: 1, handler: nil)
-
-        // Then
-        XCTAssertNotNil(receivedDoctorExtraction)
-        XCTAssertEqual(receivedDoctorExtraction?.value, expectedDoctorName)
-    }
 
     func testLoadClientConfigurationFromFile() {
         // Given
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/MockHelpers/MockSessionManager.swift b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/MockHelpers/MockSessionManager.swift
index 895ff68261..13aee678a8 100644
--- a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/MockHelpers/MockSessionManager.swift
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/MockHelpers/MockSessionManager.swift
@@ -95,27 +95,15 @@ final class MockSessionManager: SessionManagerProtocol {
             case .extractions(let documentId):
                 switch (documentId, resource.params.method) {
                 case (MockSessionManager.payableDocumentID, .get):
-                    let extractionResults: ExtractionsContainer? = load(fromFile: "extractionResultWithIBAN")
-                    if let extractionResults = extractionResults as? T.ResponseType {
-                        completion(.success(extractionResults))
-                    }
+                    handleExtractionResults(fromFile: "extractionResultWithIBAN", completion: completion)
                 case (MockSessionManager.notPayableDocumentID, .get):
-                    let extractionResults: ExtractionsContainer? = load(fromFile: "extractionResultWithoutIBAN")
-                    if let extractionResults = extractionResults as? T.ResponseType {
-                        completion(.success(extractionResults))
-                    }
+                    handleExtractionResults(fromFile: "extractionResultWithoutIBAN", completion: completion)
                 case (MockSessionManager.failurePayableDocumentID, .get):
                     completion(.failure(.noResponse))
                 case (MockSessionManager.extractionsWithPaymentDocumentID, .get):
-                    let extractionResults: ExtractionsContainer? = load(fromFile: "extractionsWithPayment")
-                    if let extractionResults = extractionResults as? T.ResponseType {
-                        completion(.success(extractionResults))
-                    }
+                    handleExtractionResults(fromFile: "extractionsWithPayment", completion: completion)
                 case (MockSessionManager.doctorsNameDocumentID, .get):
-                    let extractionResults: ExtractionsContainer? = load(fromFile: "test_doctorsname")
-                    if let extractionResults = extractionResults as? T.ResponseType {
-                        completion(.success(extractionResults))
-                    }
+                    handleExtractionResults(fromFile: "test_doctorsname", completion: completion)
                 default:
                     fatalError("Document id not found in tests")
                 }
@@ -124,11 +112,66 @@ final class MockSessionManager: SessionManagerProtocol {
                 if let clientConfiguration = clientConfiguration as? T.ResponseType {
                     completion(.success(clientConfiguration))
                 }
+            case .documents(_, _):
+                guard let bodyStringArray = decodeBody(from: resource.params.body) else { return }
+                handleBodyStringArray(bodyStringArray, completion: completion)
             default:
                 let error = GiniError.unknown(response: nil, data: nil)
                 completion(.failure(error))
             }
         }
     }
+
+    /// Helper function to decode body
+    private func decodeBody(from body: Data?) -> [String]? {
+        guard let body = body else { return nil }
+        return try? JSONDecoder().decode([String].self, from: body)
+    }
+
+    /// Helper function to handle the body array types
+    private func handleBodyStringArray<ResponseType>(
+        _ bodyStringArray: [String],
+        completion: @escaping GiniHealthAPILibrary.CompletionResult<ResponseType>
+    ) {
+        switch bodyStringArray {
+        case [""]:
+            if let emptyResponse = "" as? ResponseType {
+                completion(.success(emptyResponse))
+            }
+        case ["unauthorizedDocuments"]:
+            handleDeleteDocumentsError(fromFile: "unauthorizedDocumentsError", completion: completion)
+        case ["notFoundDocuments"]:
+            handleDeleteDocumentsError(fromFile: "notFoundDocumentsError", completion: completion)
+        case ["missingCompositeDocuments"]:
+            handleDeleteDocumentsError(fromFile: "missingCompositeDocumentsError", completion: completion)
+        default:
+            completion(.failure(GiniError.unknown(response: nil, data: nil)))
+        }
+    }
+
+    /// Helper function to load and encode errors
+    private func handleDeleteDocumentsError<ResponseType>(
+        fromFile fileName: String,
+        completion: @escaping GiniHealthAPILibrary.CompletionResult<ResponseType>
+    ) {
+        guard let extractionResults: GiniCustomError = load(fromFile: fileName),
+              let jsonData = try? JSONEncoder().encode(extractionResults) else {
+            return
+        }
+
+        let error = GiniError.customError(response: nil, data: jsonData)
+        completion(.failure(error))
+    }
+
+    /// Helper function to handle extraction results
+    private func handleExtractionResults<ResponseType>(
+        fromFile fileName: String,
+        completion: @escaping GiniHealthAPILibrary.CompletionResult<ResponseType>
+    ) {
+        let extractionResults: ExtractionsContainer? = load(fromFile: fileName)
+        if let extractionResults = extractionResults as? ResponseType {
+            completion(.success(extractionResults))
+        }
+    }
 }
 
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/missingCompositeDocumentsError.json b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/missingCompositeDocumentsError.json
new file mode 100644
index 0000000000..5784011981
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/missingCompositeDocumentsError.json
@@ -0,0 +1,3 @@
+{
+    "missingCompositeDocuments": ["missingCompositeDocuments"]
+}
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/notFoundDocumentsError.json b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/notFoundDocumentsError.json
new file mode 100644
index 0000000000..465633d6c0
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/notFoundDocumentsError.json
@@ -0,0 +1,3 @@
+{
+    "notFoundDocuments": ["notFoundDocuments"]
+}
diff --git a/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/unauthorizedDocumentsError.json b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/unauthorizedDocumentsError.json
new file mode 100644
index 0000000000..daf0ac0eec
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/Resources/unauthorizedDocumentsError.json
@@ -0,0 +1,3 @@
+{
+    "unauthorizedDocuments": ["unauthorizedDocuments"]
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
index 917bfdc0a8..3d0292eea0 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
@@ -61,7 +61,9 @@ final class AppCoordinator: Coordinator {
     private var documentMetadata: GiniHealthSDK.Document.Metadata?
     private let documentMetadataBranchId = "GiniHealthExampleIOS"
     private let documentMetadataAppFlowKey = "AppFlow"
-    
+
+    private let hardcodedInvoicesController: HardcodedInvoicesControllerProtocol = HardcodedInvoicesController()
+
     init(window: UIWindow) {
         self.window = window
         print("------------------------------------\n\n",
@@ -165,7 +167,7 @@ final class AppCoordinator: Coordinator {
                                                                                             secret: clientPassword,
                                                                                             domain: clientDomain),
                                                                                             documentMetadata: metadata,
-                                                        hardcodedInvoicesController: HardcodedInvoicesController())
+                                                        hardcodedInvoicesController: hardcodedInvoicesController)
         
         screenAPICoordinator.delegate = self
         
@@ -321,7 +323,7 @@ final class AppCoordinator: Coordinator {
         let invoicesListCoordinator = InvoicesListCoordinator()
         DispatchQueue.main.async {
             invoicesListCoordinator.start(documentService: self.health.documentService,
-                                          hardcodedInvoicesController: HardcodedInvoicesController(),
+                                          hardcodedInvoicesController: self.hardcodedInvoicesController,
                                           health: self.health,
                                           invoices: invoices)
             self.add(childCoordinator: invoicesListCoordinator)
@@ -371,7 +373,11 @@ extension AppCoordinator: SelectAPIViewControllerDelegate {
 
 extension AppCoordinator: ScreenAPICoordinatorDelegate {
     func presentError(title: String, message: String) {
-        self.rootViewController.showError(title, message: message)
+        if let presentedVC = rootViewController.presentedViewController {
+            presentedVC.showError(title, message: message)
+        } else {
+            rootViewController.showError(title, message: message)
+        }
     }
     
     func screenAPI(coordinator: ScreenAPICoordinator, didFinish: ()) {
@@ -428,4 +434,29 @@ extension AppCoordinator: DebugMenuDelegate {
         giniHealthConfiguration.customLocalization = localization
         health.setConfiguration(giniHealthConfiguration)
     }
+
+    func didTapOnBulkDelete() {
+        let documentsToDeleteIds = Array(hardcodedInvoicesController.getInvoicesWithExtractions()
+            .map { $0.documentId }
+            .prefix(Constants.numberOfDocumentsToBeDeleted)) // Number of documents to delete
+
+        health.deleteDocuments(documentIds: documentsToDeleteIds) { [weak self] result in
+            switch result {
+            case .success(_):
+                self?.hardcodedInvoicesController.deleteDocuments(withIds: documentsToDeleteIds)
+                let successMessage = "Successfully deleted documents with: \(documentsToDeleteIds)"
+                GiniUtilites.Log(successMessage, event: .success)
+                self?.presentError(title: "Success", message: successMessage)
+            case .failure(let failure):
+                GiniUtilites.Log("Failed to delete documents with error: \(failure.message)", event: .error)
+                self?.presentError(title: "Error", message: failure.message)
+            }
+        }
+    }
+}
+
+extension AppCoordinator {
+    enum Constants {
+        static let numberOfDocumentsToBeDeleted = 2
+    }
 }
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/DebugMenuViewController.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/DebugMenuViewController.swift
index 5378ba3881..630e8547d6 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/DebugMenuViewController.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/DebugMenuViewController.swift
@@ -19,6 +19,7 @@ enum SwitchType {
 protocol DebugMenuDelegate: AnyObject {
     func didChangeSwitchValue(type: SwitchType, isOn: Bool)
     func didPickNewLocalization(localization: GiniLocalization)
+    func didTapOnBulkDelete()
 }
 
 class DebugMenuViewController: UIViewController {
@@ -58,6 +59,8 @@ class DebugMenuViewController: UIViewController {
     private var closeButtonSwitch: UISwitch!
     private lazy var closeButtonRow: UIStackView = stackView(axis: .horizontal, subviews: [closeButtonOptionLabel, closeButtonSwitch])
 
+    private lazy var bulkDeleteButton: UIButton = actionButton(for: .deleteDocuments)
+
     weak var delegate: DebugMenuDelegate?
 
     init(showReviewScreen: Bool,
@@ -92,7 +95,7 @@ class DebugMenuViewController: UIViewController {
         view.backgroundColor = UIColor(named: "background")
 
         let spacer = UIView()
-        let mainStackView = stackView(axis: .vertical, subviews: [titleLabel, localizationRow, reviewScreenRow, bottomPaymentComponentEditableRow, closeButtonRow, spacer])
+        let mainStackView = stackView(axis: .vertical, subviews: [titleLabel, localizationRow, reviewScreenRow, bottomPaymentComponentEditableRow, closeButtonRow, bulkDeleteButton, spacer])
         view.addSubview(mainStackView)
 
         NSLayoutConstraint.activate([
@@ -104,12 +107,31 @@ class DebugMenuViewController: UIViewController {
             localizationRow.heightAnchor.constraint(equalToConstant: rowHeight),
             reviewScreenRow.heightAnchor.constraint(equalToConstant: rowHeight),
             bottomPaymentComponentEditableRow.heightAnchor.constraint(equalToConstant: rowHeight),
-            closeButtonRow.heightAnchor.constraint(equalToConstant: rowHeight)
+            closeButtonRow.heightAnchor.constraint(equalToConstant: rowHeight),
+            bulkDeleteButton.heightAnchor.constraint(equalToConstant: rowHeight)
         ])
     }
 }
 
 private extension DebugMenuViewController {
+    enum DebugMenuActionType {
+        case deleteDocuments
+        // Add more cases if needed
+        var title: String {
+            switch self {
+            case .deleteDocuments:
+                return "Bulk Delete"
+            }
+        }
+
+        var selector: Selector {
+            switch self {
+            case .deleteDocuments:
+                return #selector(deleteDocumentsTapped)
+            }
+        }
+    }
+
     func rowTitle(_ title: String) -> UILabel {
         let label = UILabel()
         label.translatesAutoresizingMaskIntoConstraints = false
@@ -132,6 +154,23 @@ private extension DebugMenuViewController {
         mySwitch.isOn = isOn
         return mySwitch
     }
+
+    func actionButton(for type: DebugMenuActionType) -> UIButton {
+        let button = UIButton(type: .system)
+        button.translatesAutoresizingMaskIntoConstraints = false
+        button.setTitle(type.title, for: .normal)
+        button.setTitleColor(.white, for: .normal)
+        button.backgroundColor = .systemBlue
+        button.layer.cornerRadius = 8
+        button.layer.masksToBounds = true
+        button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+        button.addTarget(self, action: type.selector, for: .touchUpInside)
+        return button
+    }
+
+    @objc private func deleteDocumentsTapped() {
+        delegate?.didTapOnBulkDelete()
+    }
 }
 
 extension DebugMenuViewController: UIPickerViewDelegate, UIPickerViewDataSource {
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift
index 374aea64fb..8762fe9807 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift
@@ -15,6 +15,7 @@ protocol HardcodedInvoicesControllerProtocol: AnyObject {
     func getInvoicesWithExtractions() -> [DocumentWithExtractions]
     func appendInvoiceWithExtractions(invoice: DocumentWithExtractions)
     func updateDocumentExtractions(documentId: String, extractions: ExtractionResult)
+    func deleteDocuments(withIds documentIds: [String])
 }
 
 final class HardcodedInvoicesController: HardcodedInvoicesControllerProtocol {
@@ -77,6 +78,20 @@ final class HardcodedInvoicesController: HardcodedInvoicesControllerProtocol {
         }
         storeInvoicesWithExtractions(invoices: invoices)
     }
+
+    func deleteDocuments(withIds documentIds: [String]) {
+        var invoices = getInvoicesWithExtractions()
+        let initialCount = invoices.count
+
+        // Remove invoices that match the given document IDs
+        invoices.removeAll { documentIds.contains($0.documentId) }
+
+        // Store the updated list back to UserDefaults
+        storeInvoicesWithExtractions(invoices: invoices)
+
+        let deletedCount = initialCount - invoices.count
+        Log("Successfully deleted \(deletedCount) invoices", event: .success)
+    }
 }
 
 extension HardcodedInvoicesController {
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift
index f189752fd7..e1b02e6913 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift
@@ -36,7 +36,7 @@ final class ScreenAPICoordinator: NSObject, Coordinator, GiniHealthTrackingDeleg
     var visionDocuments: [GiniCaptureDocument]?
     var visionConfiguration: GiniConfiguration
     private var captureExtractedResults: [GiniBankAPILibrary.Extraction] = []
-    private var hardcodedInvoicesController: HardcodedInvoicesController
+    private var hardcodedInvoicesController: HardcodedInvoicesControllerProtocol
     
     // {extraction name} : {entity name}
     private let editableSpecificExtractions = ["paymentRecipient" : "companyname", "paymentReference" : "reference", "paymentPurpose" : "text", "iban" : "iban", "bic" : "bic", "amountToPay" : "amount"]
@@ -45,7 +45,7 @@ final class ScreenAPICoordinator: NSObject, Coordinator, GiniHealthTrackingDeleg
          importedDocuments documents: [GiniCaptureDocument]?,
          client: GiniHealthAPILibrary.Client,
          documentMetadata: GiniHealthAPILibrary.Document.Metadata?,
-         hardcodedInvoicesController: HardcodedInvoicesController) {
+         hardcodedInvoicesController: HardcodedInvoicesControllerProtocol) {
         visionConfiguration = configuration
         visionDocuments = documents
         self.client = client


Reviews:
user: login: ValentinaIancu-Gini
body: @razvancapra Nice work. Thank you!
I finished reviewing this pr. Can you please check my suggestions and clarification questions? 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:09:40Z
user: login: zladzeyka
body: Thank you @razvancapra. Please find a couple of suggestions from my side 🙏
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T18:08:35Z
user: login: razvancapra
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:37:57Z
user: login: razvancapra
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:41:24Z
user: login: razvancapra
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:48:06Z
user: login: ValentinaIancu-Gini
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T09:00:25Z
user: login: ValentinaIancu-Gini
body: @razvancapra thank you!
Besides the documentation style, the pr is looking good.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T10:04:28Z
user: login: zladzeyka
body: Thank you Razvan, 2 small clarification questions from my side
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T15:19:30Z
user: login: razvancapra
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T08:44:27Z
user: login: zladzeyka
body: 👍🏻thank you
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-13T10:47:24Z

Review comments:
diff_hunk: @@ -13,7 +13,20 @@ public protocol GiniErrorProtocol {
     var data: Data? { get }
 }
 
-public enum GiniError: Error, GiniErrorProtocol, Equatable {
+public protocol GiniCustomErrorProtocol {
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/GiniError.swift
user: login: ValentinaIancu-Gini
body: [Suggestion]: Can you please add inline documentation of this public protocol?
created_at: 2025-03-11T13:52:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -317,12 +317,18 @@ private extension SessionManager {
                 return
             }
 
-            guard let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
-                  errorInfo["error"] == "invalid_grant" else {
-                completion(.failure(.badRequest(response: response, data: data)))
+            if let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
+                  errorInfo["error"] == "invalid_request" {
+                completion(.failure(.unauthorized(response: response, data: data)))
                 return
             }
-            completion(.failure(.unauthorized(response: response, data: data)))
+
+            if let jsonObject = try? JSONSerialization.jsonObject(with: responseData, options: []), let _ = jsonObject as? [String: Any] {
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift
user: login: ValentinaIancu-Gini
body: [Suggestion]I think we can write the if conditions like this:
```
if let jsonObject = try? JSONSerialization.jsonObject(with: responseData) as? [String: Any] {
    
```
created_at: 2025-03-11T13:55:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -149,7 +157,31 @@ extension DocumentService {
             }
         })
     }
-    
+
+    func deleteDocuments(resourceHandler: ResourceDataHandler<APIResource<String>>,
+                         with ids: [String],
+                         completion: @escaping CompletionResult<String>) {
+        guard let json = try? JSONEncoder().encode(ids) else {
+            assertionFailure("The document ids provided cannot be encoded")
+            return
+        }
+
+        let resource = APIResource<String>(method: .documents(limit: nil, offset: nil),
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift
user: login: ValentinaIancu-Gini
body: [Clarification] Are we sure that limit and offset should be nil here?
created_at: 2025-03-11T13:59:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +14,7 @@ public protocol GiniErrorProtocol {
     var data: Data? { get }
 }
 
-public enum GiniError: Error, GiniErrorProtocol, Equatable {
+public enum GiniError: Error, GiniErrorProtocol, GiniCustomErrorProtocol, Equatable {
path: HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Adapter/GiniError.swift
user: login: ValentinaIancu-Gini
body: [Suggestion] Can we please add inline documentation for this enum?
created_at: 2025-03-11T14:01:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,6 +301,16 @@ public final class DefaultDocumentService: DefaultDocumentServiceProtocol {
     public func file(urlString: String, completion: @escaping CompletionResult<Data>){
         file(urlString: urlString, resourceHandler: sessionManager.download, completion: completion)
     }
+
+    /**
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift
user: login: ValentinaIancu-Gini
body: [Suggestion] Can you please use the same documentation style like below:
```
/**
     Delete a batch of documents

     - Parameters:
        - documentIds: An array of document ids to be deleted
        - completion: An action for deleting a batch of documents. Result is a value that represents either a success or a failure, including an associated value in each case.
        In success it includes a success message
        In case of failure error from the server side.

     */
 ```
created_at: 2025-03-11T14:02:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,6 +94,14 @@ public protocol DocumentService: AnyObject {
      */
     
     func file(urlString: String, completion: @escaping CompletionResult<Data>)
+
+    /**
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift
user: login: ValentinaIancu-Gini
body: [Suggestion] Can you please use the same documentation style as below:
```
/**
     Delete a batch of documents

     - Parameters:
        - documentIds: An array of document ids to be deleted
        - completion: An action for deleting a batch of documents. Result is a value that represents either a success or a failure, including an associated value in each case.
        In success it includes a success message
        In case of failure error from the server side.

     */
 ```
created_at: 2025-03-11T14:03:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -560,4 +560,85 @@ final class GiniHealthTests: XCTestCase {
         XCTAssertEqual(clientConfiguration.communicationTone, expectedDefaultComunicationTone)
         XCTAssertEqual(clientConfiguration.ingredientBrandType, expectedDefaultBrandType)
     }
+
+    // MARK: - Delete Batch Of Documents Tests
+    /// Enum for Delete  Batch Document Types
+    private enum DeleteBatchDocumentType: String {
+        case unauthorizedDocuments
+        case notFoundDocuments
+        case missingCompositeDocuments
+        case success = ""
+        case failure
+
+        /// Expected failure response if applicable
+        var expectedFailure: [String]? {
+            switch self {
+            case .unauthorizedDocuments, .notFoundDocuments, .missingCompositeDocuments:
+                return [self.rawValue]
+            default:
+                return nil
+            }
+        }
+
+        /// Expected success response if applicable
+        var expectedSuccess: String? {
+            self == .success ? "" : nil
+        }
+    }
+
+    func testDeleteBatchDocumentsSuccess() {
+        performDeleteBatchDocumentsTest(
+            documentType: .success,
+            description: "Deleting Batch Of Documents with Success"
+        )
+    }
+
+    func testDeleteBatchDocumentsFailure() {
+        performDeleteBatchDocumentsTest(
+            documentType: .failure,
+            description: "Deleting Batch Of Documents with Failure"
+        )
+    }
+
+    func testDeleteBatchDocumentsErrorUnauthorizedDocuments() {
+        performDeleteBatchDocumentsTest(
+            documentType: .unauthorizedDocuments,
+            description: "Deleting Batch Of Documents with Error of unauthorized documents"
+        )
+    }
+
+    func testDeleteBatchDocumentsErrorNotFoundDocuments() {
+        performDeleteBatchDocumentsTest(
+            documentType: .notFoundDocuments,
+            description: "Deleting Batch Of Documents with Error of not found documents"
+        )
+    }
+
+    func testDeleteBatchDocumentsErrorMissingCompositeDocuments() {
+        performDeleteBatchDocumentsTest(
+            documentType: .missingCompositeDocuments,
+            description: "Deleting Batch Of Documents with Error of missing composite documents"
+        )
+    }
+
+    /// Helper Function for Delete Batch Documents Tests
+    private func performDeleteBatchDocumentsTest(
+        documentType: DeleteBatchDocumentType,
+        description: String
+    ) {
+        let expectation = self.expectation(description: description)
+
+        giniHealth.deleteBatchOfDocuments(documentIds: documentType == .failure ? [] : [documentType.rawValue]) { result in
path: HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthTests.swift
user: login: ValentinaIancu-Gini
body: [Suggestion] Can you please extract `documentType == .failure ? [] : [documentType.rawValue]` into a local property?
created_at: 2025-03-11T14:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -428,4 +430,21 @@ extension AppCoordinator: DebugMenuDelegate {
         giniHealthConfiguration.customLocalization = localization
         health.setConfiguration(giniHealthConfiguration)
     }
+
+    func didTapOnBulkDelete() {
+        let documentsToDeleteIds = Array(hardcodedInvoicesController.getInvoicesWithExtractions()
+            .map { $0.documentId }
+            .prefix(2)) // Number of documents to delete
path: HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
user: login: ValentinaIancu-Gini
body: [Clarification] Is it okay to hardcode this here?
created_at: 2025-03-11T14:06:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -557,6 +557,29 @@ public struct DataForReview {
         }
     }
 
+    /**
+     Delete a batch of documents
+
+     - Parameters:
+        - documentIds: An array of document ids to be deleted
+        - completion: An action for deleting a batch of documents. Result is a value that represents either a success or a failure, including an associated value in each case.
+        In success it includes a success message
+        In case of failure error from the server side.
+
+     */
+    public func deleteBatchOfDocuments(documentIds: [String], completion: @escaping (Result<String, GiniError>) -> Void) {
path: HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
user: login: zladzeyka
body: ```suggestion
    public func deleteDocuments(documentIds: [String], completion: @escaping (Result<String, GiniError>) -> Void) {
```
created_at: 2025-03-11T18:05:48Z
author_association: COLLABORATOR
diff_hunk: @@ -149,7 +157,31 @@ extension DocumentService {
             }
         })
     }
-    
+
+    func deleteDocuments(resourceHandler: ResourceDataHandler<APIResource<String>>,
+                         with ids: [String],
+                         completion: @escaping CompletionResult<String>) {
+        guard let json = try? JSONEncoder().encode(ids) else {
+            assertionFailure("The document ids provided cannot be encoded")
+            return
+        }
+
+        let resource = APIResource<String>(method: .documents(limit: nil, offset: nil),
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DocumentService.swift
user: login: razvancapra
body: Yes, since we're using the `.documents` endpoint with the `.delete` HTTP method, the `limit` and `offset` values are not needed.
created_at: 2025-03-12T08:37:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,6 +301,16 @@ public final class DefaultDocumentService: DefaultDocumentServiceProtocol {
     public func file(urlString: String, completion: @escaping CompletionResult<Data>){
         file(urlString: urlString, resourceHandler: sessionManager.download, completion: completion)
     }
+
+    /**
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift
user: login: razvancapra
body: We're using a completely different documentation style throughout the file. Maybe we should keep it consistent for now. Or should I update the entire file to match a uniform documentation style?
created_at: 2025-03-12T08:41:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -428,4 +430,21 @@ extension AppCoordinator: DebugMenuDelegate {
         giniHealthConfiguration.customLocalization = localization
         health.setConfiguration(giniHealthConfiguration)
     }
+
+    func didTapOnBulkDelete() {
+        let documentsToDeleteIds = Array(hardcodedInvoicesController.getInvoicesWithExtractions()
+            .map { $0.documentId }
+            .prefix(2)) // Number of documents to delete
path: HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
user: login: razvancapra
body: I've moved the value to an external `Constants` definition, but yes, we intentionally want a hardcoded value here since it's only for testing. The goal is to ensure bulk deletion works with a maximum of 2 documents per request.
created_at: 2025-03-12T08:48:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,6 +301,16 @@ public final class DefaultDocumentService: DefaultDocumentServiceProtocol {
     public func file(urlString: String, completion: @escaping CompletionResult<Data>){
         file(urlString: urlString, resourceHandler: sessionManager.download, completion: completion)
     }
+
+    /**
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/DefaultDocumentService.swift
user: login: ValentinaIancu-Gini
body: Please check this with @zladzeyka 
created_at: 2025-03-12T09:00:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -560,4 +560,86 @@ final class GiniHealthTests: XCTestCase {
         XCTAssertEqual(clientConfiguration.communicationTone, expectedDefaultComunicationTone)
         XCTAssertEqual(clientConfiguration.ingredientBrandType, expectedDefaultBrandType)
     }
+
+    // MARK: - Delete Batch Of Documents Tests
path: HealthSDK/GiniHealthSDK/Tests/GiniHealthSDKTests/GiniHealthTests.swift
user: login: zladzeyka
body: [Clarification]: Can we extract some tests in new file, so far we have >=500 lines?
created_at: 2025-03-12T15:17:41Z
author_association: COLLABORATOR
diff_hunk: @@ -317,12 +317,18 @@ private extension SessionManager {
                 return
             }
 
-            guard let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
-                  errorInfo["error"] == "invalid_grant" else {
-                completion(.failure(.badRequest(response: response, data: data)))
+            if let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift
user: login: zladzeyka
body: [Clarification]: @razvancapra are you sure that we need those changes? If so, could you please explain why?
created_at: 2025-03-12T15:18:56Z
author_association: COLLABORATOR
diff_hunk: @@ -317,12 +317,18 @@ private extension SessionManager {
                 return
             }
 
-            guard let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
-                  errorInfo["error"] == "invalid_grant" else {
-                completion(.failure(.badRequest(response: response, data: data)))
+            if let errorInfo = try? JSONDecoder().decode([String: String].self, from: responseData),
path: HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/SessionManager.swift
user: login: razvancapra
body: Previously, we only received a `String: String` value, which allowed us to parse the error and handle unauthorized errors accordingly. Now, the response includes a `String: [String]` format, requiring a new approach to parsing and passing errors to the handler as custom errors. I’ve maintained the existing logic while extending it to handle the new error types, such as `missingDocuments` and `notFoundDocuments` arrays.
created_at: 2025-03-13T08:44:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-14T13:07:41Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=gini_gini-mobile-ios&pullRequest=788) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [4.7% Duplication on New Code](https://sonarcloud.io/component_measures?id=gini_gini-mobile-ios&pullRequest=788&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=gini_gini-mobile-ios&pullRequest=788)


author_association: NONE