Skillara AI is composed of two independent, separately deployable services: a React single-page application that handles the user interface and a Node.js/Express API server that handles AI routing, prompt construction, and PDF rendering. The two services communicate exclusively over HTTP and can be hosted on different platforms — the production deployment runs the frontend on Vercel and the backend on Render.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/CristianParadaLopez/cv-builder/llms.txt
Use this file to discover all available pages before exploring further.
Frontend
The frontend is a React 19 application built with Vite and TypeScript, styled with Tailwind CSS, and deployed on Vercel. It is a single-page app with client-side routing managed by React Router v7. Application routes:| Path | Page |
|---|---|
/ | Home / landing page |
/builder | CV form and preview builder |
/guia | Usage guide |
/about | About the project |
/ats | ATS mode explainer |
/plataformas | Job platforms guide |
/seguridad | Privacy and security information |
/faq | Frequently asked questions |
/login | Google OAuth login |
/dashboard | Saved CVs (protected — requires authentication) |
GoogleAuthProvider to implement Google OAuth sign-in. Authenticated users’ generated CVs are stored in Firestore via the db client exported from firebase/config.ts. The /dashboard route is wrapped in a ProtectedRoute component that redirects unauthenticated users to /login.
Local persistence:
Form data is persisted to localStorage through a usePersistCV hook so that in-progress CVs survive page refreshes. This works for both authenticated and anonymous users and requires no backend involvement.
Backend
The backend is an Express 5 server written in TypeScript, running on port3001 by default (configurable via the PORT environment variable). It is deployed on Render and configured to accept CORS requests from the two Vercel frontend domains and any URL set in the FRONTEND_URL environment variable.
API routes:
| Method | Path | Description |
|---|---|---|
POST | /api/cv/generate | Generate a new CV from form data. Requires name and email in formData. |
POST | /api/cv/edit | Edit an existing CV HTML string using a natural-language prompt (max 1000 characters). |
POST | /api/cv/suggest | Improve a single form field value using AI. Accepts userText, context, and optional systemPrompt / examples. |
User— stores user accounts with optionalgoogleIdfor Firebase Auth linkage,photoURL, andemailVerifiedstate.CV— stores the full HTML output (html), an optional ATS variant (htmlATS), the originalformDataas JSON, thestyleandmodeused, a title, a last-editprompt, and visibility/view-count fields.FormCache— caches form data for anonymous sessions identified by asessionIdUUID, allowing server-side persistence without requiring login.
AI routing
All AI calls are handled by thecallWithFallback function in claude.service.ts. Rather than relying on a single API key or model, the system maintains a pool of OpenRouter API keys and a list of model identifiers, then tries every key–model combination in sequence until one succeeds.
Key pool:
Up to five OpenRouter API keys can be configured via environment variables (OPENROUTER_API_KEY_1 through OPENROUTER_API_KEY_5), each optionally named (DEV1_NAME through DEV5_NAME). Keys with empty values are filtered out at startup. The order is DEV2 → DEV3 → DEV4 → DEV5 → DEV1.
Model list:
The models to try are read from the AI_MODELS environment variable as a comma-separated list. The default is:
https://openrouter.ai/api/v1) using the standard OpenAI SDK with a custom baseURL.
Fallback logic:
For each request, callWithFallback iterates over the key pool and, for each key, iterates over the model list. If a request fails with HTTP status 429, 402, or 503 (rate limit, quota, or service unavailable), or if the returned content is empty or shorter than 100 characters, the function logs a warning and moves to the next combination. Only if every combination fails does it throw an error to the caller.
Data flow
The following sequence describes the complete path from user input to downloaded PDF:- User fills the form in the React frontend at
/builderand selects a template style and mode. - Frontend sends
POST /api/cv/generatewith a JSON body containingformData,style, andmode. - Backend validates the request — it checks that
nameandemailare present and normalisesstyleandmodeto allowed values. - Backend builds a prompt by combining the candidate’s form data with the detailed
styleGuidestring for the selected template (or the ATS guide formode: "ats"). If a profile photo was included, it is stripped from the data sent to the AI and aPHOTO_PLACEHOLDERinstruction is injected instead. callWithFallbackselects a key + model combination and calls the OpenRouter API. On success it returns the raw model output.- AI returns complete HTML for the styled CV document. The backend extracts the HTML (stripping any markdown fences), then re-injects the base64 photo at the
PHOTO_PLACEHOLDERsrcattribute if a photo was provided. - Backend returns
{ html }to the frontend, which renders it inside the preview iframe. - User optionally edits the result by typing a natural-language instruction into the PromptBar. The frontend sends
POST /api/cv/editwith the current HTML and the instruction; the backend repeats the AI call and returns updated HTML. - User clicks “Descargar PDF”. The frontend generates the PDF entirely in the browser using
html2canvasandjsPDF— no additional request is made to the backend. The file is saved locally asmi-cv-skillara.pdf.
Frontend source
Explore the builder form, PromptBar, preview panel, and PDF download flow.
API Reference
Full request and response schemas for every backend endpoint.
AI Keys config
How to configure OpenRouter API keys and choose which models to enable.
Database setup
Provisioning a PostgreSQL database and running Prisma migrations.