diff --git a/.envrc.local.template b/.envrc.local.template
index 584a5c81e29e3c936c0d0c6576f9dd46afd86bf7..f0c3c0d61ebb2ab380de3384d49342189ebd62af 100644
--- a/.envrc.local.template
+++ b/.envrc.local.template
@@ -1,7 +1,12 @@
+export CACHE_ENABLED=false
+
 export VOLUNTEERING_VOLTASTICS_API_URL=
 export VOLUNTEERING_VOLTASTICS_API_KEY=
+
 export DB_HOST=
 export DB_PORT=
 export DB_NAME=
 export DB_USERNAME=
 export DB_PASSWORD=
+
+export GEO_API_ENDPOINT_URL=
diff --git a/README.md b/README.md
index 5c0d30094d4305749632a6485f3b54f2584cf80b..4a1a188f3d1dafcf06609e5c69d35b8de5ba5715 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,16 @@ service to run. Please see the [configuration section](#configuration) to learn
 about the possible environment values, their purpose and where to find the
 values.
 
+### ML Recommendations
+
+In the context of HOLI-8190 recommendations were introduced. Recommendations
+are based on embeddings that were previously created by an ML pipeline and
+stored in a Postgres database within Google Cloud. These are currently not
+locally available. The endpoint `engagementRecommendations` relies on a
+connection to the aforementioned database that is only accessible to holi
+employees (unless running the service with the environment variable
+`FAKE=true`).
+
 ### Running
 
 To watch for file changes during development, run
@@ -184,11 +194,12 @@ if "you know what you're doing".
 
 ### Configuration
 
-| Environment Variable            | Default Value                                                                            | Description                         |
-| ------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------- |
-| PORT                            | 8004                                                                                     | the port to listen on               |
-| CACHE_ENABLED                   | true                                                                                     | wether or not to enable caching     |
-| CACHE_TTL_MS                    | 60 seconds                                                                               | time-to-live in ms                  |
-| VOLUNTEERING_VOLTASTICS_API_URL | undefined                                                                                | Voltastics API base URL             |
-| VOLUNTEERING_VOLTASTICS_API_KEY | undefined                                                                                | Voltastics API Token                |
-| IMAGE_PROXY_BASE_URL            | https://images.holi.social (production), https://dev-images.holi.social (all other envs) | Base URL for the image proxy server |
+| Environment Variable             | Default Value                                                                            | Description                            |
+|----------------------------------| ---------------------------------------------------------------------------------------- |----------------------------------------|
+| PORT                             | 8004                                                                                     | the port to listen on                  |
+| CACHE_ENABLED                    | true                                                                                     | wether or not to enable caching        |
+| CACHE_TTL_MS                     | 60 seconds                                                                               | time-to-live in ms                     |
+| VOLUNTEERING_VOLTASTICS_API_URL  | undefined                                                                                | Voltastics API base URL                |
+| VOLUNTEERING_VOLTASTICS_API_KEY  | undefined                                                                                | Voltastics API Token                   |
+| IMAGE_PROXY_BASE_URL             | https://images.holi.social (production), https://dev-images.holi.social (all other envs) | Base URL for the image proxy server    |
+| GEO_API_ENDPOINT_URL             | undefined                                                                                | GraphQL Endpoint URL for holis Geo API |
diff --git a/app/deps.ts b/app/deps.ts
index c3a1a08f43c5d89d766ca771d4ee4b0f43b11b2c..515034b748ff2bf6378e364f24302b1b4f36bf07 100644
--- a/app/deps.ts
+++ b/app/deps.ts
@@ -3,4 +3,7 @@ export { createSchema, createYoga } from "npm:graphql-yoga@5.1.1";
 export { useResponseCache } from "npm:@graphql-yoga/plugin-response-cache@1.0.0";
 export { GraphQLError } from "npm:graphql@16.8.1";
 export * as turf from "https://esm.sh/@turf/turf@6.5.0";
-export { Client } from "https://deno.land/x/postgres/mod.ts";
+export {
+  Client,
+  type QueryObjectResult,
+} from "https://deno.land/x/postgres/mod.ts";
diff --git a/app/dev_deps.ts b/app/dev_deps.ts
index df97030726db7e77b2c16df8e160b01135347258..dd30673135247255a2a88a65530e2d7b506744f8 100644
--- a/app/dev_deps.ts
+++ b/app/dev_deps.ts
@@ -1,6 +1,9 @@
 export {
   assertSpyCall,
+  assertSpyCalls,
   returnsNext,
+  type Spy,
+  spy,
   stub,
 } from "https://deno.land/std@0.155.0/testing/mock.ts";
 export type { Stub } from "https://deno.land/std@0.155.0/testing/mock.ts";
diff --git a/app/geo_api_client.ts b/app/geo_api_client.ts
new file mode 100644
index 0000000000000000000000000000000000000000..427e954c5527a8b44c73cf8d303d8eb5779f5509
--- /dev/null
+++ b/app/geo_api_client.ts
@@ -0,0 +1,39 @@
+import { GeoLocation } from "./volunteering_db.ts";
+
+type GeoAPIResponse = {
+  data?: {
+    placeDetails?: {
+      geolocation?: {
+        properties?: {
+          lat?: number;
+          lon?: number;
+        };
+      };
+    };
+  };
+};
+
+export const resolveGeoLocationViaGeoAPI =
+  (geoAPIEndpointUrl: string) =>
+  async (geolocationId: string): Promise<GeoLocation> => {
+    const graphQLQuery =
+      `{ placeDetails(id: "${geolocationId}") { geolocation } }`;
+    const response = await fetch(geoAPIEndpointUrl, {
+      "body": JSON.stringify({ query: graphQLQuery }),
+      "headers": {
+        "Accept": "application/graphql-response+json, application/json",
+        "Content-Type": "application/json",
+      },
+      "method": "POST",
+    });
+    const responseJSON = await response.json() as GeoAPIResponse;
+    const { lat, lon } =
+      responseJSON.data?.placeDetails?.geolocation?.properties || {};
+    if (lat && lon) {
+      return { lat, lon };
+    } else {
+      return Promise.reject(
+        `Resolution of lat/lng failed (no data included in response for geolocationId=${geolocationId})`,
+      );
+    }
+  };
diff --git a/app/main.ts b/app/main.ts
index bb3eff44d771afbc83e4ea06f7a4ee4988cd49b2..c3efe304aa0d37d86fb25b3213c54d0cb87f02fc 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -64,11 +64,27 @@ const serverConfigFromEnv = (): ServerConfig => {
         : "https://dev-images.holi.social",
     ),
     volunteeringDB: {
-      hostname: requiredEnv("DB_HOST", String),
-      port: requiredEnv("DB_PORT", String),
-      database: requiredEnv("DB_NAME", String),
-      username: requiredEnv("DB_USERNAME", String),
-      password: requiredEnv("DB_PASSWORD", String),
+      hostname: requiredEnv(
+        "DB_HOST",
+        String,
+        fake ? "dummy value" : undefined,
+      ),
+      port: requiredEnv("DB_PORT", String, fake ? "31337" : undefined),
+      database: requiredEnv(
+        "DB_NAME",
+        String,
+        fake ? "dummy value" : undefined,
+      ),
+      username: requiredEnv(
+        "DB_USERNAME",
+        String,
+        fake ? "dummy value" : undefined,
+      ),
+      password: requiredEnv(
+        "DB_PASSWORD",
+        String,
+        fake ? "dummy value" : undefined,
+      ),
       connectionAttempts: requiredEnv(
         "DB_CONNECTION_ATTEMPTS",
         Number,
@@ -76,6 +92,11 @@ const serverConfigFromEnv = (): ServerConfig => {
       ),
     },
     fake,
+    geoAPIEndpointUrl: requiredEnv(
+      "GEO_API_ENDPOINT_URL",
+      String,
+      fake ? "dummy value" : undefined,
+    ),
   };
 };
 
diff --git a/app/server.ts b/app/server.ts
index be9f6e012729c4301f30f50d8b61b60fb517fd72..adcc31d9579eb457d20e920ffbb83be5b010ddc0 100644
--- a/app/server.ts
+++ b/app/server.ts
@@ -21,6 +21,8 @@ import {
 } from "./voltastics.ts";
 import { logger } from "./logging.ts";
 import { VolunteeringDB, VolunteeringDBConfig } from "./volunteering_db.ts";
+import { resolveGeoLocationViaGeoAPI } from "./geo_api_client.ts";
+import { Client } from "https://deno.land/x/postgres@v0.19.3/client.ts";
 
 const typeDefs = `
     type Organizer {
@@ -75,7 +77,7 @@ const typeDefs = `
     type Query {
         # uses offset-based pagination as described in https://www.apollographql.com/docs/react/pagination/offset-based
         engagementOpportunities(offset: Int! = 0, limit: Int! = 10, location: GeoJSON, category: String): EngagementsResponse!
-        engagementRecommendations(offset: Int! = 0, limit: Int! = 10, topics: [String!], skills: [String!], location: GeoJSON): EngagementsResponse!
+        engagementRecommendations(offset: Int! = 0, limit: Int! = 10, topics: [String!], skills: [String!], geolocationId: String): EngagementsResponse!
         engagement(id: String!): Engagement
         categories: CategoriesResponse!
         engagements(offset: Int! = 0, limit: Int! = 10, location: String, category: String, longitude: Float, latitude: Float, radiusKm: Float): EngagementsResponse! @deprecated(reason: "Use engagementOpportunities instead, as its support will be discontinued after 1.5.1.")
@@ -167,6 +169,7 @@ export interface ServerConfig {
   imageProxyBaseUrl: string;
   fake: boolean; // For local development. If set, the API returns dummy data
   volunteeringDB: VolunteeringDBConfig;
+  geoAPIEndpointUrl: string;
 }
 
 export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
@@ -184,9 +187,28 @@ export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
       }),
     ]
     : [];
+  const client = new Client({
+    hostname: config.volunteeringDB.hostname,
+    port: config.volunteeringDB.port,
+    database: config.volunteeringDB.database,
+    user: config.volunteeringDB.username,
+    password: config.volunteeringDB.password,
+    controls: {
+      debug: {
+        queries: false,
+        notices: false,
+        results: false,
+        queryInError: true,
+      },
+    },
+    connection: {
+      attempts: config.volunteeringDB.connectionAttempts,
+    },
+  });
   const volunteeringDB = new VolunteeringDB(
-    config.volunteeringDB,
+    client,
     config.imageProxyBaseUrl,
+    resolveGeoLocationViaGeoAPI(config.geoAPIEndpointUrl),
   );
   const resolvers = createResolvers(config, volunteeringDB);
   return createYoga({
diff --git a/app/types.ts b/app/types.ts
index 9c425fd2eb8854f6f009b5650420b41020fa66e9..078a6d171d96b2722c485c614a1643cca37c147b 100644
--- a/app/types.ts
+++ b/app/types.ts
@@ -58,7 +58,7 @@ export type EngagementRecommendationsParameters = {
   offset: number;
   skills: string[];
   topics: string[];
-  location?: PlaceDetails;
+  geolocationId?: string;
 };
 
 export type PlaceDetails = {
diff --git a/app/voltastics_test.ts b/app/voltastics_test.ts
index 2cfd2949eaa14c033fa02c9e8875d8ef4bf7b676..3150b8f345c613d76d1bcb138772f5c7d17fb7e8 100644
--- a/app/voltastics_test.ts
+++ b/app/voltastics_test.ts
@@ -143,6 +143,7 @@ const serverConfigMock: ServerConfig = {
     username: "admin",
     connectionAttempts: 10,
   },
+  geoAPIEndpointUrl: "fakeUrl",
 };
 
 const noCacheServerConfig = {
@@ -160,6 +161,7 @@ const noCacheServerConfig = {
     username: "admin",
     connectionAttempts: 10,
   },
+  geoAPIEndpointUrl: "fakeUrl",
 };
 
 describe("voltastics", () => {
diff --git a/app/volunteering_db.ts b/app/volunteering_db.ts
index 3576b4ce2a13194c2d25bef69ea1d4e431d74c1c..0d0d8af3be520a3d82bee2681768f953036338dc 100644
--- a/app/volunteering_db.ts
+++ b/app/volunteering_db.ts
@@ -1,4 +1,5 @@
 import { Client } from "./deps.ts";
+import type { QueryObjectResult } from "./deps.ts";
 import {
   Engagement,
   EngagementRecommendationsParameters,
@@ -164,64 +165,131 @@ const toEngagement =
     longitude: row.longitude,
   });
 
+export type GeoLocation = {
+  lat: number;
+  lon: number;
+};
+
+export type GeoLocationResolver = (
+  geoLocationId: string,
+) => Promise<GeoLocation>;
+
 export class VolunteeringDB {
   private client: Client;
   private readonly imageProxyBaseUrl: string;
+  private resolveGeoLocation: GeoLocationResolver;
 
-  constructor(config: VolunteeringDBConfig, imageProxyBaseUrl: string) {
-    this.client = new Client({
-      hostname: config.hostname,
-      port: config.port,
-      database: config.database,
-      user: config.username,
-      password: config.password,
-      controls: {
-        debug: {
-          queries: true,
-          notices: true,
-          results: true,
-          queryInError: true,
-        },
-      },
-      connection: {
-        attempts: config.connectionAttempts,
-      },
-    });
+  constructor(
+    client: Client,
+    imageProxyBaseUrl: string,
+    resolveGeoLocation: GeoLocationResolver,
+  ) {
+    this.client = client;
     this.imageProxyBaseUrl = imageProxyBaseUrl;
+    this.resolveGeoLocation = resolveGeoLocation;
   }
 
   async findRecommendations(
-    params: EngagementRecommendationsParameters,
+    { offset, limit, topics, skills, geolocationId }:
+      EngagementRecommendationsParameters,
   ): Promise<EngagementsResponse> {
-    const recos = await this.queryRecommendations(
-      params.topics,
-      params.skills,
-      params.offset < 0 ? 0 : params.offset,
-      params.limit > 100 || params.limit < 1 ? 10 : params.limit,
-    )
-      .catch((reason) => {
-        console.error("Error executing query", reason);
-        throw reason;
-      });
+    const result = await this.queryRecos(
+      offset < 0 ? 0 : offset,
+      limit > 100 || limit < 1 ? 10 : limit,
+      topics,
+      skills,
+      geolocationId,
+    ).catch((reason) => {
+      console.error("Error retrieving recommendations. Reason: ", reason);
+      throw reason;
+    });
+    const recos = result.rows.map(toEngagement(this.imageProxyBaseUrl));
     return Promise.resolve({
       totalResults: recos.length,
       data: recos,
     });
   }
 
-  // Function to perform cosine similarity search
-  private async queryRecommendations(
+  private async queryRecos(
+    offset: number,
+    limit: number,
     topics: string[],
     skills: string[],
+    geolocationId?: string,
+  ): Promise<QueryObjectResult<VolunteeringDBRow>> {
+    const beforeResolve = Date.now();
+    const geoLocation = geolocationId
+      ? await this.resolveGeoLocation(geolocationId)
+        .then((geoLocation) => {
+          const afterResolve = Date.now();
+          console.debug(
+            `[${
+              (afterResolve - beforeResolve).toString().padStart(4, " ")
+            } ms] Successfully resolved ${
+              JSON.stringify(geoLocation)
+            } for geolocationId=${geolocationId}`,
+          );
+          return geoLocation;
+        })
+        .catch((error) => {
+          console.warn(error);
+          if (topics.length == 0 && skills.length == 0) {
+            throw new Error(
+              "Can't retrieve recommendations: geo location resolution failed and no topics or skills given",
+            );
+          }
+          // gracefully catch error so that recommendations will still be delivered but without location factored in
+          return undefined;
+        })
+      : undefined;
+
+    const beforeQuery = Date.now();
+    const logQueryDuration = (
+      result: QueryObjectResult<VolunteeringDBRow>,
+    ): QueryObjectResult<VolunteeringDBRow> => {
+      const afterQuery = Date.now();
+      console.debug(
+        `[${
+          (afterQuery - beforeQuery).toString().padStart(4, " ")
+        } ms] Successfully retrieved ${result.rows.length} recommendations from DB`,
+      );
+      return result;
+    };
+
+    if (geoLocation && (topics.length > 0 || skills.length > 0)) {
+      return this.queryRecosBasedOnTopicsSkillsAndLocation(
+        offset,
+        limit,
+        topics,
+        skills,
+        geoLocation,
+      ).then(logQueryDuration);
+    } else if (geoLocation) {
+      return this.queryRecosBasedOnLocation(offset, limit, geoLocation).then(
+        logQueryDuration,
+      );
+    } else if (topics.length > 0 || skills.length > 0) {
+      return this.queryRecosBasedOnTopicsAndSkills(
+        offset,
+        limit,
+        topics,
+        skills,
+      ).then(logQueryDuration);
+    } else {
+      return Promise.reject(
+        "It is required to provide at least one of (topics, skills, or location) in order to retrieve recommendations",
+      );
+    }
+  }
+
+  private async queryRecosBasedOnTopicsAndSkills(
     offset: number,
     limit: number,
-  ): Promise<Engagement[]> {
+    topics: string[],
+    skills: string[],
+  ): Promise<QueryObjectResult<VolunteeringDBRow>> {
     const queryVector = JSON.stringify(recosQueryVector(topics, skills));
-    console.debug(queryVector);
-
-    // special syntax for Denos postgres client, resulting in an SQL injection-safe prepared statement
-    // see https://deno-postgres.com/#/?id=template-strings
-    const result = await this.client.queryObject<VolunteeringDBRow>`
+    return await this.client.queryObject<VolunteeringDBRow>`
       WITH vector_matches AS (
         SELECT *, 1 - (embedding_array <=> ${queryVector}) AS cosine_similarity
         FROM volunteering_voltastics_with_classification
@@ -232,6 +300,60 @@ export class VolunteeringDB {
       SELECT *
       FROM vector_matches;
     `;
-    return result.rows.map(toEngagement(this.imageProxyBaseUrl));
+  }
+
+  private async queryRecosBasedOnTopicsSkillsAndLocation(
+    offset: number,
+    limit: number,
+    topics: string[],
+    skills: string[],
+    geoLocation: GeoLocation,
+  ): Promise<QueryObjectResult<VolunteeringDBRow>> {
+    const rankingWeightCosineSimilarity = 0.7; // Weight for cosine similarity
+    const rankingWeightDistance = 0.3; // Weight for proximity
+    const maxDistanceInMeters = 50_000; // in meters (e.g., 50 km)
+    const queryVector = JSON.stringify(recosQueryVector(topics, skills));
+    const { lat, lon } = geoLocation;
+
+    // Useful knowledge
+    // - PostGIS uses lon, lat (NOT lat, lon)
+    // - <#> uses index scans if used in an ORDER BY clause, ST_Distance does not
+    // - Database stores in GPS-Coordinates (SRID 4326)
+    // - In order to calculate a distance in meters, a geometry needs to be projected by transforming using
+    //   ST_Transform(geom, 3857) where we use the SRID 3857 for Pseudo-Mercator
+    return await this.client.queryObject<VolunteeringDBRow>`
+      WITH calculations AS (
+        SELECT *,
+              1 - (embedding_array <=> ${queryVector}) AS cosine_similarity,
+              ST_Transform(location_gps, 3857) <#> ST_Transform(ST_SetSRID(ST_MakePoint(${lon.toString()}, ${lat.toString()}), 4326), 3857) AS distance_in_meters
+        FROM volunteering_voltastics_with_classification
+      ), scored AS (
+        SELECT *,
+              (${rankingWeightCosineSimilarity} * cosine_similarity) + 
+              (${rankingWeightDistance} * (1 - LEAST(distance_in_meters, ${maxDistanceInMeters})) / ${maxDistanceInMeters}) AS weighted_score
+        FROM calculations
+      )
+      SELECT *
+      FROM scored
+      ORDER BY weighted_score DESC
+      OFFSET ${offset}
+      LIMIT ${limit};
+    `;
+  }
+
+  private async queryRecosBasedOnLocation(
+    offset: number,
+    limit: number,
+    geoLocation: GeoLocation,
+  ): Promise<QueryObjectResult<VolunteeringDBRow>> {
+    const { lat, lon } = geoLocation;
+    return await this.client.queryObject<VolunteeringDBRow>`
+      SELECT *, 
+             ST_Transform(location_gps, 3857) <#> ST_Transform(ST_SetSRID(ST_MakePoint(${lon.toString()}, ${lat.toString()}), 4326), 3857) AS distance_in_meters
+      FROM volunteering_voltastics_with_classification
+      ORDER BY distance_in_meters ASC
+      OFFSET ${offset}
+      LIMIT ${limit};
+    `;
   }
 }
diff --git a/app/volunteering_db_test.ts b/app/volunteering_db_test.ts
index 4df332aa7127ff81862e60d8237fd1929da373c9..f26e4399ce7c54379dcc194a827c332f46d0fa51 100644
--- a/app/volunteering_db_test.ts
+++ b/app/volunteering_db_test.ts
@@ -1,5 +1,51 @@
-import { assertEquals, describe, it } from "./dev_deps.ts";
-import { exportedForTesting } from "./volunteering_db.ts";
+import type { Spy } from "./dev_deps.ts";
+import {
+  assertEquals,
+  assertRejects,
+  assertSpyCall,
+  assertSpyCalls,
+  describe,
+  it,
+  spy,
+} from "./dev_deps.ts";
+import {
+  exportedForTesting,
+  GeoLocationResolver,
+  VolunteeringDB,
+} from "./volunteering_db.ts";
+import { Client } from "./deps.ts";
+
+type ResolveLocationSpy = Spy<
+  unknown,
+  [_geoLocationId: string],
+  Promise<{ lat: number; lon: number }>
+>;
+
+const succeedingGeoLocationResolver: GeoLocationResolver = (
+  _geoLocationId: string,
+) => Promise.resolve({ lat: 123, lon: 321 });
+const failingGeoLocationResolver: GeoLocationResolver = (
+  _geoLocationId: string,
+) => Promise.reject("boom!");
+
+const withMockedDependencies = (
+  geoLocationResolver: GeoLocationResolver,
+  test: (
+    vDB: VolunteeringDB,
+    resolveLocationSpy: ResolveLocationSpy,
+  ) => unknown | Promise<unknown>,
+) => {
+  const geoLocationResolverSpy = spy(geoLocationResolver);
+  const mockClient = {
+    queryObject: () => Promise.resolve({ rows: [] }),
+  } as unknown as Client;
+  const volunteeringDB = new VolunteeringDB(
+    mockClient,
+    "mock-image-proxy-base-url",
+    geoLocationResolverSpy,
+  );
+  test(volunteeringDB, geoLocationResolverSpy);
+};
 
 describe("VolunteeringDB", () => {
   it("correctly constructs a query vector", () => {
@@ -28,4 +74,70 @@ describe("VolunteeringDB", () => {
     assertEquals(actualVector.length, expectedLength);
     assertEquals(actualVector, expectedVector);
   });
+  it("resolves latitude/longitude using the GeoAPI client when geoLocationId is given", () => {
+    withMockedDependencies(
+      succeedingGeoLocationResolver,
+      (volunteeringDB, resolveLocationSpy) => {
+        volunteeringDB.findRecommendations({
+          limit: 10,
+          offset: 0,
+          topics: [],
+          skills: [],
+          geolocationId: "mock-geolocation-id",
+        });
+        assertSpyCall(resolveLocationSpy, 0, {
+          args: ["mock-geolocation-id"],
+        });
+      },
+    );
+  });
+  it("does not resolve latitude/longitude when geoLocationId is not given", () => {
+    withMockedDependencies(
+      succeedingGeoLocationResolver,
+      (volunteeringDB, resolveLocationSpy) => {
+        volunteeringDB.findRecommendations({
+          limit: 10,
+          offset: 0,
+          topics: ["agriculture-food"],
+          skills: [],
+        });
+        assertSpyCalls(resolveLocationSpy, 0);
+      },
+    );
+  });
+  it("falls back to recommend based only on topics and skills if given and geo location resolution fails", () => {
+    withMockedDependencies(
+      failingGeoLocationResolver,
+      async (volunteeringDB, _resolveLocationSpy) => {
+        const result = await volunteeringDB.findRecommendations({
+          limit: 10,
+          offset: 0,
+          topics: ["agriculture-food"],
+          skills: [],
+          geolocationId: "mock-geolocation-id",
+        });
+        assertEquals(result.totalResults, 0);
+        assertEquals(result.data, []);
+      },
+    );
+  });
+  it("fails with error message if no topics and skills are given and geo location resolution fails", () => {
+    withMockedDependencies(
+      failingGeoLocationResolver,
+      async (volunteeringDB, _resolveLocationSpy) => {
+        await assertRejects(
+          () =>
+            volunteeringDB.findRecommendations({
+              limit: 10,
+              offset: 0,
+              topics: [],
+              skills: [],
+              geolocationId: "mock-geolocation-id",
+            }),
+          Error,
+          "geo location resolution failed and no topics or skills given",
+        );
+      },
+    );
+  });
 });
diff --git a/deno.json b/deno.json
index b409918fc270a1a693f99087b4094bc5fa926759..184e3eda5b4a4ee8464ba50b1fc65aa248b3c1f0 100644
--- a/deno.json
+++ b/deno.json
@@ -13,7 +13,7 @@
       "docker": "docker build -t volunteering-api . && docker run -it --init -p 8004:8004 volunteering-api",
       "coverage": "deno test --coverage=coverage && deno coverage coverage",
       "pre-commit": {
-        "cmd": "vr lint && vr fmt:check",
+        "cmd": "vr lint && vr fmt:check && deno test",
         "gitHook": "pre-commit"
       }
     }
diff --git a/terraform/environments/deployment.tf b/terraform/environments/deployment.tf
index ec78777c8b69e5aab7e419629d1aa35b5e38dcac..86a05f82e347259aca5ac3fe5b7f730be48f197a 100644
--- a/terraform/environments/deployment.tf
+++ b/terraform/environments/deployment.tf
@@ -114,6 +114,10 @@ resource "google_cloud_run_service" "volunteering_api" {
             }
           }
         }
+        env {
+          name = "GEO_API_ENDPOINT_URL"
+          value = data.terraform_remote_state.holi_geo_api_environments_state.outputs.api_endpoint_url
+        }
         resources {
           limits = {
             # cpu can only be scaled down to 1000m as long as container_concurrency is set to != 1
@@ -136,7 +140,7 @@ resource "google_cloud_run_service" "volunteering_api" {
         # Use the VPC Connector
         "run.googleapis.com/vpc-access-connector" = data.terraform_remote_state.holi_infra_state.outputs.vpc_access_connector_name
         # possible values: all-traffic/private-ranges-only(default) https://cloud.google.com/sdk/gcloud/reference/run/services/update#--vpc-egress
-        "run.googleapis.com/vpc-access-egress" = "private-ranges-only"
+        "run.googleapis.com/vpc-access-egress" = "all-traffic"
       }
     }
   }
diff --git a/terraform/environments/init.tf b/terraform/environments/init.tf
index 1fd30f206489a57033642104d1acfef3a65a26a0..955dd9a410307fb4c71c87ec7395a5bde786b787 100644
--- a/terraform/environments/init.tf
+++ b/terraform/environments/init.tf
@@ -21,6 +21,15 @@ data "terraform_remote_state" "holi_volunteering_api_common_state" {
   }
 }
 
+data "terraform_remote_state" "holi_geo_api_environments_state" {
+  backend = "gcs"
+  workspace = terraform.workspace == "production" ? "production" : "staging"
+  config = {
+    bucket = "holi-shared-terraform-state"
+    prefix = "geo-api-environments"
+  }
+}
+
 # provider google including beta features
 provider "google" {
   region = local.default_region