Skip to content

Configuration

All configuration is done through environment variables in the .env file. At least one provider must be configured (Gemini, Groq, Cerebras, or Ollama); the route returns 503 otherwise.

VariableRequiredDescription
GEMINI_API_KEYOne of theseGoogle AI API key (Gemini 3.5 Flash Lite)
GROQ_API_KEYOne of theseGroq API key (Llama 3.3 70B). Legacy model; verify availability and quotas before enabling.
CEREBRAS_API_KEYOne of theseCerebras API key (Llama 3.3 70B). Legacy model; verify availability and quotas before enabling.
OLLAMA_BASE_URLOne of theseBase URL of a local Ollama daemon (e.g. http://127.0.0.1:11434)
OLLAMA_MODELOptionalOllama model tag, defaults to llama3.2. Use any tag from ollama list.
OLLAMA_API_KEYOptionalBearer token sent as Authorization: Bearer {key} on every Ollama request. Only needed if your Ollama is behind auth.

The LLM chain composes from whatever’s configured in env. Ordering is fixed:

  1. Ollama (OLLAMA_BASE_URL), local first when configured
  2. Gemini 3.5 Flash Lite via Google (GEMINI_API_KEY)
  3. Llama 3.3 70B via Groq (GROQ_API_KEY)
  4. Llama 3.3 70B via Cerebras (CEREBRAS_API_KEY)

Exactly one model per vendor. A second model on the same API key shares that key’s quota, so it adds latency without adding redundancy; crossing vendors is what makes the fallback meaningful.

The API accepts and sends 50,000 résumé and 20,000 job-description UTF-16 code units. All prompt builders preserve input text. The hosted deployment currently uses only Gemini 3.5 Flash Lite. Its published model limits are 1,048,576 input tokens and 65,536 output tokens. The app retains its 6,144-token output budget and platform scoring rules. Instructions precede the full documents, with a final evidence check after the résumé. Before declaring a skill missing, the model must recheck the entire résumé, including its final sections. It first emits a shared ledger of required-skill matches with résumé quotes; exact matches stay present across all six platforms, while semantic matches follow each platform’s strategy. Character limits are not token counts; language, punctuation, and extracted text affect tokenization.

Gemini rate limits depend on the account and shared project usage. A successful full-input request does not guarantee concurrent throughput or remaining daily quota. No paid plan, quota, or spend-limit change is required by this update. See the deployment acceptance record for measured input/output usage and latency.

Ollama requests 32,768 context tokens and at most 6,144 output tokens. It sends truncate: false and shift: false to reject context overflow instead of losing input. Use a current server that supports these fields and a model with sufficient context and memory; older servers that ignore them are unsupported for complete-input analysis. Local models and non-English/token-dense documents may exceed that context. Ollama retains its existing four-minute provider timeout; deployments using it must also accommodate that deadline in their host and client (the hosted client waits 65s).

Run node scripts/test-providers.mjs with local provider keys to measure the real maximum-input scoring prompt. Run node scripts/verify-full-input.mjs ORIGIN OUTPUT.json against a deployed preview or production instance to check capabilities, targeted and general scoring, job-description extraction, and end-of-document evidence using only fictional data.

If a provider fails (timeout, rate limit, malformed response), the system automatically tries the next one. Because each provider uses a separate credential, their quotas are completely independent. Self-hosters who want a fully offline scanner should set only OLLAMA_BASE_URL and leave the cloud keys unset.

For privacy-first deployments where every byte of the resume stays on your machine:

Terminal window
# install ollama from https://ollama.com and pull a model
ollama pull llama3.2
# in your .env (or as shell vars before pnpm dev):
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=llama3.2
# leave GEMINI_API_KEY / GROQ_API_KEY unset for offline-only mode

The Ollama path uses Ollama’s format: 'json' so the model returns strict JSON without prompt-engineering tricks. First scan is slow on commodity hardware (60-120s for llama3.2:3b on a typical laptop); subsequent scans of the same resume hit the in-memory result cache and return in <100ms. Bigger models produce noticeably better suggestions but take longer.

The /api/analyze response includes _provider: "ollama-{model}" so you can confirm requests are landing locally and not falling back to a cloud key you forgot to remove.

Vanilla ollama serve on 127.0.0.1 has no authentication, which is fine for a local-only setup. If your Ollama lives behind a reverse proxy that requires a bearer token, or you’re pointing at a hosted Ollama-compatible endpoint (OpenWebUI, LiteLLM, OpenRouter’s Ollama-compatible routes, a Cloudflare-tunneled daemon with a service token, etc.), set OLLAMA_API_KEY and the request will include Authorization: Bearer {key} on every call:

Terminal window
# in your .env
OLLAMA_BASE_URL=https://ollama.your-domain.tld
OLLAMA_MODEL=llama3.2
OLLAMA_API_KEY=sk-your-proxy-token

The header is only attached when the env var is non-empty, so leaving it unset keeps the request shape identical to the local-only setup. Empty or whitespace-only values are treated as not set so a stray OLLAMA_API_KEY= line in .env does not produce a malformed Authorization: Bearer header that the proxy would reject.

How users sign in (or whether they sign in at all) is a separate choice from the LLM provider, and it’s also driven by environment variables. ATS Screener supports three modes, picked automatically:

  • Anonymous: leave Firebase and LDAP unset. The scanner is open and history is local. This is the default.
  • Firebase: set the PUBLIC_FIREBASE_* variables for Google / email sign-in and synced history.
  • Active Directory: set LDAP_URL for on-premise AD sign-in.

See Authentication for the full comparison and the Active Directory guide for AD setup. The Firebase variables are listed below.

Terminal window
# self-host without firebase: leave every PUBLIC_FIREBASE_* var unset (the default).
# self-host with firebase: set all six.
PUBLIC_FIREBASE_API_KEY=...
PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
PUBLIC_FIREBASE_PROJECT_ID=your-project
PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
PUBLIC_FIREBASE_MESSAGING_SENDER_ID=1234567890
PUBLIC_FIREBASE_APP_ID=1:1234567890:web:abc
ProviderModelRPMRPDTPMCost
GoogleGemini 3.5 Flash Lite15500250KFree
GroqLlama 3.3 70B301,00012KFree
CerebrasLlama 3.3 70B---Free

The 12K TPM on Groq is the whole reason that leg retires on 2026-08-16: it is the only free-tier model there with enough headroom for this prompt, and every model that outlives it caps at 8K. Cerebras limits vary by account, so check your dashboard.

Every provider blocks at its limits and never auto-charges. You cannot accidentally incur costs.

For the latest limits, see the official documentation:

Rate limiting is configured in src/routes/api/analyze/+server.ts:

const RATE_LIMIT = {
maxPerMinute: 10,
maxPerDay: 200
};

Adjust these values based on your expected traffic and API key limits.

Each provider has its own timeout. Vercel Fluid Compute is enabled by default and allows up to 300 seconds on the Hobby plan:

// Google: 30s, Groq: 15s, Cerebras: 12s → worst case total: 57s
timeoutMs: 30_000; // buildGoogleProvider
timeoutMs: 15_000; // buildGroqProvider
timeoutMs: 12_000; // buildCerebrasProvider

Two constraints govern these numbers.

They must sum to less than the route’s maxDuration (60s), or the platform kills the function before the last leg can run, silently turning a three-provider chain into a shorter one. 30 + 15 + 12 leaves 3s of margin.

Provider deadlines include response-body reading. Output-token throughput alone is not a latency guarantee: input processing, queueing, and network time also count. The app retains 6,144 output tokens for Google and 3,072 for Groq/Cerebras. Responses with token-limit or other unsuccessful finish reasons are rejected even if their text happens to parse as JSON. No shorter prompt or lower-quality rubric is retried.

If every provider fails the route returns 503 and logs llm.all_providers_failed at error level, and the client falls back to rule-based scoring. Successful provider logs include duration and numeric input/output token usage when reported by the provider; they do not include the submitted documents. Gemini answer text is assembled from all non-thought response parts. Malformed generated JSON is retried once with the identical complete prompt and output budget; both attempts share the existing provider deadline. Quota, token-limit, and timeout failures are not retried on the same provider. A retry can consume additional shared account quota; _tokenUsage describes the winning response and excludes failed attempts.