diff --git a/Jenkinsfile b/Jenkinsfile
index 53f1a6106b8993756de54919198d2ab335df96ae..8216d104532320e6d2938732a454fa3dec5d6ef2 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -125,7 +125,7 @@
             readTrusted 'pom.xml'
             sh 'make generate-spec'
             withCredentials([string(credentialsId: 'sonarcloud-token-git-eca-rest-api', variable: 'SONAR_TOKEN')]) {
-              sh 'mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -B -Dsonar.login=${SONAR_TOKEN}'
+              sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.login=${SONAR_TOKEN} -Dmaven.test.skip=true'
             }
             stash name: "target", includes: "target/**/*"
           }
diff --git a/pom.xml b/pom.xml
index fd2d12a4155bca867a0eb362688d1626457f273c..0905d4abd609365d071ba3068286704938c56fbf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
   <artifactId>git-eca</artifactId>
   <version>1.1.0</version>
   <properties>
-    <eclipse-api-version>0.7.4</eclipse-api-version>
+    <eclipse-api-version>0.7.6-SNAPSHOT</eclipse-api-version>
     <compiler-plugin.version>3.8.1</compiler-plugin.version>
     <maven.compiler.parameters>true</maven.compiler.parameters>
     <maven.compiler.source>11</maven.compiler.source>
@@ -14,7 +14,7 @@
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
     <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
-    <quarkus.platform.version>2.14.2.Final</quarkus.platform.version>
+    <quarkus.platform.version>2.16.3.Final</quarkus.platform.version>
     <surefire-plugin.version>2.22.1</surefire-plugin.version>
     <auto-value.version>1.8.2</auto-value.version>
     <org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
@@ -24,7 +24,6 @@
     <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
     <sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
     <sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath>
-    <sonar.host.url>https://sonarcloud.io</sonar.host.url>
     <sonar.organization>eclipse-foundation-it</sonar.organization>
     <sonar.projectKey>eclipse-foundation-it_git-eca-rest-api</sonar.projectKey>
     <sonar.projectName>Git ECA REST API</sonar.projectName>
@@ -63,6 +62,11 @@
       <artifactId>quarkus-persistence</artifactId>
       <version>${eclipse-api-version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.eclipsefoundation</groupId>
+      <artifactId>quarkus-efservices</artifactId>
+      <version>${eclipse-api-version}</version>
+    </dependency>
     <dependency>
       <groupId>io.quarkus</groupId>
       <artifactId>quarkus-resteasy</artifactId>
@@ -87,7 +91,6 @@
     <dependency>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcpkix-jdk15on</artifactId>
-      <version>1.70</version>
     </dependency>
 
     <dependency>
@@ -129,18 +132,6 @@
       <scope>provided</scope>
     </dependency>
 
-    <!-- Third-party reqs -->
-    <dependency>
-      <groupId>com.github.scribejava</groupId>
-      <artifactId>scribejava-apis</artifactId>
-      <version>6.4.1</version>
-    </dependency>
-    <!-- Caching -->
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-    </dependency>
-
     <!-- Test requirements -->
     <dependency>
       <groupId>io.quarkus</groupId>
diff --git a/spec/openapi.yaml b/spec/openapi.yaml
index c1b7bcecb9aee9d78b10dee01e9c84d817e4f699..906bde10bb40c02ec735c55ae7dfc8fa980908a5 100644
--- a/spec/openapi.yaml
+++ b/spec/openapi.yaml
@@ -2,6 +2,10 @@ openapi: "3.1.0"
 info:
   version: 1.1.0
   title: Eclipse Foundation Git ECA API
+  description: Collection of API endpoints used in the validation and management of external Git services, such as Gitlab and Github.
+  contact:
+    name: IT support
+    url: https://gitlab.eclipse.org/eclipsefdn/it/api/git-eca-rest-api/-/issues
   license:
     name: Eclipse Public License - 2.0
     url: https://www.eclipse.org/legal/epl-2.0/
@@ -11,10 +15,15 @@ servers:
 tags:
   - name: ECA Validation
     description: Definitions in relation to the validation of Git commits through ECA signage
+  - name: Reports
+    description: Reports on metadata associated with Git systems managed by the Eclipse Foundation
+  - name: Integration Webhooks
+    description: Endpoints related to binding to external Git services through a webhook
 
 paths:
   /eca:
     post:
+      operationId: validate
       tags:
         - ECA Validation
       summary: ECA validation
@@ -25,29 +34,36 @@ paths:
             schema:
               $ref: "#/components/schemas/ValidationRequest"
       responses:
-        200:
+        "200":
           description: Success
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/ValidationResponse"
-        500:
+        "500":
           description: Error while retrieving data
-
   /eca/status/{fingerprint}:
+    parameters:
+       - name: fingerprint
+         in: path
+         description: Unique ID for the request group
+         required: true
+         schema:
+            type: string
     get:
+      operationId: getCommitValidation
       tags:
-        - ECA Validation Status
+        - ECA Validation
       summary: Historic ECA validation status
       description: Returns a set of validation messages for the given unique fingerprint
       responses:
-        200:
+        "200":
           description: Success
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/CommitValidationStatuses"
-        500:
+        "500":
           description: Error while retrieving data
 
   /eca/status/{fingerprint}/ui:
@@ -59,34 +75,41 @@ paths:
          schema:
             type: string
     get:
+      operationId: getCommitValidationUI
+      tags:
+        - ECA Validation
       summary: Historic ECA validation status in a HTML format
       description: Returns an HTMl page containing validation messages
       responses:
-        200:
+        "200":
           description: Success. An HTML page containing status info
-        404:
+        "404":
           description: Not Found
-        500:
+        "500":
           description: Error while retrieving data
 
   /eca/lookup:
     get:
+      operationId: getUserStatus
+      tags:
+        - ECA Validation
       summary: User status lookup
       description: Returns wether or not the user has a signed ECA
       responses:
-        200:
+        "200":
           description: Success
-        403:
+        "403":
           description: User exists with no ECA
-        404:
+        "404":
           description: User not found
-        500:
+        "500":
           description: Error while retrieving data
 
   /webhooks/github:
     post:
+      operationId: processGithubWebhook
       tags:
-        - Github validation processing
+        - Integration Webhooks
       summary: Github incoming hook event processing
       description: Process incoming pull request hook events from Github
       parameters:
@@ -111,9 +134,9 @@ paths:
             schema:
               $ref: "#/components/schemas/GithubWebhookEvent"
       responses:
-        200:
+        "200":
           description: Success
-        500:
+        "500":
           description: Error while processing data
   /webhooks/github/revalidate/{fingerprint}:
     parameters:
@@ -124,8 +147,9 @@ paths:
          schema:
             type: string
     post:
+      operationId: revalidateWebhookRequest
       tags:
-        - Github validation processing
+        - Integration Webhooks
       summary: Gitlab webhook revalidation request
       description: Process incoming system hooks from GitLab
       requestBody:
@@ -134,20 +158,21 @@ paths:
             schema:
               $ref: '#/components/schemas/RevalidationRequest'
       responses:
-        200:
+        "200":
           description: Success
-        400:
+        "400":
           description: Bad request
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/Error"
-        404:
+        "404":
           description: Not found
   /webhooks/gitlab/system:
     post:
+      operationId: processGitlabHook
       tags:
-        - Gitlab system event processing
+        - Integration Webhooks
       summary: Gitlab event processing
       description: Process incoming system hooks from GitLab
       parameters:
@@ -162,9 +187,9 @@ paths:
             schema:
               $ref: "#/components/schemas/SystemHook"
       responses:
-        200:
+        "200":
           description: Success
-        500:
+        "500":
           description: Error while processing data
 
   /reports/gitlab/private-projects:
@@ -194,26 +219,27 @@ paths:
         schema:
           type: string
     get:
+      operationId: getPrivateProjectEvents
       tags:
-        - Private project event report
+        - Reports
       summary: Gitlab private project event report
       description: Returns list of private project events using desired filters
       responses:
-        200:
+        "200":
           description: Success
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/PrivateProjectEvents"
-        400:
+        "400":
           description: Bad Request - invalid non-null prams
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/Error"
-        401:
+        "401":
           description: Unauthorized - invalid key
-        500:
+        "500":
           description: Error while processing request
 
 components:
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java
deleted file mode 100644
index 9e95121240aa50c101b224e1ddff06251bd0cfa3..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2022 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.api;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.ws.rs.BeanParam;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Response;
-
-import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
-import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
-import org.jboss.resteasy.annotations.GZIP;
-
-/**
- * Interface for interacting with the PMI Projects API. Used to link Git
- * repos/projects with an Eclipse project to validate committer access.
- * 
- * @author Martin Lowe
- *
- */
-@ApplicationScoped
-@Path("/api")
-@RegisterRestClient
-@GZIP
-public interface ProjectsAPI {
-
-	/**
-	 * Retrieves all projects with the given repo URL.
-	 * 
-	 * @param repoUrl the target repos URL
-	 * @return a list of Eclipse Foundation projects.
-	 */
-	@GET
-	@Path("projects")
-	Response getProjects(@BeanParam BaseAPIParameters baseParams);
-
-	@GET
-	@Path("interest-groups")
-	@Produces("application/json")
-	Response getInterestGroups(@BeanParam BaseAPIParameters params);
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java
deleted file mode 100644
index e70f830625227182bdd2af7faadd6dc385bbc79e..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.eclipsefoundation.git.eca.api.models;
-
-import java.util.List;
-
-import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject;
-import org.eclipsefoundation.git.eca.api.models.Project.User;
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-import com.google.auto.value.AutoValue;
-
-@AutoValue
-@JsonDeserialize(builder = AutoValue_InterestGroupData.Builder.class)
-public abstract class InterestGroupData {
-    public abstract String getId();
-    public abstract String getTitle();
-    public abstract String getLogo();
-    public abstract String getState();
-    public abstract String getProjectId();
-    public abstract Descriptor getDescription();
-    public abstract Descriptor getScope();
-    public abstract List<User> getLeads();
-    public abstract List<User> getParticipants();
-    public abstract GitlabProject getGitlab();
-
-    public static Builder builder() {
-        return new AutoValue_InterestGroupData.Builder();
-    }
-
-    @AutoValue.Builder
-    @JsonPOJOBuilder(withPrefix = "set")
-    public abstract static class Builder {
-        public abstract Builder setId(String id);
-        public abstract Builder setTitle(String title);
-        public abstract Builder setGitlab(GitlabProject gitlab);
-        public abstract Builder setLogo(String logo);
-        public abstract Builder setState(String state);
-        public abstract Builder setProjectId(String foundationdbProjectId);
-        public abstract Builder setDescription(Descriptor description);
-        public abstract Builder setScope(Descriptor scope);
-        public abstract Builder setLeads(List<User> leads);
-        public abstract Builder setParticipants(List<User> participants);
-        public abstract InterestGroupData build();
-    }
-
-    @AutoValue
-    @JsonDeserialize(builder = AutoValue_InterestGroupData_Descriptor.Builder.class)
-    public abstract static class Descriptor {
-        public abstract String getSummary();
-        public abstract String getFull();
-
-        public static Builder builder() {
-            return new AutoValue_InterestGroupData_Descriptor.Builder();
-        }
-
-        @AutoValue.Builder
-        @JsonPOJOBuilder(withPrefix = "set")
-        public abstract static class Builder {
-            public abstract Builder setSummary(String summary);
-            public abstract Builder setFull(String full);
-            public abstract Descriptor build();
-        }
-    }
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java
deleted file mode 100644
index 7b30c1be33557fbbf24efc8c0bf4957355b86c50..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.api.models;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-import com.google.auto.value.AutoValue;
-import com.google.auto.value.extension.memoized.Memoized;
-
-/**
- * Represents a project in the Eclipse API, along with the users and repos that exist within the context of the project.
- * 
- * @author Martin Lowe
- *
- */
-@AutoValue
-@JsonDeserialize(builder = $AutoValue_Project.Builder.class)
-public abstract class Project {
-    public abstract String getProjectId();
-
-    public abstract String getName();
-
-    public abstract List<User> getCommitters();
-
-    public abstract List<User> getProjectLeads();
-
-    @Nullable
-    public abstract List<Repo> getRepos();
-
-    public abstract List<Repo> getGitlabRepos();
-
-    public abstract List<Repo> getGithubRepos();
-
-    public abstract List<Repo> getGerritRepos();
-
-    public abstract Object getSpecProjectWorkingGroup();
-
-    public abstract GitlabProject getGitlab();
-
-    public abstract GithubProject getGithub();
-
-    @Nullable
-    @Memoized
-    public String getSpecWorkingGroup() {
-        // stored as map as empty returns an array instead of a map
-        Object specProjectWorkingGroup = getSpecProjectWorkingGroup();
-        if (specProjectWorkingGroup instanceof Map) {
-            // we checked in line above that the map exists, so we can safely cast it
-            @SuppressWarnings("unchecked")
-            Object raw = ((Map<Object, Object>) specProjectWorkingGroup).get("id");
-            if (raw instanceof String) {
-                return (String) raw;
-            }
-        }
-        return null;
-    }
-
-    public static Builder builder() {
-        // adds empty lists as default values
-        return new AutoValue_Project.Builder()
-                .setRepos(new ArrayList<>())
-                .setCommitters(new ArrayList<>())
-                .setGithubRepos(new ArrayList<>())
-                .setGitlabRepos(new ArrayList<>())
-                .setGerritRepos(new ArrayList<>());
-    }
-
-    @AutoValue.Builder
-    @JsonPOJOBuilder(withPrefix = "set")
-    public abstract static class Builder {
-        public abstract Builder setProjectId(String projectId);
-
-        public abstract Builder setName(String name);
-
-        public abstract Builder setProjectLeads(List<User> projectLeads);
-
-        public abstract Builder setCommitters(List<User> committers);
-
-        public abstract Builder setRepos(@Nullable List<Repo> repos);
-
-        public abstract Builder setGitlabRepos(List<Repo> gitlabRepos);
-
-        public abstract Builder setGithubRepos(List<Repo> githubRepos);
-
-        public abstract Builder setGerritRepos(List<Repo> gerritRepos);
-
-        public abstract Builder setSpecProjectWorkingGroup(Object specProjectWorkingGroup);
-
-        public abstract Builder setGitlab(GitlabProject gitlab);
-
-        public abstract Builder setGithub(GithubProject github);
-
-        public abstract Project build();
-    }
-
-    @AutoValue
-    @JsonDeserialize(builder = AutoValue_Project_User.Builder.class)
-    public abstract static class User {
-        public abstract String getUsername();
-
-        public abstract String getUrl();
-
-        public static Builder builder() {
-            return new AutoValue_Project_User.Builder();
-        }
-
-        @AutoValue.Builder
-        @JsonPOJOBuilder(withPrefix = "set")
-        public abstract static class Builder {
-            public abstract Builder setUsername(String username);
-
-            public abstract Builder setUrl(String url);
-
-            public abstract User build();
-        }
-    }
-
-    @AutoValue
-    @JsonDeserialize(builder = AutoValue_Project_GitlabProject.Builder.class)
-    public abstract static class GitlabProject {
-        public abstract String getProjectGroup();
-
-        public abstract List<String> getIgnoredSubGroups();
-
-        public static Builder builder() {
-            return new AutoValue_Project_GitlabProject.Builder();
-        }
-
-        @AutoValue.Builder
-        @JsonPOJOBuilder(withPrefix = "set")
-        public abstract static class Builder {
-            public abstract Builder setProjectGroup(String projectGroup);
-
-            public abstract Builder setIgnoredSubGroups(List<String> ignoredSubGroups);
-
-            public abstract GitlabProject build();
-        }
-    }
-
-    @AutoValue
-    @JsonDeserialize(builder = AutoValue_Project_GithubProject.Builder.class)
-    public abstract static class GithubProject {
-        public abstract String getOrg();
-
-        public abstract List<String> getIgnoredRepos();
-
-        public static Builder builder() {
-            return new AutoValue_Project_GithubProject.Builder();
-        }
-
-        @AutoValue.Builder
-        @JsonPOJOBuilder(withPrefix = "set")
-        public abstract static class Builder {
-            public abstract Builder setOrg(String org);
-
-            public abstract Builder setIgnoredRepos(List<String> ignoredRepos);
-
-            public abstract GithubProject build();
-        }
-    }
-
-    /**
-     * Does not use autovalue as the value should be mutable.
-     * 
-     * @author Martin Lowe
-     *
-     */
-    public static class Repo {
-        private String url;
-
-        public String getUrl() {
-            return this.url;
-        }
-
-        public void setUrl(String url) {
-            this.url = url;
-        }
-    }
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java
index 0650a2e89a34d6c0c53f4fea19defbbac3c79a8f..eaa36924daf54e8bf40702963e0d780641b970ea 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java
@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
 
 import javax.ws.rs.core.MultivaluedMap;
 
-import org.eclipsefoundation.git.eca.api.models.Project;
+import org.eclipsefoundation.efservices.api.models.Project;
 import org.eclipsefoundation.git.eca.model.Commit;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..49c85eb298367dab23f4d75cf67083eceee346b9
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2023 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.git.eca.helper;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipsefoundation.core.service.CachingService;
+import org.eclipsefoundation.efservices.api.models.InterestGroup;
+import org.eclipsefoundation.efservices.api.models.Project;
+import org.eclipsefoundation.efservices.api.models.Project.GithubProject;
+import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant;
+import org.eclipsefoundation.efservices.services.ProjectService;
+import org.eclipsefoundation.git.eca.model.ValidationRequest;
+import org.eclipsefoundation.git.eca.namespace.ProviderType;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helps manage projects by providing filters on top of the generic service as well as operations like adapting interest
+ * groups to projects for easier processing.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@ApplicationScoped
+public final class ProjectHelper {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ProjectHelper.class);
+
+    @Inject
+    CachingService cache;
+    @Inject
+    ProjectService projects;
+
+    public List<Project> retrieveProjectsForRequest(ValidationRequest req) {
+        String repoUrl = req.getRepoUrl().getPath();
+        if (repoUrl == null) {
+            LOGGER.warn("Can not match null repo URL to projects");
+            return Collections.emptyList();
+        }
+        return retrieveProjectsForRepoURL(repoUrl, req.getProvider());
+    }
+
+    public List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider) {
+        if (repoUrl == null) {
+            LOGGER.warn("Can not match null repo URL to projects");
+            return Collections.emptyList();
+        }
+        // check for all projects that make use of the given repo
+        List<Project> availableProjects = getProjects();
+        if (availableProjects.isEmpty()) {
+            LOGGER.warn("Could not find any projects to match against");
+            return Collections.emptyList();
+        }
+        LOGGER.debug("Checking projects for repos that end with: {}", repoUrl);
+
+        String projectNamespace = URI.create(repoUrl).getPath().substring(1).toLowerCase();
+        // filter the projects based on the repo URL. At least one repo in project must
+        // match the repo URL to be valid
+        switch (provider) {
+            case GITLAB:
+                return availableProjects
+                        .stream()
+                        .filter(p -> projectNamespace.startsWith(p.getGitlab().getProjectGroup() + "/")
+                                && p.getGitlab().getIgnoredSubGroups().stream().noneMatch(sg -> projectNamespace.startsWith(sg + "/")))
+                        .collect(Collectors.toList());
+            case GITHUB:
+                return availableProjects
+                        .stream()
+                        .filter(p -> p.getGithubRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl))
+                                || (StringUtils.isNotBlank(p.getGithub().getOrg()) && projectNamespace.startsWith(p.getGithub().getOrg())
+                                        && p.getGithub().getIgnoredRepos().stream().noneMatch(repoUrl::endsWith)))
+                        .collect(Collectors.toList());
+            case GERRIT:
+                return availableProjects
+                        .stream()
+                        .filter(p -> p.getGerritRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl)))
+                        .collect(Collectors.toList());
+            default:
+                return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Retrieve cached and adapted projects list to avoid having to create all interest group adaptations on every request.
+     * 
+     * @return list of available projects or empty list if none found.
+     */
+    public List<Project> getProjects() {
+        return cache.get("all-combined", new MultivaluedMapImpl<>(), Project.class, () -> {
+            List<Project> availableProjects = projects.getAllProjects();
+            availableProjects.addAll(adaptInterestGroups(projects.getAllInterestGroups()));
+            return availableProjects;
+        }).orElseGet(Collections::emptyList);
+    }
+
+    private List<Project> adaptInterestGroups(List<InterestGroup> igs) {
+        return igs
+                .stream()
+                .map(ig -> Project
+                        .builder()
+                        .setProjectId(ig.getProjectId())
+                        .setGerritRepos(Collections.emptyList())
+                        .setGithubRepos(Collections.emptyList())
+                        .setGitlabRepos(Collections.emptyList())
+                        .setGitlab(ig.getGitlab())
+                        .setCommitters(ig
+                                .getParticipants()
+                                .stream()
+                                .map(p -> ProjectParticipant
+                                        .builder()
+                                        .setFullName(p.getFullName())
+                                        .setUrl(p.getUrl())
+                                        .setUsername(p.getUsername())
+                                        .build())
+                                .collect(Collectors.toList()))
+                        .setProjectLeads(ig
+                                .getLeads()
+                                .stream()
+                                .map(p -> ProjectParticipant
+                                        .builder()
+                                        .setFullName(p.getFullName())
+                                        .setUrl(p.getUrl())
+                                        .setUsername(p.getUsername())
+                                        .build())
+                                .collect(Collectors.toList()))
+                        .setContributors(Collections.emptyList())
+                        .setShortProjectId(ig.getShortProjectId())
+                        .setSlsaLevel("")
+                        .setSummary("")
+                        .setWebsiteUrl("")
+                        .setWebsiteRepo(Collections.emptyList())
+                        .setGitlab(ig.getGitlab())
+                        .setGithub(GithubProject.builder().setOrg("").setIgnoredRepos(Collections.emptyList()).build())
+                        .setWorkingGroups(Collections.emptyList())
+                        .setIndustryCollaborations(Collections.emptyList())
+                        .setReleases(Collections.emptyList())
+                        .setTopLevelProject("")
+                        .setUrl("")
+                        .setLogo(ig.getLogo())
+                        .setTags(Collections.emptyList())
+                        .setName(ig.getTitle())
+                        .setSpecProjectWorkingGroup(Collections.emptyMap())
+                        .build())
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java b/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java
deleted file mode 100644
index b89a37039614a0558baea6d562d24d45ddec0b0c..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.oauth;
-
-import com.github.scribejava.core.builder.api.DefaultApi20;
-import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
-import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
-
-/**
- * Wrapper around the OAuth API for Scribejava. Enables OAuth2.0 binding to the
- * Eclipse Foundation OAuth server.
- * 
- * @author Martin Lowe
- *
- */
-public class EclipseApi extends DefaultApi20 {
-
-    @Override
-    public String getAccessTokenEndpoint() {
-        return "https://accounts.eclipse.org/oauth2/token";
-    }
-
-    @Override
-    protected String getAuthorizationBaseUrl() {
-        return null;
-    }
-
-    @Override
-    public ClientAuthentication getClientAuthentication() {
-        return RequestBodyAuthenticationScheme.instance();
-    }
-
-    private static class InstanceHolder {
-        private static final EclipseApi INSTANCE = new EclipseApi();
-    }
-
-    public static EclipseApi instance() {
-        return InstanceHolder.INSTANCE;
-    }
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java
index b2bd837117ac7fcebcb08dd14666957095215c24..8c612938e0e53d23b617d0dce85f31af71ba696f 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java
@@ -27,14 +27,14 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import org.apache.commons.lang3.StringUtils;
+import org.eclipsefoundation.efservices.api.models.Project;
 import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest;
 import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest;
-import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
 import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
+import org.eclipsefoundation.git.eca.helper.ProjectHelper;
 import org.eclipsefoundation.git.eca.model.Commit;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
-import org.eclipsefoundation.git.eca.service.ProjectsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,7 +52,7 @@ public class StatusResource extends GithubAdjacentResource {
     private static final Logger LOGGER = LoggerFactory.getLogger(StatusResource.class);
 
     @Inject
-    ProjectsService projects;
+    ProjectHelper projects;
 
     // Qute templates, generates UI status page
     @Location("simple_fingerprint_ui")
diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java
index 90df077eab27d987906b83d80308fa98d3aae66c..a567bcda314ed2d588a4b3595fdbea22c72ff9ca 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java
@@ -29,11 +29,10 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipsefoundation.core.model.RequestWrapper;
 import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
+import org.eclipsefoundation.git.eca.helper.ProjectHelper;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.model.ValidationResponse;
 import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
-import org.eclipsefoundation.git.eca.service.InterestGroupService;
-import org.eclipsefoundation.git.eca.service.ProjectsService;
 import org.eclipsefoundation.git.eca.service.UserService;
 import org.eclipsefoundation.git.eca.service.ValidationService;
 import org.jboss.resteasy.annotations.jaxrs.QueryParam;
@@ -67,13 +66,11 @@ public class ValidationResource {
     @Inject
     CachingService cache;
     @Inject
-    ProjectsService projects;
+    ProjectHelper projects;
     @Inject
     UserService users;
     @Inject
     ValidationService validation;
-    @Inject
-    InterestGroupService ig;
 
     /**
      * Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java
deleted file mode 100644
index 8c2d182b10bf40196da01e8d4ce116339e34d6da..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.eclipsefoundation.git.eca.service;
-
-import java.util.List;
-
-import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
-import org.eclipsefoundation.git.eca.api.models.Project;
-
-/**
- * Service for retrieving and interacting with interest groups.
- * 
- * @author Martin Lowe
- *
- */
-public interface InterestGroupService {
-
-    /**
-     * Retrieve all available interest groups.
-     * 
-     * @return list of all available interest groups
-     */
-    List<InterestGroupData> getInterestGroups();
-
-    /**
-     * Converts interest groups into projects for processing downstream.
-     * 
-     * @param igs the interest groups to convert
-     * @return the converted interest groups
-     */
-    List<Project> adaptInterestGroups(List<InterestGroupData> igs);
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java
deleted file mode 100644
index 144601aeea1356da9a7a16ac5e20c3dd2cf633f3..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.service;
-
-/**
- * Used to generate OAuth tokens for use with internal services rather than
- * bolted on introspection. This is required over the (now deprecated) Elytron
- * plugin or the OIDC plugin as those plugins work with requests to validate
- * incoming rather than outgoing requests.
- * 
- * @author Martin Lowe
- *
- */
-public interface OAuthService {
-
-    /**
-     * Retrieve an access token for the service from the Eclipse API for internal
-     * usage.
-     * 
-     * @return current access token, or null if none could be retrieved for current
-     *         API credentials/settings.
-     */
-    String getToken();
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java
deleted file mode 100644
index 54c2199f477ffbe7b149a485617aaf04ad108b74..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.service;
-
-import java.util.List;
-
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.model.ValidationRequest;
-import org.eclipsefoundation.git.eca.namespace.ProviderType;
-
-/**
- * Intermediate layer between resource and API layers that handles retrieval of all projects and caching of that data
- * for availability purposes.
- * 
- * @author Martin Lowe
- *
- */
-public interface ProjectsService {
-
-    /**
-     * Retrieves all currently available projects from cache if available, otherwise going to API to retrieve a fresh copy
-     * of the data.
-     * 
-     * @return list of projects available from API.
-     */
-    List<Project> getProjects();
-
-    /**
-     * Retrieves projects valid for the current request, or an empty list if no data or matching project repos could be
-     * found.
-     *
-     * @param req the current request
-     * @return list of matching projects for the current request, or an empty list if none found.
-     */
-    List<Project> retrieveProjectsForRequest(ValidationRequest req);
-
-    /**
-     * Retrieves projects for given provider, using the repo URL to match to a stored repository.
-     * 
-     * @param repoUrl the repo URL to match
-     * @param provider the provider that is being served for the request.
-     * @return a list of matching projects, or an empty list if none are found.
-     */
-    List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider);
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java
index 06c01484868c90cb1d02529ff13e03990bf95226..1134f1d9a2ead650a6f9d65fb1521dc639af8f01 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java
@@ -13,8 +13,8 @@ package org.eclipsefoundation.git.eca.service;
 
 import java.util.List;
 
+import org.eclipsefoundation.efservices.api.models.Project;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.api.models.Project;
 
 public interface UserService {
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java
index 1f441ca0b783c220d831a8661b0734c336dbf081..b26a3fb815105bcd64ab33f3b4330fc74ddae726 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java
@@ -16,7 +16,7 @@ import java.security.NoSuchAlgorithmException;
 import java.util.List;
 
 import org.eclipsefoundation.core.model.RequestWrapper;
-import org.eclipsefoundation.git.eca.api.models.Project;
+import org.eclipsefoundation.efservices.api.models.Project;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.model.ValidationResponse;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java
index 45ad50178491186e32490dd7c31a49ed7ce24f21..de6836a89bc78fcc25411b8ec63aed7b4c47e5c0 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java
@@ -14,8 +14,8 @@ package org.eclipsefoundation.git.eca.service.impl;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Optional;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -28,11 +28,11 @@ import org.apache.commons.lang3.StringUtils;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.eclipsefoundation.core.service.CachingService;
+import org.eclipsefoundation.efservices.api.models.Project;
+import org.eclipsefoundation.efservices.services.DrupalTokenService;
 import org.eclipsefoundation.git.eca.api.AccountsAPI;
 import org.eclipsefoundation.git.eca.api.BotsAPI;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.service.OAuthService;
 import org.eclipsefoundation.git.eca.service.UserService;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.slf4j.Logger;
@@ -63,7 +63,7 @@ public class CachedUserService implements UserService {
     BotsAPI bots;
 
     @Inject
-    OAuthService oauth;
+    DrupalTokenService oauth;
     @Inject
     CachingService cache;
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java
deleted file mode 100644
index c56f56da55063036bd5f9b404ad66e6a763700af..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.eclipsefoundation.git.eca.service.impl;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.inject.Inject;
-
-import org.eclipse.microprofile.rest.client.inject.RestClient;
-import org.eclipsefoundation.core.service.APIMiddleware;
-import org.eclipsefoundation.core.service.CachingService;
-import org.eclipsefoundation.git.eca.api.ProjectsAPI;
-import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.service.InterestGroupService;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
-
-/**
- * Default implementation of interest group service.
- * 
- * @author Martin Lowe
- */
-@ApplicationScoped
-public class DefaultInterestGroupService implements InterestGroupService {
-
-    @Inject
-    APIMiddleware middleware;
-    @Inject
-    CachingService cache;
-
-    @Inject
-    @RestClient
-    ProjectsAPI api;
-
-    @Override
-    public List<InterestGroupData> getInterestGroups() {
-        return cache
-                .get("all", new MultivaluedMapImpl<>(), InterestGroupData.class,
-                        () -> middleware.getAll(api::getInterestGroups, InterestGroupData.class))
-                .orElse(Collections.emptyList());
-    }
-
-    @Override
-    public List<Project> adaptInterestGroups(List<InterestGroupData> igs) {
-        return igs
-                .stream()
-                .map(ig -> Project
-                        .builder()
-                        .setProjectId(ig.getProjectId())
-                        .setGerritRepos(Collections.emptyList())
-                        .setGithubRepos(Collections.emptyList())
-                        .setGitlabRepos(Collections.emptyList())
-                        .setRepos(Collections.emptyList())
-                        .setGitlab(ig.getGitlab())
-                        .setCommitters(ig.getParticipants())
-                        .setProjectLeads(ig.getLeads())
-                        .setName(ig.getTitle())
-                        .setSpecProjectWorkingGroup(Collections.emptyMap())
-                        .build())
-                .collect(Collectors.toList());
-    }
-
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java
deleted file mode 100644
index 713b0988bde06a0983067fb5be8b6c197a1ddc6a..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.service.impl;
-
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.PostConstruct;
-import javax.enterprise.context.ApplicationScoped;
-
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.eclipsefoundation.git.eca.oauth.EclipseApi;
-import org.eclipsefoundation.git.eca.service.OAuthService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.github.scribejava.core.builder.ServiceBuilder;
-import com.github.scribejava.core.model.OAuth2AccessToken;
-import com.github.scribejava.core.oauth.OAuth20Service;
-
-/**
- * Default implementation for requesting an OAuth request token. The reason that
- * this class is implemented over the other implementations baked into Quarkus
- * is to better bind to the Drupal OAuth APIs.
- * 
- * @author Martin Lowe
- *
- */
-@ApplicationScoped
-public class DefaultOAuthService implements OAuthService {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOAuthService.class);
-
-    @ConfigProperty(name = "oauth2.client-id")
-    String id;
-    @ConfigProperty(name = "oauth2.client-secret")
-    String secret;
-    @ConfigProperty(name = "oauth2.scope")
-    String scope;
-
-    // service reference (as we only need one)
-    private OAuth20Service service;
-
-    // token state vars
-    private long expirationTime;
-    private String accessToken;
-
-    /**
-     * Create an OAuth service reference.
-     */
-    @PostConstruct
-    void createServiceRef() {
-        this.service = new ServiceBuilder(id).apiSecret(secret).scope(scope).build(EclipseApi.instance());
-    }
-
-    @Override
-    public String getToken() {
-        // lock on the class instance to stop multiple threads from requesting new
-        // tokens at the same time
-        synchronized (this) {
-            if (accessToken == null || System.currentTimeMillis() >= expirationTime) {
-                // clear access token
-                this.accessToken = null;
-                try {
-                    OAuth2AccessToken requestToken = service.getAccessTokenClientCredentialsGrant();
-                    if (requestToken != null) {
-                        this.accessToken = requestToken.getAccessToken();
-                        this.expirationTime = System.currentTimeMillis()
-                                + TimeUnit.SECONDS.toMillis(requestToken.getExpiresIn().longValue());
-                    }
-                } catch (IOException e) {
-                    LOGGER.error("Issue communicating with OAuth server for authentication", e);
-                } catch (InterruptedException e) {
-                    LOGGER.error("Authentication communication was interrupted before completion", e);
-                    Thread.currentThread().interrupt();
-                } catch (ExecutionException e) {
-                    LOGGER.error("Error while retrieving access token for request", e);
-                }
-            }
-        }
-        return accessToken;
-    }
-
-}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java
index 647252e019e68fa4badc619a08dab7bc97e487d4..7c45cd7888ee0499982a6fa20dd208cc2b869fe0 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java
@@ -28,20 +28,19 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipsefoundation.core.helper.DateTimeHelper;
 import org.eclipsefoundation.core.model.RequestWrapper;
 import org.eclipsefoundation.core.service.CachingService;
+import org.eclipsefoundation.efservices.api.models.Project;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.dto.CommitValidationMessage;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatusGrouping;
 import org.eclipsefoundation.git.eca.helper.CommitHelper;
+import org.eclipsefoundation.git.eca.helper.ProjectHelper;
 import org.eclipsefoundation.git.eca.model.Commit;
 import org.eclipsefoundation.git.eca.model.GitUser;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.model.ValidationResponse;
 import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
 import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
-import org.eclipsefoundation.git.eca.service.InterestGroupService;
-import org.eclipsefoundation.git.eca.service.ProjectsService;
 import org.eclipsefoundation.git.eca.service.UserService;
 import org.eclipsefoundation.git.eca.service.ValidationService;
 import org.eclipsefoundation.persistence.dao.PersistenceDao;
@@ -66,9 +65,7 @@ public class DefaultValidationService implements ValidationService {
     List<String> allowListUsers;
 
     @Inject
-    ProjectsService projects;
-    @Inject
-    InterestGroupService ig;
+    ProjectHelper projects;
     @Inject
     UserService users;
 
@@ -386,7 +383,7 @@ public class DefaultValidationService implements ValidationService {
             if (p.getCommitters().stream().anyMatch(u -> u.getUsername().equals(user.getName()))) {
                 // check if the current project is a committer project, and if the user can
                 // commit to specs
-                if (p.getSpecWorkingGroup() != null && !user.getECA().getCanContributeSpecProject()) {
+                if (p.getSpecWorkingGroup().isPresent() && !user.getECA().getCanContributeSpecProject()) {
                     // set error + update response status
                     r
                             .addError(hash, String
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java
deleted file mode 100644
index 9b48cac9ea16cec2a7aa5faf46d72f4f96897a9b..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*	      Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.service.impl;
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-import javax.annotation.PostConstruct;
-import javax.enterprise.context.ApplicationScoped;
-import javax.inject.Inject;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.eclipse.microprofile.context.ManagedExecutor;
-import org.eclipse.microprofile.rest.client.inject.RestClient;
-import org.eclipsefoundation.core.service.APIMiddleware;
-import org.eclipsefoundation.core.service.CachingService;
-import org.eclipsefoundation.git.eca.api.ProjectsAPI;
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.model.ValidationRequest;
-import org.eclipsefoundation.git.eca.namespace.ProviderType;
-import org.eclipsefoundation.git.eca.service.InterestGroupService;
-import org.eclipsefoundation.git.eca.service.ProjectsService;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListenableFutureTask;
-
-import io.quarkus.runtime.Startup;
-
-/**
- * Projects service implementation that handles pagination of data manually, as well as makes use of a loading cache to
- * have data be always available with as little latency to the user as possible.
- *
- * @author Martin Lowe
- * @author Zachary Sabourin
- */
-@Startup
-@ApplicationScoped
-public class PaginationProjectsService implements ProjectsService {
-    private static final Logger LOGGER = LoggerFactory.getLogger(PaginationProjectsService.class);
-
-    @ConfigProperty(name = "eclipse.projects.precache.enabled", defaultValue = "true")
-    boolean isEnabled;
-    @ConfigProperty(name = "cache.pagination.refresh-frequency-seconds", defaultValue = "3600")
-    long refreshAfterWrite;
-
-    @Inject
-    @RestClient
-    ProjectsAPI projects;
-    @Inject
-    InterestGroupService ig;
-    @Inject
-    CachingService cache;
-    @Inject
-    APIMiddleware middleware;
-
-    @Inject
-    ManagedExecutor exec;
-    // this class has a separate cache as this data is long to load and should be
-    // always available.
-    LoadingCache<String, List<Project>> internalCache;
-
-    /**
-     * Initializes the internal loader cache and pre-populates the data with the one available key. If more than one key is
-     * used, eviction of previous results will happen and create degraded performance.
-     */
-    @PostConstruct
-    public void init() {
-        // set up the internal cache
-        this.internalCache = CacheBuilder
-                .newBuilder()
-                .maximumSize(1)
-                .refreshAfterWrite(refreshAfterWrite, TimeUnit.SECONDS)
-                .build(new CacheLoader<String, List<Project>>() {
-                    @Override
-                    public List<Project> load(String key) throws Exception {
-                        return getProjectsInternal();
-                    }
-
-                    /**
-                     * Implementation required for refreshAfterRewrite to be async rather than sync and blocking while awaiting for
-                     * expensive reload to complete.
-                     */
-                    @Override
-                    public ListenableFuture<List<Project>> reload(String key, List<Project> oldValue) throws Exception {
-                        ListenableFutureTask<List<Project>> task = ListenableFutureTask.create(() -> {
-                            LOGGER.debug("Retrieving new project data async");
-                            List<Project> newProjects = oldValue;
-                            try {
-                                newProjects = getProjectsInternal();
-                            } catch (Exception e) {
-                                LOGGER.error("Error while reloading internal projects data, data will be stale for current cycle.", e);
-                            }
-                            LOGGER.debug("Done refreshing project values");
-                            return newProjects;
-                        });
-                        // run the task using the Quarkus managed executor
-                        exec.execute(task);
-                        return task;
-                    }
-                });
-
-        if (isEnabled) {
-            // pre-cache the projects to reduce load time for other users
-            LOGGER.debug("Starting pre-cache of projects");
-            if (getProjects() == null) {
-                LOGGER.warn("Unable to populate pre-cache for Eclipse projects. Calls may experience degraded performance.");
-            }
-            LOGGER.debug("Completed pre-cache of projects assets");
-        }
-    }
-
-    @Override
-    public List<Project> getProjects() {
-        try {
-            return internalCache.get("projects");
-        } catch (ExecutionException e) {
-            throw new RuntimeException("Could not load Eclipse projects", e);
-        }
-    }
-
-    @Override
-    public List<Project> retrieveProjectsForRequest(ValidationRequest req) {
-        String repoUrl = req.getRepoUrl().getPath();
-        if (repoUrl == null) {
-            LOGGER.warn("Can not match null repo URL to projects");
-            return Collections.emptyList();
-        }
-        return retrieveProjectsForRepoURL(repoUrl, req.getProvider());
-    }
-
-    @Override
-    public List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider) {
-        if (repoUrl == null) {
-            LOGGER.warn("Can not match null repo URL to projects");
-            return Collections.emptyList();
-        }
-        // check for all projects that make use of the given repo
-        List<Project> availableProjects = getProjects();
-        availableProjects
-                .addAll(cache
-                        .get("all", new MultivaluedMapImpl<>(), Project.class, () -> ig.adaptInterestGroups(ig.getInterestGroups()))
-                        .orElse(Collections.emptyList()));
-        if (availableProjects.isEmpty()) {
-            LOGGER.warn("Could not find any projects to match against");
-            return Collections.emptyList();
-        }
-        LOGGER.debug("Checking projects for repos that end with: {}", repoUrl);
-
-        String projectNamespace = URI.create(repoUrl).getPath().substring(1).toLowerCase();
-        // filter the projects based on the repo URL. At least one repo in project must
-        // match the repo URL to be valid
-        switch (provider) {
-            case GITLAB:
-                return availableProjects
-                        .stream()
-                        .filter(p -> projectNamespace.startsWith(p.getGitlab().getProjectGroup() + "/")
-                                && p.getGitlab().getIgnoredSubGroups().stream().noneMatch(sg -> projectNamespace.startsWith(sg + "/")))
-                        .collect(Collectors.toList());
-            case GITHUB:
-                return availableProjects
-                        .stream()
-                        .filter(p -> p.getGithubRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl))
-                                || (StringUtils.isNotBlank(p.getGithub().getOrg()) && projectNamespace.startsWith(p.getGithub().getOrg())
-                                        && p.getGithub().getIgnoredRepos().stream().noneMatch(repoUrl::endsWith)))
-                        .collect(Collectors.toList());
-            case GERRIT:
-                return availableProjects
-                        .stream()
-                        .filter(p -> p.getGerritRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl)))
-                        .collect(Collectors.toList());
-            default:
-                return Collections.emptyList();
-        }
-    }
-
-    /**
-     * Logic for retrieving projects from API.
-     *
-     * @return list of projects for the cache
-     */
-    private List<Project> getProjectsInternal() {
-
-        return middleware.getAll(params -> projects.getProjects(params), Project.class).stream().map(proj -> {
-            proj.getGerritRepos().forEach(repo -> {
-                if (repo.getUrl().endsWith(".git")) {
-                    repo.setUrl(repo.getUrl().substring(0, repo.getUrl().length() - 4));
-                }
-            });
-            return proj;
-        }).collect(Collectors.toList());
-    }
-}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 33b399d082fff9e6e59e2faafe8a824a623e71e8..09df93d127b12af156beed3fced69e970defe46b 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,6 +1,8 @@
 quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".scope=javax.enterprise.context.ApplicationScoped
 quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".url=https://api.eclipse.org
-org.eclipsefoundation.git.eca.api.ProjectsAPI/mp-rest/url=https://projects.eclipse.org
+projects-api/mp-rest/url=https://projects.eclipse.org
+eclipse.cache.loading."projects".timeout=10
+eclipse.cache.loading."projects".start-at-boot=true
 org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org
 quarkus.rest-client."org.eclipsefoundation.git.eca.api.GitlabAPI".url=https://gitlab.eclipse.org/api/v4/
 
diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java
index 231b401c7987c5d425c752cd7eac239b9e356996..b0c25a00f0a05328e649b82786af7ff7ad0d3102 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java
@@ -18,9 +18,9 @@ import java.util.Optional;
 
 import javax.inject.Inject;
 
+import org.eclipsefoundation.efservices.api.models.Project;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.service.ProjectsService;
+import org.eclipsefoundation.git.eca.helper.ProjectHelper;
 import org.eclipsefoundation.git.eca.service.UserService;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -40,7 +40,7 @@ class CachedUserServiceTest {
     @Inject
     UserService users;
     @Inject
-    ProjectsService projects;
+    ProjectHelper projects;
 
     @Test
     void getUser_success() {
diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java
deleted file mode 100644
index 3ebbabcebc99b3cc6f9fa62801ff5139d66fafd8..0000000000000000000000000000000000000000
--- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
-package org.eclipsefoundation.git.eca.service.impl;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.api.models.Project.Repo;
-import org.eclipsefoundation.git.eca.service.ProjectsService;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import io.quarkus.test.junit.QuarkusTest;
-
-@QuarkusTest
-class PaginationProjectsServiceTest {
-  // get the projects service
-  @Inject
-  ProjectsService ps;
-
-  @Test
-  void validateGerritUrlScrubbed() {
-    // get all projects
-    List<Project> projectsAll = ps.getProjects();
-    // get projects that have gerrit repos
-    List<Project> projs = projectsAll
-        .stream()
-        .filter(p -> !p.getGerritRepos().isEmpty())
-        .collect(Collectors.toList());
-    // for all repos, check that none end with .git (this doesn't account for
-    // .git.git, but I assume that would be an entry error)
-    for (Project p : projs) {
-      for (Repo r : p.getGerritRepos()) {
-        Assertions.assertFalse(r.getUrl().endsWith(".git"), "Expected no URLs to end with '.git'");
-      }
-    }
-  }
-
-  @Test
-  void validateGithubUrlNotScrubbed() {
-    // get all projects
-    List<Project> projectsAll = ps.getProjects();
-    // get projects that have github repos
-    List<Project> projs = projectsAll
-        .stream()
-        .filter(p -> !p.getGithubRepos().isEmpty())
-        .collect(Collectors.toList());
-    // for all repos, check that at least one ends with .git
-    boolean foundGitSuffix = false;
-    for (Project p : projs) {
-      for (Repo r : p.getGithubRepos()) {
-        if (r.getUrl().endsWith(".git")) {
-          foundGitSuffix = true;
-        }
-      }
-    }
-    Assertions.assertTrue(foundGitSuffix, "Expected a URL to end with '.git'");
-  }
-
-  @Test
-  void validateGitlabUrlNotScrubbed() {
-    // get all projects
-    List<Project> projectsAll = ps.getProjects();
-    // get projects that have gitlab repos
-    List<Project> projs = projectsAll
-        .stream()
-        .filter(p -> !p.getGitlabRepos().isEmpty())
-        .collect(Collectors.toList());
-    // for all repos, check that at least one ends with .git
-    boolean foundGitSuffix = false;
-    for (Project p : projs) {
-      for (Repo r : p.getGitlabRepos()) {
-        if (r.getUrl().endsWith(".git")) {
-          foundGitSuffix = true;
-        }
-      }
-    }
-    Assertions.assertTrue(foundGitSuffix, "Expected a URL to end with '.git'");
-  }
-}
diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java
index 0528b1ea0497f4772e52e731a45f168b74736f6c..4be050fc7877141f12d23b884d579173386dfb62 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java
@@ -1,11 +1,11 @@
 /*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
+* Copyright (c) 2023 Eclipse Foundation.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
+* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
 *
 * SPDX-License-Identifier: EPL-2.0
 **********************************************************************/
@@ -14,24 +14,26 @@ package org.eclipsefoundation.git.eca.test.api;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
-import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.ws.rs.core.Response;
 
 import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
-import org.eclipsefoundation.git.eca.api.ProjectsAPI;
-import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
-import org.eclipsefoundation.git.eca.api.models.Project;
-import org.eclipsefoundation.git.eca.api.models.InterestGroupData.Descriptor;
-import org.eclipsefoundation.git.eca.api.models.Project.GithubProject;
-import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject;
-import org.eclipsefoundation.git.eca.api.models.Project.Repo;
-import org.eclipsefoundation.git.eca.api.models.Project.User;
+import org.eclipsefoundation.efservices.api.ProjectsAPI;
+import org.eclipsefoundation.efservices.api.models.GitlabProject;
+import org.eclipsefoundation.efservices.api.models.InterestGroup;
+import org.eclipsefoundation.efservices.api.models.InterestGroup.Descriptor;
+import org.eclipsefoundation.efservices.api.models.InterestGroup.InterestGroupParticipant;
+import org.eclipsefoundation.efservices.api.models.InterestGroup.Organization;
+import org.eclipsefoundation.efservices.api.models.InterestGroup.Resource;
+import org.eclipsefoundation.efservices.api.models.Project;
+import org.eclipsefoundation.efservices.api.models.Project.GithubProject;
+import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant;
+import org.eclipsefoundation.efservices.api.models.Project.Repo;
 
 import io.quarkus.test.Mock;
 
@@ -40,124 +42,169 @@ import io.quarkus.test.Mock;
 @ApplicationScoped
 public class MockProjectsAPI implements ProjectsAPI {
 
-    private List<Project> src;
+    private List<Project> projects;
 
-    @PostConstruct
-    public void build() {
-        this.src = new ArrayList<>();
+    public MockProjectsAPI() {
+        this.projects = new ArrayList<>();
 
         // sample repos
-        Repo r1 = new Repo();
-        r1.setUrl("http://www.github.com/eclipsefdn/sample");
-        Repo r2 = new Repo();
-        r2.setUrl("http://www.github.com/eclipsefdn/test");
-        Repo r3 = new Repo();
-        r3.setUrl("http://www.github.com/eclipsefdn/prototype.git");
-        Repo r5 = new Repo();
-        r5.setUrl("/gitroot/sample/gerrit.project.git");
-        Repo r6 = new Repo();
-        r6.setUrl("/gitroot/sample/gerrit.other-project");
-        Repo r7 = new Repo();
-        r7.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git");
-        Repo r8 = new Repo();
-        r8.setUrl("https://gitlab.eclipse.org/eclipse/dash-second/dash.handbook.test");
-
-        // sample users, correlates to users in Mock projects API
-        User u1 = User.builder().setUrl("").setUsername("da_wizz").build();
-        User u2 = User.builder().setUrl("").setUsername("grunter").build();
-
-        // projects
-        Project p1 = Project
-                .builder()
+        Repo r1 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/sample").build();
+        Repo r2 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/test").build();
+        Repo r3 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/prototype.git").build();
+        Repo r4 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/tck-proto").build();
+        Repo r5 = Repo.builder().setUrl("/gitroot/sample/gerrit.project.git").build();
+        Repo r6 = Repo.builder().setUrl("/gitroot/sample/gerrit.other-project").build();
+        Repo r7 = Repo.builder().setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git").build();
+        Repo r8 = Repo.builder().setUrl("https://gitlab.eclipse.org/eclipse/dash-second/dash.handbook.test").build();
+
+        // sample users
+        ProjectParticipant u1 = ProjectParticipant.builder().setUrl("").setUsername("da_wizz").setFullName("da_wizz")
+                .build();
+        ProjectParticipant u2 = ProjectParticipant.builder().setUrl("").setUsername("grunter").setFullName("grunter")
+                .build();
+
+        this.projects.add(Project.builder()
                 .setName("Sample project")
                 .setProjectId("sample.proj")
-                .setSpecProjectWorkingGroup(Collections.emptyList())
+                .setSpecProjectWorkingGroup(Collections.emptyMap())
                 .setGithubRepos(Arrays.asList(r1, r2))
                 .setGerritRepos(Arrays.asList(r5))
                 .setCommitters(Arrays.asList(u1, u2))
                 .setProjectLeads(Collections.emptyList())
-                .setGitlab(GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("").build())
-                .setGithub(GithubProject.builder().setIgnoredRepos(Collections.emptyList()).setOrg("").build())
-                .build();
-        src.add(p1);
+                .setGitlab(GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("")
+                        .build())
+                .setShortProjectId("sample.proj")
+                .setSummary("summary")
+                .setUrl("project.url.com")
+                .setWebsiteUrl("someproject.com")
+                .setWebsiteRepo(Collections.emptyList())
+                .setLogo("logoUrl.com")
+                .setTags(Collections.emptyList())
+                .setGithub(GithubProject.builder()
+                        .setOrg("")
+                        .setIgnoredRepos(Collections.emptyList()).build())
+                .setGitlabRepos(Collections.emptyList())
+                .setContributors(Collections.emptyList())
+                .setWorkingGroups(Collections.emptyList())
+                .setIndustryCollaborations(Collections.emptyList())
+                .setReleases(Collections.emptyList())
+                .setTopLevelProject("eclipse")
+                .setSlsaLevel("1")
+                .build());
 
-        Project p2 = Project
-                .builder()
+        this.projects.add(Project.builder()
                 .setName("Prototype thing")
                 .setProjectId("sample.proto")
-                .setSpecProjectWorkingGroup(Collections.emptyList())
+                .setSpecProjectWorkingGroup(Collections.emptyMap())
                 .setGithubRepos(Arrays.asList(r3))
                 .setGerritRepos(Arrays.asList(r6))
                 .setGitlabRepos(Arrays.asList(r8))
                 .setCommitters(Arrays.asList(u2))
                 .setProjectLeads(Collections.emptyList())
                 .setGitlab(
-                        GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("eclipse/dash-second").build())
-                .setGithub(GithubProject.builder().setIgnoredRepos(Collections.emptyList()).setOrg("").build())
-                .build();
-        src.add(p2);
+                        GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList())
+                                .setProjectGroup("eclipse/dash-second").build())
+                .setShortProjectId("sample.proto")
+                .setWebsiteUrl("someproject.com")
+                .setSummary("summary")
+                .setUrl("project.url.com")
+                .setWebsiteRepo(Collections.emptyList())
+                .setLogo("logoUrl.com")
+                .setTags(Collections.emptyList())
+                .setGithub(GithubProject.builder()
+                        .setOrg("")
+                        .setIgnoredRepos(Collections.emptyList()).build())
+                .setContributors(Collections.emptyList())
+                .setWorkingGroups(Collections.emptyList())
+                .setIndustryCollaborations(Collections.emptyList())
+                .setReleases(Collections.emptyList())
+                .setTopLevelProject("eclipse")
+                .setSlsaLevel("1")
+                .build());
 
-        Map<String, String> map = new HashMap<>();
-        map.put("id", "proj1");
-        Project p3 = Project
-                .builder()
+        this.projects.add(Project.builder()
                 .setName("Spec project")
                 .setProjectId("spec.proj")
-                .setSpecProjectWorkingGroup(map)
-                .setGithubRepos(Collections.emptyList())
+                .setSpecProjectWorkingGroup(Map.of("id", "proj1", "name", "proj1"))
+                .setGithubRepos(Arrays.asList(r4))
                 .setGitlabRepos(Arrays.asList(r7))
+                .setGerritRepos(Collections.emptyList())
                 .setGitlab(GitlabProject
                         .builder()
                         .setIgnoredSubGroups(Arrays.asList("eclipse/dash/mirror"))
                         .setProjectGroup("eclipse/dash")
                         .build())
-                .setGithub(GithubProject
-                        .builder()
-                        .setIgnoredRepos(Arrays.asList("eclipsefdn-tck/tck-ignored"))
-                        .setOrg("eclipsefdn-tck")
-                        .build())
                 .setCommitters(Arrays.asList(u1, u2))
                 .setProjectLeads(Collections.emptyList())
-                .build();
-        src.add(p3);
+                .setShortProjectId("spec.proj")
+                .setSummary("summary")
+                .setUrl("project.url.com")
+                .setWebsiteUrl("someproject.com")
+                .setWebsiteRepo(Collections.emptyList())
+                .setLogo("logoUrl.com")
+                .setTags(Collections.emptyList())
+                .setGithub(GithubProject.builder()
+                        .setOrg("eclipsefdn-tck")
+                        .setIgnoredRepos(Arrays.asList("eclipsefdn-tck/tck-ignored")).build())
+                .setContributors(Collections.emptyList())
+                .setWorkingGroups(Collections.emptyList())
+                .setIndustryCollaborations(Collections.emptyList())
+                .setReleases(Collections.emptyList())
+                .setTopLevelProject("eclipse")
+                .setSlsaLevel("1")
+                .build());
     }
 
     @Override
-    public Response getProjects(BaseAPIParameters baseParams) {
-        return Response.ok(baseParams.getPage() == 1 ? new ArrayList<>(src) : Collections.emptyList()).build();
+    public Response getProjects(BaseAPIParameters params, int isSpecProject) {
+
+        if (isSpecProject == 1) {
+            return Response
+                    .ok(projects.stream().filter(p -> p.getSpecWorkingGroup().isPresent()).collect(Collectors.toList()))
+                    .build();
+        }
+
+        return Response.ok(projects).build();
     }
 
     @Override
     public Response getInterestGroups(BaseAPIParameters params) {
-        return Response
-                .ok(Arrays
-                        .asList(InterestGroupData
-                                .builder()
-                                .setProjectId("foundation-internal.ig.mittens")
-                                .setId("1")
-                                .setLogo("")
-                                .setState("active")
-                                .setTitle("Magical IG Tributed To Eclipse News Sources")
-                                .setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
-                                .setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
-                                .setGitlab(GitlabProject
-                                        .builder()
-                                        .setIgnoredSubGroups(Collections.emptyList())
-                                        .setProjectGroup("eclipse-ig/mittens")
-                                        .build())
-                                .setLeads(Arrays
-                                        .asList(User
-                                                .builder()
-                                                .setUrl("https://api.eclipse.org/account/profile/zacharysabourin")
-                                                .setUsername("zacharysabourin")
-                                                .build()))
-                                .setParticipants(Arrays
-                                        .asList(User
-                                                .builder()
-                                                .setUrl("https://api.eclipse.org/account/profile/skilpatrick")
-                                                .setUsername("skilpatrick")
-                                                .build()))
-                                .build()))
-                .build();
+        return Response.ok(Arrays.asList(InterestGroup
+                .builder()
+                .setProjectId("foundation-internal.ig.mittens")
+                .setId("1")
+                .setLogo("")
+                .setState("active")
+                .setTitle("Magical IG Tributed To Eclipse News Sources")
+                .setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
+                .setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
+                .setGitlab(GitlabProject.builder()
+                        .setIgnoredSubGroups(Collections.emptyList())
+                        .setProjectGroup("eclipse-ig/mittens")
+                        .build())
+                .setLeads(Arrays.asList(InterestGroupParticipant
+                        .builder()
+                        .setUrl("https://api.eclipse.org/account/profile/zacharysabourin")
+                        .setUsername("zacharysabourin")
+                        .setFullName("zachary sabourin")
+                        .setOrganization(Organization.builder()
+                                .setDocuments(Collections.emptyMap())
+                                .setId("id")
+                                .setName("org").build())
+                        .build()))
+                .setParticipants(Arrays.asList(InterestGroupParticipant
+                        .builder()
+                        .setUrl("https://api.eclipse.org/account/profile/skilpatrick")
+                        .setUsername("skilpatrick")
+                        .setFullName("Skil Patrick")
+                        .setOrganization(Organization.builder()
+                                .setDocuments(Collections.emptyMap())
+                                .setId("id")
+                                .setName("org").build())
+                        .build()))
+                .setShortProjectId("mittens")
+                .setResources(Resource.builder().setMembers("members").setWebsite("google.com").build())
+                .setMailingList("mailinglist.com")
+                .build())).build();
     }
 }
diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockTokenService.java
similarity index 83%
rename from src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java
rename to src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockTokenService.java
index 8ce32a299b03e3328ad8d5e69a2f60d7dde09b11..4b8bc5cec1d92c8ee06633141cb24b64584ce797 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockTokenService.java
@@ -13,13 +13,13 @@ package org.eclipsefoundation.git.eca.test.service.impl;
 
 import javax.inject.Singleton;
 
-import org.eclipsefoundation.git.eca.service.OAuthService;
+import org.eclipsefoundation.efservices.services.DrupalTokenService;
 
 import io.quarkus.test.Mock;
 
 @Mock
 @Singleton
-public class MockOAuthService implements OAuthService {
+public class MockTokenService implements DrupalTokenService {
 
     @Override
     public String getToken() {