Skip to content

Data flow ​

This page describes what the code does: what is stored, where, and which outside service sees which data. The privacy policy is the legal text for agisse.xyz. A self-hosted copy uses your own accounts for each service below, and leaves out any service you don't configure.

What is stored, and where ​

DataWhereNotes
Account, sessions, sign-in methodPostgres: user, session, accountPasswords are hashed by Better Auth. An account unused for 24 months gets one email; 30 days later it's deleted unless its owner signed in (worker/lib/retention.ts)
Profile and résumé textPostgres: profileThe PDF's text is read in the browser (src/lib/pdf-text.ts)
Original résumé PDFR2: resumes/<userId>.pdfOne per user, replaced on re-upload
Own AI keyPostgres: ai_keyAES-GCM encrypted, never sent back to the browser; only the last four characters are shown
Saved postings, tracker, notes, draftsPostgres: jobPer user
Saved programmesPostgres: saved_programmePer user: the programme's link and when it was saved
Pasted posting textPostgres: postingShared across users, so two people who paste the same job reuse one evaluation. It holds the posting, not anyone's CV
People found at a companyPostgres: search, personPublic profile text from Exa, per user
Practice answers, interview and assessment prepPostgres: practice_answer, interview_question_set, training_session, …Per user. Mock-interview video stays in the browser
AI usagePostgres: usageFeature, model, tokens and cost, for the monthly allowance; the output and the seeker's note for 90 days
Feed pages per dayPostgres: feed_dayA count per day, for the feed's daily limit; deleted after 30 days
Admin actionsPostgres: admin_actionWhich admin exported, deleted, gave credit to or reset the free AI limit of which account id, and when; no content. Kept 2 years, also after the account is deleted
Live switchesPostgres: settingLimits an admin changed in /admin (daily caps, the feed's page cap, the monthly allowance), with the account id of who changed each last; set to null when that account is deleted
Web search cachePostgres: exa_cacheDeleted after 7 days
Company factsPostgres: companyFrom Wikidata, refreshed after 30 days
Job feedPostgres: vacancy, profile_embedding, vacancy_fitWritten by a feed provider outside this repo, see below
Education programmesPostgres: programmePublic facts about programmes, written by a provider outside this repo (education contract); no personal data

Deleting an account removes the user's own rows (foreign keys cascade) and their PDF in R2. Shared rows, such as how a company's hiring rounds run (assessment_intel, selection_intel), stay, with the link to the user removed.

Who sees what ​

From the Worker ​

ServiceWhenWhat it receives
NeonAlwaysEverything stored above
Cloudflare R2Résumé uploadThe PDF
OpenRouterAn AI feature, on the server's key or the user's OpenRouter keyWhat the feature needs, such as résumé text, profile and a posting
Mistral AI, EU endpoint only (through OpenRouter)Most AI features on the server's key, by default (OPENROUTER_MODELS); not tailored CVsThe same
OpenAI, Anthropic, Google Gemini or MistralAn AI feature, when the user connected that provider's keyThe same, sent straight to that provider instead of OpenRouter
ExaPeople and company search (EXA_API_KEY)The company, role and search terms
WikidataCompany factsA company's website domain
AdzunaCity searchA role and a city
Public ATS boards (Greenhouse, Lever, Ashby, …)The daily closed-posting checkThe board's public listing URL
ResendAccount email (RESEND_API_KEY)The recipient's email address and the message
GoogleSign in with Google (GOOGLE_CLIENT_ID)The standard OAuth exchange
SentryWorker errors (SENTRY_DSN)The error message and stack, the request address without its query string or ids; no request bodies, cookies or user. agisse.xyz's project is in Sentry's EU region (Frankfurt) and keeps errors 90 days

Before any model call, on any key, email addresses, phone numbers, labelled birth dates, addresses and photo lines, and national ID numbers in the prompt are swapped for placeholders such as [email 1] (worker/lib/redact.ts). The real values go back into the model's answer, so a tailored CV keeps its header. Names stay: a cover letter needs one.

From the browser ​

ServiceWhenWhat it receives
Nominatim (OpenStreetMap)The world mapLocation names of roles, and the user's IP address
OpenFreeMapThe world mapMap tile requests, and the user's IP address
logo.devCompany logos (VITE_LOGO_DEV_TOKEN)A company's domain, and the user's IP address
Cloudflare TurnstileThe sign-up and sign-in forms (TURNSTILE_SECRET_KEY)The bot check, and the user's IP address
PostHogAnalytics (VITE_POSTHOG_KEY and VITE_POSTHOG_HOST)Pages viewed and named feature events, with the account id (never the name or email). Addresses lose their query string and ids (/jobs/:id), so reset tokens and invite codes never leave. No session recordings, autocapture, heatmaps or surveys; off when the browser sends Do Not Track. With POSTHOG_PERSONAL_API_KEY, deleting the account asks PostHog to delete that id's person and events
SentryBrowser errors (VITE_SENTRY_DSN)The error message and stack, the page address without its query string or ids (/jobs/:id), and the browser and OS. Breadcrumbs keep only page changes and request addresses, scrubbed the same way: no clicks, typing or console lines. No account id, name or email, no tracing or session replay; off when the browser sends Do Not Track. EU region (Frankfurt), 90 days
The browser's speech recognitionMock interview, where the browser has itThe answer's audio, handled by the browser vendor

Where the browser has no speech recognition (Firefox), the answer's audio goes once to the Worker, which has a model transcribe it and keeps only the text.

The extension talks only to Agisse, and only when you click.

The job feed ​

A feed provider fills the job feed. It isn't part of this repository; agisse.xyz's own provider is private. It writes public early-career vacancies into vacancy, embeds each user's profile and résumé into profile_embedding, and writes a fit score per user and role into vacancy_fit, so it reads users' profiles and résumés. The feed contract describes those tables. A self-hosted copy has none of this unless you run a provider that writes them; the feed then shows whatever vacancy holds, such as the synthetic roles from npm run db:seed.

Telemetry ​

Off by default. A self-hosted copy sends nothing to Agisse, Sentry or PostHog unless you set SENTRY_DSN, VITE_SENTRY_DSN, VITE_POSTHOG_KEY and VITE_POSTHOG_HOST, or SENTRY_AUTH_TOKEN. Your own keys send to your own accounts.