diff --git a/example.env b/example.env
index 6108a4f356a7c1952c1200c5f71e982c8d4082cd..4d1de3e199ca8336a74b0e706603fa6e69ca8eb5 100644
--- a/example.env
+++ b/example.env
@@ -18,7 +18,6 @@ BASE_URL="http://localhost:5173"
 # BUDGET_API_URL="https://SECRET.DOMAIN.NAME/state_simulation"
 
 # Public HTTP(S) URL of LexImpact Socio-Fiscal Budget server
-# BUDGET_PUBLIC_API_URL="https://SECRET.DOMAIN.NAME/default_state_simulation"
 
 # Secret key used to sign JSON web tokens sent to Budget API
 # BUDGET_JWT_SECRET="SECRET"
diff --git a/src/lib/budgets.ts b/src/lib/budgets.ts
index 8ec2a2035531b5b461cffaef45249e4739dc9172..d5ec0c7f6fbda9f26bd4514f83600462fc628cfa 100644
--- a/src/lib/budgets.ts
+++ b/src/lib/budgets.ts
@@ -1,5 +1,3 @@
-import type { ParametricReform } from "$lib/reforms"
-
 export interface BudgetCalculationResult {
   compare_before_after?: BudgetPopulationComparison | null
   quantiles: BudgetQuantile[]
@@ -62,11 +60,7 @@ export interface BudgetSimulation {
     plf: BudgetCalculationResult
     revaluation: BudgetCalculationResult
   }
-}
-
-export interface BudgetSimulationCache {
-  budgetSimulation: BudgetSimulation
-  parametricReform: ParametricReform
+  isPublic: boolean
 }
 
 export interface StateBudget {
diff --git a/src/lib/components/BudgetConnexionModal.svelte b/src/lib/components/BudgetConnexionModal.svelte
index 386d3f82c2d270ae65f74a984db93cc31576c0ea..273e1293aa360659a062d220f24054bf18b08d55 100644
--- a/src/lib/components/BudgetConnexionModal.svelte
+++ b/src/lib/components/BudgetConnexionModal.svelte
@@ -52,8 +52,8 @@
       console.error(`Error fetching cached simulations`)
       return
     }
-    const { index } = await res.json()
-    cachedSimulations = index
+    const { simulations } = await res.json()
+    cachedSimulations = simulations
   }
 
   function onClose() {
diff --git a/src/lib/components/BudgetSimulationSharingModal.svelte b/src/lib/components/BudgetSimulationSharingModal.svelte
index e05e4b099ec903cbfffa8b494427ebd844f4572f..50a2a7393d9ea43f05802c340e4d2a87c09f5eeb 100644
--- a/src/lib/components/BudgetSimulationSharingModal.svelte
+++ b/src/lib/components/BudgetSimulationSharingModal.svelte
@@ -15,6 +15,7 @@
   import type { DisplayMode } from "$lib/displays"
   import type { ParametricReform } from "$lib/reforms"
   import { budgetEditableParametersName } from "$lib/variables"
+  import { hashObject } from "$lib/hash.js"
 
   export let displayMode: DisplayMode
   export let isOpen = false
@@ -24,7 +25,6 @@
   >
   let clipboardElement: HTMLElement
   let hasClickedCopy = false
-  let isSimulationShared = false
   const parametricReform = getContext(
     "parametricReform",
   ) as Writable<ParametricReform>
@@ -47,6 +47,8 @@
   ]
   let url = ""
 
+  $: isSimulationShared = $budgetSimulation?.isPublic
+
   function copyLink() {
     if (hasClickedCopy) {
       return
@@ -61,14 +63,13 @@
     setTimeout(() => (hasClickedCopy = false), 2500)
   }
 
-  async function onChange({ target }: Event) {
-    if ((target as HTMLInputElement).checked) {
-      const urlString = "/simulations_budget"
-      const res = await fetch(urlString, {
-        body: JSON.stringify({
-          budgetSimulation: $budgetSimulation?.result,
-          displayMode,
-          parametricReform: Object.entries($parametricReform)
+  async function onChange() {
+    const urlString = "/simulations_budget"
+    const res = await fetch(urlString, {
+      body: JSON.stringify({
+        displayMode,
+        hash: hashObject(
+          Object.entries($parametricReform)
             .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
             .reduce((filtered: ParametricReform, [parameterName, value]) => {
               if (budgetEditableParametersName.has(parameterName)) {
@@ -76,32 +77,32 @@
               }
               return filtered
             }, {}),
-        }),
-        headers: {
-          Accept: "application/json",
-          "Content-Type": "application/json; charset=utf-8",
-        },
-        method: "POST",
-      })
-      if (!res.ok) {
-        console.error(
-          `Error ${
-            res.status
-          } while creating a share link at ${urlString}\n\n${await res.text()}`,
-        )
-        return
-      }
-      const { token } = await res.json()
-      url = new URL(
-        `/simulations_budget/${token}`,
-        $page.data.baseUrl,
-      ).toString()
+        ),
+      }),
+      headers: {
+        Accept: "application/json",
+        "Content-Type": "application/json; charset=utf-8",
+      },
+      method: "POST",
+    })
+    if (!res.ok) {
+      console.error(
+        `Error ${
+          res.status
+        } while sharing budget simulation\n\n${await res.text()}`,
+      )
+      return
+    }
+    const { hash } = await res.json()
+    url = new URL(`/simulations_budget/${hash}`, $page.data.baseUrl).toString()
+    $budgetSimulation = {
+      ...$budgetSimulation,
+      isPublic: true,
     }
   }
 
   function onClose() {
     isOpen = false
-    isSimulationShared = false
   }
 </script>
 
@@ -171,7 +172,7 @@
                   type="checkbox"
                   class="sr-only peer"
                   bind:checked={isSimulationShared}
-                  on:change={(event) => onChange(event)}
+                  on:change={onChange}
                   disabled={isSimulationShared}
                 />
                 <div
diff --git a/src/lib/components/ReformsChanges.svelte b/src/lib/components/ReformsChanges.svelte
index 5af044233d7f1b1aca5a1609cde03a242573a052..7cfccfbfe4076e8ef19c93909e30f987f127e2c8 100644
--- a/src/lib/components/ReformsChanges.svelte
+++ b/src/lib/components/ReformsChanges.svelte
@@ -66,7 +66,7 @@
     <!--<h5 class="text-sm">
       {reformMetadataByName[$billName].label}
     </h5>-->
-    <ul class="bg-gray-200 text-xs leading-relaxed text-gray-600">
+    <ul class="text-xs leading-relaxed text-gray-600">
       <li class="my-1">
         <Tooltip
           arrowClass="bg-white border-le-rouge-bill"
diff --git a/src/lib/server/auditors/config.ts b/src/lib/server/auditors/config.ts
index 253e63cdc46d318611295592332d66091f4ae42a..4603fec64e45cb3dd112ed6f4b29c977167153b0 100644
--- a/src/lib/server/auditors/config.ts
+++ b/src/lib/server/auditors/config.ts
@@ -65,9 +65,14 @@ export function auditConfig(
       auditRequire,
     )
   }
-  for (const key of ["budgetApiUrl", "budgetPublicApiUrl"]) {
-    audit.attribute(data, key, true, errors, remainingKeys, auditHttpUrl)
-  }
+  audit.attribute(
+    data,
+    "budgetApiUrl",
+    true,
+    errors,
+    remainingKeys,
+    auditHttpUrl,
+  )
   for (const key of [
     "childrenKey",
     "familyEntityKey",
diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts
index 3e2a97b96ad093380f5c7069414314273b3420c1..2abbd37ae9edc7920c0810336271713d75857dad 100644
--- a/src/lib/server/config.ts
+++ b/src/lib/server/config.ts
@@ -10,7 +10,6 @@ export interface Config {
   apiWebSocketBaseUrls: string[]
   baseUrl: string
   budgetApiUrl?: string
-  budgetPublicApiUrl?: string
   budgetJwtSecret?: string
   childrenKey: string
   familyEntityKey: string
@@ -49,7 +48,6 @@ const [validConfig, error] = validateConfig({
   apiBaseUrls: process.env["API_BASE_URLS"],
   baseUrl: process.env["BASE_URL"],
   budgetApiUrl: process.env["BUDGET_API_URL"],
-  budgetPublicApiUrl: process.env["BUDGET_PUBLIC_API_URL"],
   budgetJwtSecret: process.env["BUDGET_JWT_SECRET"],
   childrenKey: process.env["CHILDREN_KEY"],
   familyEntityKey: process.env["FAMILY_KEY"],
diff --git a/src/lib/simulations.ts b/src/lib/simulations.ts
index 1722aca0b02f1d182c8895eb6db74b21a37d3f71..da168369e5a7eabfd89aa090b49d025a20a69a43 100644
--- a/src/lib/simulations.ts
+++ b/src/lib/simulations.ts
@@ -2,6 +2,7 @@ export interface CachedSimulation {
   date: string
   hash: string
   parameters: string[]
+  public: boolean
   title: string
 }
 
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 7779a39d75e185bef43b21ace814c7ecb0574fb8..06825778f404680bf9136b2b21e4e61bb6fff898 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -17,7 +17,7 @@
 
   import { browser } from "$app/environment"
   import { page } from "$app/stores"
-  import type { BudgetSimulation, BudgetSimulationCache } from "$lib/budgets"
+  import type { BudgetSimulation } from "$lib/budgets"
   import {
     calculationNames,
     requestAllTestCasesCalculations,
@@ -38,6 +38,7 @@
     waterfalls,
     type EvaluationByName,
   } from "$lib/decompositions"
+  import type { DisplayMode } from "$lib/displays"
   import { entityByKey } from "$lib/entities"
   import { reformMetadataByName, type ParametricReform } from "$lib/reforms"
   import {
@@ -59,7 +60,6 @@
   import type { WebSocketByName, WebSocketOpenByName } from "$lib/websockets"
 
   import type { LayoutData } from "./$types"
-  import type { DisplayMode } from "$lib/displays"
 
   export let data: LayoutData
 
@@ -73,9 +73,6 @@
   setContext("budgetSimulation", budgetSimulation)
   let budgetSimulationAbortController = new AbortController()
   const budgetSimulationByBody: { [body: string]: BudgetSimulation } = {}
-  const budgetSimulationCache: Writable<BudgetSimulationCache | undefined> =
-    writable(undefined)
-  setContext("budgetSimulationCache", budgetSimulationCache)
 
   let currentBillName: string | undefined =
     data.reformName === undefined
@@ -103,18 +100,6 @@
   const date = writable("2023-01-01") // new Date().toISOString().split("T")[0]
   setContext("date", date)
 
-  const showNulls = writable(false)
-  setContext("showNulls", showNulls)
-
-  const showTutorial = writable(false)
-  setContext("showTutorial", showTutorial)
-
-  const vectorLength = writable(1)
-  setContext("vectorLength", vectorLength)
-
-  let waterfall = writable(waterfalls[0])
-  setContext("waterfall", waterfall)
-
   const decompositionByName = writable(
     // Note: We always use the reform decomposition, because it is more
     // complete than decomposition before reform.
@@ -127,6 +112,9 @@
   )
   setContext("decompositionByName", decompositionByName)
 
+  const displayMode: Writable<DisplayMode | undefined> = writable(undefined)
+  setContext("displayMode", displayMode)
+
   const evaluationByNameArray = writable(
     new Array(testCasesCore.length).fill({}) as EvaluationByName[],
   )
@@ -168,6 +156,21 @@
   })
   setContext("requestedCalculations", requestedCalculations)
 
+  /*
+   * Search
+   * */
+  const isSearchActive: Writable<boolean> = writable(false)
+  setContext("isSearchActive", isSearchActive)
+
+  const searchParameterName: Writable<string | undefined> = writable(undefined)
+  setContext("searchParameterName", searchParameterName)
+
+  const showNulls = writable(false)
+  setContext("showNulls", showNulls)
+
+  const showTutorial = writable(false)
+  setContext("showTutorial", showTutorial)
+
   const testCasesValue: Situation[] = testCasesCore
   // let testCasesValue: Situation[]
   // if (browser) {
@@ -198,8 +201,14 @@
     valuesByCalculationNameByVariableNameArray,
   )
 
+  const vectorLength = writable(1)
+  setContext("vectorLength", vectorLength)
+
   let vectorIndexes = new Array(testCasesCore.length).fill(0)
 
+  let waterfall = writable(waterfalls[0])
+  setContext("waterfall", waterfall)
+
   const webSocketByName: Writable<WebSocketByName | undefined> =
     writable(undefined)
   setContext("webSocketByName", webSocketByName)
@@ -292,9 +301,6 @@
     budgetVariableName: string,
     // budgetCalculationNames: Set<CalculationName>,
   ): Promise<void> {
-    if (user === undefined && $budgetSimulation !== undefined) {
-      return
-    }
     budgetSimulationAbortController.abort()
     budgetSimulationAbortController = new AbortController()
     const budgetParametricReform = Object.fromEntries(
@@ -1096,15 +1102,6 @@
       $evaluationByNameArray = updatedEvaluationByNameArray
     }
   }
-
-  const isSearchActive: Writable<boolean> = writable(false)
-  setContext("isSearchActive", isSearchActive)
-
-  const searchParameterName: Writable<string | undefined> = writable(undefined)
-  setContext("searchParameterName", searchParameterName)
-
-  const displayMode: Writable<DisplayMode | undefined> = writable(undefined)
-  setContext("displayMode", displayMode)
 </script>
 
 <svelte:head>
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index f0d9ce808cfaf1c26882460afd7021dde6bf3bc6..0e57ed62fcb41008731de2578887bcb48fcf080e 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -11,7 +11,6 @@
     auditFunction,
     auditOptions,
   } from "@auditors/core"
-  import deepEqual from "deep-equal"
   import type { PopulationWithoutId, Waterfall } from "@openfisca/json-model"
   import introJs from "intro.js"
   import { getContext, setContext } from "svelte"
@@ -22,7 +21,7 @@
   import { page } from "$app/stores"
   import { auditQueryArray, auditQuerySingleton } from "$lib/auditors/queries"
   import { auditSimulationHash } from "$lib/auditors/hashes"
-  import type { BudgetSimulation, BudgetSimulationCache } from "$lib/budgets"
+  import type { BudgetSimulation } from "$lib/budgets"
   import {
     requestAllBudgetCalculations,
     requestAllTestCasesCalculations,
@@ -86,9 +85,6 @@
   const budgetSimulation = getContext("budgetSimulation") as Writable<
     BudgetSimulation | undefined
   >
-  const budgetSimulationCache = getContext("budgetSimulationCache") as Writable<
-    BudgetSimulationCache | undefined
-  >
   let changeTestCaseIndex: number | undefined
   let clipboardElement: HTMLElement
   const date = getContext("date") as Writable<string>
@@ -1352,16 +1348,8 @@
         >
           {#if displayMode.budget}
             {#if displayMode.parametersVariableName !== undefined}
-              {@const budgetSimulationData =
-                $budgetSimulationCache !== undefined &&
-                deepEqual(
-                  $budgetSimulationCache?.parametricReform,
-                  $parametricReform,
-                )
-                  ? $budgetSimulationCache.budgetSimulation
-                  : $budgetSimulation}
               <div class="mb-6 flex flex-col px-2 lg:px-5 w-screen md:w-full">
-                {#if budgetSimulationData === undefined || budgetSimulationData.errors.length > 0}
+                {#if $budgetSimulation === undefined || $budgetSimulation.errors.length > 0}
                   {#if displayMode.parametersVariableName !== undefined}
                     <div class="z-10 bg-le-jaune bg-opacity-20">
                       <Spinner />
@@ -1384,9 +1372,9 @@
                   <h3 class="mx-4 mb-2 text-2xl font-bold md:mx-0">
                     Impôt sur le revenu
                   </h3>
-                  <IrBudgetView budgetSimulation={budgetSimulationData} />
+                  <IrBudgetView budgetSimulation={$budgetSimulation} />
                   <IrGagnantsPerdantsView
-                    budgetSimulation={budgetSimulationData}
+                    budgetSimulation={$budgetSimulation}
                   />
                 {:else if displayMode.parametersVariableName === "csg_deductible_salaire" || displayMode.parametersVariableName === "csg_imposable_salaire"}
                   <!--     <a
@@ -1408,9 +1396,9 @@
                       >Imposable et déductible</span
                     >
                   </h3>
-                  <CsgBudgetView budgetSimulation={budgetSimulationData} />
+                  <CsgBudgetView budgetSimulation={$budgetSimulation} />
                   <CsgGagnantsPerdantsView
-                    budgetSimulation={budgetSimulationData}
+                    budgetSimulation={$budgetSimulation}
                   />
                 {/if}
               </div>
@@ -1626,7 +1614,8 @@
       Object.keys($parametricReform).filter((parameterName) =>
         budgetEditableParametersName.has(parameterName),
       ).length > 0 &&
-      !deepEqual($budgetSimulationCache?.parametricReform, $parametricReform)}
+      Object.keys($parametricReform).length > 0 &&
+      $budgetSimulation?.result.amendement === undefined}
     <div
       class="flex flex-col items-center md:block fixed bottom-0 md:bottom-auto md:top-12 2xl:top-14 md:right-3 bg-le-jaune-light z-40 rounded-t-lg md:rounded-t-none md:rounded-b-lg shadow-md mx-5 md:mx-0 inset-x-0 md:inset-x-auto px-3 lg:px-5 pt-3 pb-6 md:pt-6 md:pb-3 lg:pt-8 transition-transform duration-[350ms] ease-out-back md:delay-0"
       class:translate-y-full={!showButton}
@@ -1659,7 +1648,8 @@
       displayMode.budget &&
       !displayMode.mobileLaw &&
       displayMode.parametersVariableName !== undefined &&
-      deepEqual($budgetSimulationCache?.parametricReform, $parametricReform)}
+      Object.keys($parametricReform).length > 0 &&
+      $budgetSimulation?.result.amendement !== undefined}
     <div
       class="flex flex-col items-center md:block fixed bottom-0 md:bottom-auto md:top-12 2xl:top-14 md:right-3 bg-le-jaune-light z-40 rounded-t-lg md:rounded-t-none md:rounded-b-lg shadow-md mx-5 md:mx-0 inset-x-0 md:inset-x-auto px-3 lg:px-5 pt-3 pb-6 md:pt-6 md:pb-3 lg:pt-8 transition-transform duration-[350ms] ease-out-back md:delay-0"
       class:translate-y-full={!showButton}
diff --git a/src/routes/budget/+server.ts b/src/routes/budget/+server.ts
index f4e4c04ee51da31f5cdbe9b0f0ffa9d651e2dec6..c734d133a4d6b1aa4f21dd127dd6649c6f40703d 100644
--- a/src/routes/budget/+server.ts
+++ b/src/routes/budget/+server.ts
@@ -1,16 +1,32 @@
-import { error } from "@sveltejs/kit"
-
-import type { User } from "$lib/users"
+import fs from "fs-extra"
 import jwt from "jsonwebtoken"
+import path from "path"
+import { error, json } from "@sveltejs/kit"
 
+import type { BudgetSimulation } from "$lib/budgets"
+import { hashObject } from "$lib/hash"
+import { getParameter, rootParameter } from "$lib/parameters"
+import type { ParametricReform } from "$lib/reforms"
 import config from "$lib/server/config"
+import type { CachedSimulation } from "$lib/simulations"
+import type { User } from "$lib/users"
+import { budgetEditableParametersName } from "$lib/variables"
 
 import type { RequestHandler } from "./$types"
 
+const { simulationsBudgetDir } = config
+
+function getPath(digest: string) {
+  return path.join(
+    simulationsBudgetDir,
+    digest.substring(0, 2),
+    `${digest}.json`,
+  )
+}
+
 export const POST: RequestHandler = async ({ fetch, locals, request }) => {
   if (
     config.budgetApiUrl === undefined ||
-    config.budgetPublicApiUrl === undefined ||
     config.budgetJwtSecret === undefined
   ) {
     throw error(
@@ -21,15 +37,49 @@ export const POST: RequestHandler = async ({ fetch, locals, request }) => {
 
   const user = locals.user as User
 
-  if (user === undefined) {
-    return await fetch(config.budgetPublicApiUrl, {
-      body: await request.arrayBuffer(),
-      headers: {
-        Accept: "application/json",
-        "Content-Type": "application/json; charset=utf-8",
-      },
-      method: "POST",
-    })
+  const indexFilePath = path.join(simulationsBudgetDir, "index.json")
+  const indexContents: CachedSimulation[] = (await fs.pathExists(indexFilePath))
+    ? await fs.readJson(indexFilePath)
+    : []
+
+  const parametricReformSorted = Object.entries(
+    (await request.clone().json()).amendement,
+  )
+    .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
+    .reduce((filtered: ParametricReform, [parameterName, value]) => {
+      if (budgetEditableParametersName.has(parameterName)) {
+        filtered[parameterName] = value
+      }
+      return filtered
+    }, {})
+
+  const parametricReformDigest = hashObject(parametricReformSorted)
+
+  // Path of the no-reform base cache (hashing empty object)
+  const baseCachePath = getPath(hashObject({}))
+
+  // Path of the current reform cache
+  const cachePath = getPath(parametricReformDigest)
+
+  const isSimulationPublic = indexContents.find(
+    (cachedSimulation) => cachedSimulation.hash === parametricReformDigest,
+  )?.public
+
+  if (
+    (await fs.pathExists(cachePath)) &&
+    (user !== undefined || isSimulationPublic)
+  ) {
+    return json({
+      errors: [],
+      result: (await fs.readJson(cachePath)).budgetSimulation,
+      isPublic: isSimulationPublic,
+    } as BudgetSimulation)
+  } else if (user === undefined && (await fs.pathExists(baseCachePath))) {
+    return json({
+      errors: [],
+      result: (await fs.readJson(baseCachePath)).budgetSimulation,
+      isPublic: true,
+    } as BudgetSimulation)
   }
 
   const payload = {
@@ -49,5 +99,54 @@ export const POST: RequestHandler = async ({ fetch, locals, request }) => {
     },
     method: "POST",
   })
+
+  const responseJson = await response.clone().json()
+
+  responseJson.isPublic = false
+
+  if (response.ok && responseJson.errors?.length === 0) {
+    const digest = hashObject(parametricReformSorted)
+
+    const modifiedParametersTitles = Object.keys(parametricReformSorted).map(
+      (parameterName) => getParameter(rootParameter, parameterName)?.title,
+    )
+
+    const modifiedParametersNames = Object.keys(parametricReformSorted).map(
+      (parameterName) => getParameter(rootParameter, parameterName)?.name,
+    )
+
+    const simulationDir = path.join(
+      simulationsBudgetDir,
+      digest.substring(0, 2),
+    )
+    const simulationFilePath = path.join(simulationDir, `${digest}.json`)
+    if (!(await fs.pathExists(simulationFilePath))) {
+      await fs.ensureDir(simulationDir)
+      await fs.writeFile(
+        simulationFilePath,
+        JSON.stringify(
+          {
+            budgetSimulation: responseJson.result,
+            parametricReform: parametricReformSorted,
+          },
+          null,
+          2,
+        ),
+      )
+    }
+
+    const contents: CachedSimulation[] = [
+      ...indexContents,
+      {
+        date: new Intl.DateTimeFormat("fr-FR").format(new Date()),
+        hash: digest,
+        public: false,
+        parameters: modifiedParametersNames,
+        title: modifiedParametersTitles.join(" | "),
+      } as CachedSimulation,
+    ]
+    await fs.writeFile(indexFilePath, JSON.stringify(contents, null, 2))
+  }
+
   return response
 }
diff --git a/src/routes/simulations_budget/+page.svelte b/src/routes/simulations_budget/+page.svelte
index e08e42cc31818b5855f9ba3026b97f7788987e3d..4e3c625a1201fd76c24d1736b752efc4b6001117 100644
--- a/src/routes/simulations_budget/+page.svelte
+++ b/src/routes/simulations_budget/+page.svelte
@@ -14,8 +14,8 @@
       console.error(`Error fetching cached simulations`)
       return
     }
-    const { index } = await res.json()
-    cachedSimulations = index
+    const { simulations } = await res.json()
+    cachedSimulations = simulations
   })
 </script>
 
diff --git a/src/routes/simulations_budget/+server.ts b/src/routes/simulations_budget/+server.ts
index 99e4ac2c2f52849e44699c49447ca18d9b4e1aae..f582cda1d94f68b759a354f4c1cd6883a5f095ec 100644
--- a/src/routes/simulations_budget/+server.ts
+++ b/src/routes/simulations_budget/+server.ts
@@ -1,15 +1,13 @@
 import { auditRequire, cleanAudit, type Audit } from "@auditors/core"
-import { error, json } from "@sveltejs/kit"
 import fs from "fs-extra"
 import path from "path"
+import { error, json } from "@sveltejs/kit"
 
-import { getParameter, rootParameter } from "$lib/parameters"
-import type { ParametricReform } from "$lib/reforms"
+import type { DisplayMode } from "$lib/displays"
 import config from "$lib/server/config"
 import type { CachedSimulation } from "$lib/simulations"
 
 import type { RequestHandler } from "./$types"
-import { hashObject } from "$lib/hash"
 
 const { simulationsBudgetDir } = config
 
@@ -25,16 +23,6 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
   const errors: { [key: string]: unknown } = {}
   const remainingKeys = new Set(Object.keys(data))
 
-  audit.attribute(
-    data,
-    "budgetSimulation",
-    true,
-    errors,
-    remainingKeys,
-    // TODO
-    auditRequire,
-  )
-
   audit.attribute(
     data,
     "displayMode",
@@ -47,7 +35,7 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
 
   audit.attribute(
     data,
-    "parametricReform",
+    "hash",
     true,
     errors,
     remainingKeys,
@@ -58,7 +46,11 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
   return audit.reduceRemaining(data, errors, remainingKeys)
 }
 
-export const POST: RequestHandler = async ({ request, url }) => {
+export const POST: RequestHandler = async ({ locals, request, url }) => {
+  if (locals.user === undefined) {
+    throw error(401, "Unauthorized")
+  }
+
   const [body, bodyError] = auditBody(cleanAudit, await request.json())
   if (bodyError !== null) {
     console.error(
@@ -71,44 +63,40 @@ export const POST: RequestHandler = async ({ request, url }) => {
     throw error(400, `Invalid body: ${JSON.stringify(bodyError, null, 2)}`)
   }
 
-  const modifiedParametersTitles = Object.keys(
-    (body as { parametricReform: ParametricReform }).parametricReform,
-  ).map((parameterName) => getParameter(rootParameter, parameterName)?.title)
-
-  const modifiedParametersNames = Object.keys(
-    (body as { parametricReform: ParametricReform }).parametricReform,
-  ).map((parameterName) => getParameter(rootParameter, parameterName)?.name)
-
-  const bodyJson = JSON.stringify(body, null, 2)
-
-  const digest = hashObject(
-    (body as { parametricReform: ParametricReform }).parametricReform,
-  )
-
-  const simulationDir = path.join(simulationsBudgetDir, digest.substring(0, 2))
-  const simulationFilePath = path.join(simulationDir, `${digest}.json`)
-  if (!(await fs.pathExists(simulationFilePath))) {
-    await fs.ensureDir(simulationDir)
-    await fs.writeFile(simulationFilePath, bodyJson)
+  const { displayMode, hash } = body as {
+    displayMode: DisplayMode
+    hash: string
   }
 
-  const indexDir = path.join(simulationsBudgetDir, "index.json")
-  const indexContents: CachedSimulation[] = (await fs.pathExists(indexDir))
-    ? await fs.readJson(indexDir)
-    : []
-  const contents: CachedSimulation[] = [
-    ...indexContents,
-    {
-      date: new Intl.DateTimeFormat("fr-FR").format(new Date()),
-      hash: digest,
-      parameters: modifiedParametersNames,
-      title: modifiedParametersTitles.join(" | "),
-    } as CachedSimulation,
-  ].filter(
-    (value, index, self) =>
-      index === self.findLastIndex((el) => el.hash === value.hash),
+  /*
+   *
+   * Add display mode to public simulation (not needed for private cache)
+   *
+   * */
+
+  const simulationDir = path.join(simulationsBudgetDir, hash.substring(0, 2))
+  const simulationFilePath = path.join(simulationDir, `${hash}.json`)
+  const simulationContents = await fs.readJson(simulationFilePath)
+  simulationContents.displayMode = displayMode
+  await fs.writeFile(
+    simulationFilePath,
+    JSON.stringify(simulationContents, null, 2),
   )
-  await fs.writeFile(indexDir, JSON.stringify(contents))
 
-  return json({ token: digest })
+  /*
+   *
+   * Modify index.json file to indicate cache is now public
+   *
+   * */
+
+  const indexFilePath = path.join(simulationsBudgetDir, "index.json")
+  const indexContents = (await fs.readJson(indexFilePath)) as CachedSimulation[]
+  indexContents.forEach((cachedSimulation) => {
+    if (cachedSimulation.hash === hash) {
+      cachedSimulation.public = true
+    }
+  })
+  await fs.writeFile(indexFilePath, JSON.stringify(indexContents, null, 2))
+
+  return json({ hash })
 }
diff --git a/src/routes/simulations_budget/[simulation]/+page.server.ts b/src/routes/simulations_budget/[simulation]/+page.server.ts
index 6417a70664691db40c7f8d7a41b982b7c94e1f91..aeeaa7ac356e8f950f66b03009ca8514f2ff494c 100644
--- a/src/routes/simulations_budget/[simulation]/+page.server.ts
+++ b/src/routes/simulations_budget/[simulation]/+page.server.ts
@@ -15,6 +15,7 @@ import config from "$lib/server/config"
 
 import type { PageServerLoad } from "./$types"
 import type { BudgetCalculationResult } from "$lib/budgets"
+import { CachedSimulation } from "$lib/simulations"
 
 const { simulationsBudgetDir } = config
 
@@ -47,7 +48,11 @@ function auditParams(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
   return audit.reduceRemaining(data, errors, remainingKeys)
 }
 
-export const load: PageServerLoad = async ({ params: requestParams, url }) => {
+export const load: PageServerLoad = async ({
+  locals,
+  params: requestParams,
+  url,
+}) => {
   const [params, paramsError] = auditParams(cleanAudit, requestParams)
   if (paramsError !== null) {
     console.error(
@@ -61,12 +66,23 @@ export const load: PageServerLoad = async ({ params: requestParams, url }) => {
   }
   const { simulation: digest } = params as { simulation: string }
 
+  const indexFilePath = path.join(simulationsBudgetDir, "index.json")
+  const indexContents: CachedSimulation[] = (await fs.pathExists(indexFilePath))
+    ? await fs.readJson(indexFilePath)
+    : []
+
   const simulationFilePath = path.join(
     simulationsBudgetDir,
     digest.substring(0, 2),
     `${digest}.json`,
   )
-  if (!(await fs.pathExists(simulationFilePath))) {
+  if (
+    !(await fs.pathExists(simulationFilePath)) ||
+    (locals.user === undefined &&
+      !indexContents.find(
+        (cachedSimulation) => cachedSimulation.hash === digest,
+      )?.public)
+  ) {
     throw error(404, `Simulation ${digest} not found`)
   }
   return {
diff --git a/src/routes/simulations_budget/[simulation]/+page.svelte b/src/routes/simulations_budget/[simulation]/+page.svelte
index b86717590fc272862e570a5ca6a84be497a308b0..293e77d7b7182603bddbd212a8819c8c22a537b7 100644
--- a/src/routes/simulations_budget/[simulation]/+page.svelte
+++ b/src/routes/simulations_budget/[simulation]/+page.svelte
@@ -2,7 +2,6 @@
   import { getContext, onMount } from "svelte"
   import type { Writable } from "svelte/store"
 
-  import type { BudgetSimulationCache } from "$lib/budgets"
   import type { ParametricReform } from "$lib/reforms"
 
   import type { PageData } from "./$types"
@@ -11,9 +10,6 @@
 
   export let data: PageData
 
-  const budgetSimulationCache = getContext("budgetSimulationCache") as Writable<
-    BudgetSimulationCache | undefined
-  >
   const parametricReform = getContext(
     "parametricReform",
   ) as Writable<ParametricReform>
@@ -21,13 +17,6 @@
   $: ({ simulation } = data)
 
   onMount(() => {
-    $budgetSimulationCache = {
-      budgetSimulation: {
-        result: simulation.budgetSimulation,
-        errors: [],
-      },
-      parametricReform: simulation.parametricReform,
-    }
     $parametricReform = simulation.parametricReform
     goto(
       simulation.displayMode === undefined
diff --git a/src/routes/simulations_budget/index/+server.ts b/src/routes/simulations_budget/index/+server.ts
index b8820b7dfd379daba454d35398da1464ce447369..002a69846a41f7bffe4d92612750671ff4b9e7c3 100644
--- a/src/routes/simulations_budget/index/+server.ts
+++ b/src/routes/simulations_budget/index/+server.ts
@@ -12,9 +12,13 @@ const { simulationsBudgetDir } = config
 export const POST: RequestHandler = async () => {
   const indexDir = path.join(simulationsBudgetDir, "index.json")
 
+  const cachedSimulations = (await fs.readJson(indexDir)) as CachedSimulation[]
+
   return json({
-    index: (await fs.pathExists(indexDir))
-      ? ((await fs.readJson(indexDir)) as CachedSimulation[])
+    simulations: (await fs.pathExists(indexDir))
+      ? cachedSimulations.filter(
+          (simulation) => simulation.public && simulation.parameters.length > 0,
+        )
       : [],
   })
 }
diff --git a/src/routes/simulations_budget/server.old.ts b/src/routes/simulations_budget/server.old.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99e4ac2c2f52849e44699c49447ca18d9b4e1aae
--- /dev/null
+++ b/src/routes/simulations_budget/server.old.ts
@@ -0,0 +1,114 @@
+import { auditRequire, cleanAudit, type Audit } from "@auditors/core"
+import { error, json } from "@sveltejs/kit"
+import fs from "fs-extra"
+import path from "path"
+
+import { getParameter, rootParameter } from "$lib/parameters"
+import type { ParametricReform } from "$lib/reforms"
+import config from "$lib/server/config"
+import type { CachedSimulation } from "$lib/simulations"
+
+import type { RequestHandler } from "./$types"
+import { hashObject } from "$lib/hash"
+
+const { simulationsBudgetDir } = config
+
+function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
+  if (dataUnknown == null) {
+    return [dataUnknown, null]
+  }
+  if (typeof dataUnknown !== "object") {
+    return audit.unexpectedType(dataUnknown, "object")
+  }
+
+  const data = { ...dataUnknown }
+  const errors: { [key: string]: unknown } = {}
+  const remainingKeys = new Set(Object.keys(data))
+
+  audit.attribute(
+    data,
+    "budgetSimulation",
+    true,
+    errors,
+    remainingKeys,
+    // TODO
+    auditRequire,
+  )
+
+  audit.attribute(
+    data,
+    "displayMode",
+    true,
+    errors,
+    remainingKeys,
+    // TODO
+    auditRequire,
+  )
+
+  audit.attribute(
+    data,
+    "parametricReform",
+    true,
+    errors,
+    remainingKeys,
+    // TODO
+    auditRequire,
+  )
+
+  return audit.reduceRemaining(data, errors, remainingKeys)
+}
+
+export const POST: RequestHandler = async ({ request, url }) => {
+  const [body, bodyError] = auditBody(cleanAudit, await request.json())
+  if (bodyError !== null) {
+    console.error(
+      `Error in ${url.pathname} body:\n${JSON.stringify(
+        body,
+        null,
+        2,
+      )}\n\nError:\n${JSON.stringify(bodyError, null, 2)}`,
+    )
+    throw error(400, `Invalid body: ${JSON.stringify(bodyError, null, 2)}`)
+  }
+
+  const modifiedParametersTitles = Object.keys(
+    (body as { parametricReform: ParametricReform }).parametricReform,
+  ).map((parameterName) => getParameter(rootParameter, parameterName)?.title)
+
+  const modifiedParametersNames = Object.keys(
+    (body as { parametricReform: ParametricReform }).parametricReform,
+  ).map((parameterName) => getParameter(rootParameter, parameterName)?.name)
+
+  const bodyJson = JSON.stringify(body, null, 2)
+
+  const digest = hashObject(
+    (body as { parametricReform: ParametricReform }).parametricReform,
+  )
+
+  const simulationDir = path.join(simulationsBudgetDir, digest.substring(0, 2))
+  const simulationFilePath = path.join(simulationDir, `${digest}.json`)
+  if (!(await fs.pathExists(simulationFilePath))) {
+    await fs.ensureDir(simulationDir)
+    await fs.writeFile(simulationFilePath, bodyJson)
+  }
+
+  const indexDir = path.join(simulationsBudgetDir, "index.json")
+  const indexContents: CachedSimulation[] = (await fs.pathExists(indexDir))
+    ? await fs.readJson(indexDir)
+    : []
+  const contents: CachedSimulation[] = [
+    ...indexContents,
+    {
+      date: new Intl.DateTimeFormat("fr-FR").format(new Date()),
+      hash: digest,
+      parameters: modifiedParametersNames,
+      title: modifiedParametersTitles.join(" | "),
+    } as CachedSimulation,
+  ].filter(
+    (value, index, self) =>
+      index === self.findLastIndex((el) => el.hash === value.hash),
+  )
+  await fs.writeFile(indexDir, JSON.stringify(contents))
+
+  return json({ token: digest })
+}