flowcvcli 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ # Copy to `.env` (in the dir you run `flowcv` from, or ~/.config/flowcvcli/.env)
2
+ # and fill in. Real environment variables override this file. Secrets are gitignored.
3
+
4
+ # Auth — pick ONE:
5
+ # (a) the session cookie: DevTools > Application > Cookies > app.flowcv.com >
6
+ # copy the `flowcvsidapp` value (only this cookie is needed for auth):
7
+ FLOWCV_COOKIE=flowcvsidapp=s%3A...
8
+ # (b) or credentials — the tool logs in and caches the session to
9
+ # ~/.config/flowcvcli/session (override with $FLOWCV_SESSION_FILE):
10
+ # FLOWCV_EMAIL=you@example.com
11
+ # FLOWCV_PASSWORD=...
12
+
13
+ # RESUME_ID is OPTIONAL: with one resume the tool auto-selects it; set this (or
14
+ # pass --resume-id) only if your account has several. Get ids via `flowcv resumes`.
15
+ # FLOWCV_RESUME_ID=00000000-0000-0000-0000-000000000000
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dannyota
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include .env.example
4
+ recursive-include docs *.md
5
+
6
+ # never ship local/dev artifacts or anything with personal data
7
+ exclude flowcv.py
8
+ prune .playwright-mcp
9
+ global-exclude *.pyc __pycache__ .env .flowcv_env .flowcv_session *.pdf *.html
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: flowcvcli
3
+ Version: 0.2.0
4
+ Summary: Control a FlowCV resume from the command line or Python — content, design, templates, photo, publish and PDF export — via FlowCV's private JSON API. Zero dependencies.
5
+ Author: dannyota
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dannyota/flowcvcli
8
+ Project-URL: Repository, https://github.com/dannyota/flowcvcli
9
+ Project-URL: Documentation, https://github.com/dannyota/flowcvcli/blob/main/docs/API.md
10
+ Project-URL: Issues, https://github.com/dannyota/flowcvcli/issues
11
+ Keywords: flowcv,resume,cv,cli,resume-builder,json-api,automation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Topic :: Utilities
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Dynamic: license-file
30
+
31
+ # flowcvcli
32
+
33
+ [![PyPI](https://img.shields.io/pypi/v/flowcvcli.svg)](https://pypi.org/project/flowcvcli/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/flowcvcli.svg)](https://pypi.org/project/flowcvcli/)
35
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
36
+
37
+ Control a [FlowCV](https://flowcv.com) resume from the **command line** or from
38
+ **Python** — content, header & links, **customization**, **templates**,
39
+ **avatar**, reorder/hide, multi-resume management, publish, and **PDF export**.
40
+ It drives FlowCV's private JSON API (the same calls the web app makes), so it
41
+ works for any FlowCV resume with your own session. **Zero dependencies** (Python
42
+ standard library only), so it's easy to drop into scripts and LLM agents.
43
+
44
+ > Unofficial and not affiliated with FlowCV. It uses FlowCV's undocumented
45
+ > internal API and may break if that changes; use it with your own account and at
46
+ > your own risk (mind FlowCV's Terms of Service). See [`docs/API.md`](docs/API.md)
47
+ > for the reverse-engineered API and [`docs/RENDERING.md`](docs/RENDERING.md) for
48
+ > how the editor renders the live preview and persists edits.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install flowcvcli # installs the `flowcv` command
54
+ ```
55
+
56
+ Or run from source without installing:
57
+
58
+ ```bash
59
+ git clone https://github.com/dannyota/flowcvcli && cd flowcvcli
60
+ python3 flowcv.py --help # equivalent to the `flowcv` command
61
+ ```
62
+
63
+ ## Configure
64
+
65
+ Put a `.env` in the directory you run `flowcv` from (or in
66
+ `~/.config/flowcvcli/.env`). Real environment variables override it.
67
+
68
+ ```dotenv
69
+ # Auth — pick ONE:
70
+ FLOWCV_COOKIE=flowcvsidapp=s%3A... # your session cookie, OR
71
+ # FLOWCV_EMAIL=you@example.com # log in with credentials instead
72
+ # FLOWCV_PASSWORD=... # (session cached to ~/.config/flowcvcli/session)
73
+
74
+ # FLOWCV_RESUME_ID=... # optional; only if your account has several resumes
75
+ ```
76
+
77
+ - **Cookie**: DevTools → Application → Cookies → `app.flowcv.com` → copy the
78
+ `flowcvsidapp` value. That single cookie is the auth.
79
+ - **Credentials**: with `FLOWCV_EMAIL` + `FLOWCV_PASSWORD` the tool logs in and
80
+ caches the session (re-login is automatic when the cookie expires). The cache
81
+ is written `0600` to `~/.config/flowcvcli/session` (override with
82
+ `$FLOWCV_SESSION_FILE`).
83
+ - **Resume id** is optional: with one resume it's auto-selected; with several,
84
+ set `FLOWCV_RESUME_ID` or pass `--resume-id <id>` (run `flowcv resumes` to list).
85
+
86
+ ## CLI
87
+
88
+ ```bash
89
+ flowcv resumes # list resumes (id, title, share token)
90
+ flowcv show [section] # sections + entries (ids, labels, dates)
91
+ flowcv dump <section> <id> # one entry, fields + rich text
92
+
93
+ # manage resumes (multi-resume / paid plans)
94
+ flowcv new "My Second Resume" # new resume (same details+style, no content) -> prints id
95
+ flowcv duplicate ["Copy title"] # full copy of the current resume
96
+ flowcv rename "New Title" # rename the current resume
97
+ flowcv delete-resume --yes # permanent (refuses without --yes)
98
+
99
+ # content (markdown mini-format below); `add` creates the section if needed
100
+ flowcv add work --set title="Engineer" --set company="Acme" \
101
+ --set start=01/2022 --set end=Present --text $'- Did a measurable thing.'
102
+ flowcv desc work <id> --file role.md
103
+ flowcv field work <id> employer --text "Acme Corp"
104
+ flowcv rm work <id>
105
+
106
+ # reorder / hide / sections
107
+ flowcv reorder work <id3> <id1> <id2> # set entry order (all of the section's ids)
108
+ flowcv hide work <id> ; flowcv show-entry work <id>
109
+ flowcv rename-section skill "Core Skills"
110
+ flowcv section-icon skill head-side-brain
111
+ flowcv rm-section custom1 --yes # delete a section + its entries
112
+ flowcv reorder-sections profile work skill education # one-column order
113
+
114
+ # header details & links (links are social entries: orcid, googlescholar, github…)
115
+ flowcv pd jobTitle --text "Security Leader"
116
+ flowcv link orcid ORCID https://orcid.org/0000-0000-0000-0000
117
+ flowcv unlink orcid ; flowcv links
118
+
119
+ # avatar
120
+ flowcv avatar set https://example.com/me.png # upload from URL or file
121
+ flowcv avatar on | off | remove
122
+
123
+ # styling (a delta into resume.customization) and templates
124
+ flowcv customize font.fontFamily "Source Sans Pro"
125
+ flowcv customize colors.basic.single '"#0e374e"'
126
+ flowcv templates # lists each as [free] / [PAID] (paid needs a subscription)
127
+ flowcv apply-template <templateId> # warns first if the template is paid
128
+
129
+ # render & share
130
+ flowcv download -o resume.pdf # the rendered PDF
131
+ flowcv download --token <webToken> -o out.pdf # any PUBLIC resume by its share token (no auth)
132
+ flowcv share | publish | unpublish
133
+
134
+ flowcv login # refresh the cached session
135
+ flowcv md2html --file role.md # preview HTML (offline)
136
+ ```
137
+
138
+ Any command takes `--resume-id <id>` to target a specific resume. (From source,
139
+ replace `flowcv` with `python3 flowcv.py`.)
140
+
141
+ ## Library (for scripts & LLM agents)
142
+
143
+ ```python
144
+ from flowcvcli import FlowCV
145
+
146
+ fc = FlowCV() # or FlowCV(resume_id="...")
147
+ fc.set_personal_field("fullName", "Jane Doe")
148
+ fc.add_entry("work", sets={"jobTitle": "Engineer", "employer": "Acme",
149
+ "startDateNew": "01/2022", "endDateNew": "Present"},
150
+ md="- Shipped a thing with **measurable** impact.")
151
+ fc.set("font.fontFamily", "Source Sans Pro") # a customization delta
152
+ fc.set_photo("https://example.com/me.png") # avatar from URL
153
+ fc.apply_template("a3fb6c37-...") # a design from list_templates()
154
+ fc.save_pdf("resume.pdf") # render to PDF
155
+
156
+ # structure & resume management
157
+ fc.reorder_entries("work", ["id3", "id1", "id2"]) # set entry order
158
+ fc.rename_section("skill", "Core Skills"); fc.delete_section("custom1")
159
+ fc.hide_entry("work", "id", hidden=True)
160
+ new_id = fc.create_resume("Second Resume") # or fc.duplicate_resume()
161
+ fc.rename_resume("New Title"); fc.delete_resume() # delete is permanent
162
+ ```
163
+
164
+ ### Build → render → check → improve
165
+
166
+ The PDF *is* the rendered output. An agent can write content, `save_pdf(...)`,
167
+ **open the PDF to see the actual layout**, then adjust and re-render — a closed
168
+ feedback loop for building a resume from raw info.
169
+
170
+ ## Markdown mini-format (`desc` / `add`)
171
+
172
+ | You write | You get |
173
+ |---|---|
174
+ | blank line | block separator |
175
+ | `## Heading` / `**Whole line bold**` | bold subheader |
176
+ | `- item` | bullet (consecutive = one list) |
177
+ | anything else | justified paragraph |
178
+ | `**bold**` inline | `<strong>bold</strong>` |
179
+
180
+ ## How it works
181
+
182
+ - **Read-modify-write**: edits fetch the resume, change one part, and send it
183
+ back — unrelated fields are never touched.
184
+ - New entries append to the bottom of their section; use `reorder` to change order.
185
+ - The on-screen preview is client-side HTML; the **PDF download is a separate
186
+ server render** of the same data (details in [`docs/RENDERING.md`](docs/RENDERING.md)).
187
+
188
+ > **Scope:** this tool covers **resumes**. The same FlowCV account also has Cover
189
+ > Letters, Job Tracker, Email Signatures and Personal Websites (separate APIs —
190
+ > see `docs/API.md` "Other FlowCV products"); documented but not implemented here.
191
+
192
+ ## Project layout
193
+
194
+ ```
195
+ flowcvcli/ # the package (import flowcvcli)
196
+ config.py # resolve resume id + auth from .env / env vars
197
+ client.py # HTTP, login, cookie-jar session, retry, get_resume
198
+ markup.py # markdown <-> FlowCV rich-text HTML
199
+ content.py # sections & entries (add/edit/reorder/hide/sections)
200
+ personal.py # header details & links
201
+ customization.py # styling deltas & templates
202
+ photo.py # avatar upload / toggle
203
+ resume.py # list, create/duplicate/rename/delete, download, publish
204
+ api.py # FlowCV = Client + all mixins
205
+ cli.py / __main__.py # the `flowcv` command
206
+ docs/API.md # reverse-engineered API reference
207
+ docs/RENDERING.md # how the editor renders the preview & debounces saves
208
+ flowcv.py # source-tree entry point (python3 flowcv.py …)
209
+ ```
210
+
211
+ ## License
212
+
213
+ [MIT](LICENSE) © dannyota
@@ -0,0 +1,183 @@
1
+ # flowcvcli
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/flowcvcli.svg)](https://pypi.org/project/flowcvcli/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/flowcvcli.svg)](https://pypi.org/project/flowcvcli/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ Control a [FlowCV](https://flowcv.com) resume from the **command line** or from
8
+ **Python** — content, header & links, **customization**, **templates**,
9
+ **avatar**, reorder/hide, multi-resume management, publish, and **PDF export**.
10
+ It drives FlowCV's private JSON API (the same calls the web app makes), so it
11
+ works for any FlowCV resume with your own session. **Zero dependencies** (Python
12
+ standard library only), so it's easy to drop into scripts and LLM agents.
13
+
14
+ > Unofficial and not affiliated with FlowCV. It uses FlowCV's undocumented
15
+ > internal API and may break if that changes; use it with your own account and at
16
+ > your own risk (mind FlowCV's Terms of Service). See [`docs/API.md`](docs/API.md)
17
+ > for the reverse-engineered API and [`docs/RENDERING.md`](docs/RENDERING.md) for
18
+ > how the editor renders the live preview and persists edits.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install flowcvcli # installs the `flowcv` command
24
+ ```
25
+
26
+ Or run from source without installing:
27
+
28
+ ```bash
29
+ git clone https://github.com/dannyota/flowcvcli && cd flowcvcli
30
+ python3 flowcv.py --help # equivalent to the `flowcv` command
31
+ ```
32
+
33
+ ## Configure
34
+
35
+ Put a `.env` in the directory you run `flowcv` from (or in
36
+ `~/.config/flowcvcli/.env`). Real environment variables override it.
37
+
38
+ ```dotenv
39
+ # Auth — pick ONE:
40
+ FLOWCV_COOKIE=flowcvsidapp=s%3A... # your session cookie, OR
41
+ # FLOWCV_EMAIL=you@example.com # log in with credentials instead
42
+ # FLOWCV_PASSWORD=... # (session cached to ~/.config/flowcvcli/session)
43
+
44
+ # FLOWCV_RESUME_ID=... # optional; only if your account has several resumes
45
+ ```
46
+
47
+ - **Cookie**: DevTools → Application → Cookies → `app.flowcv.com` → copy the
48
+ `flowcvsidapp` value. That single cookie is the auth.
49
+ - **Credentials**: with `FLOWCV_EMAIL` + `FLOWCV_PASSWORD` the tool logs in and
50
+ caches the session (re-login is automatic when the cookie expires). The cache
51
+ is written `0600` to `~/.config/flowcvcli/session` (override with
52
+ `$FLOWCV_SESSION_FILE`).
53
+ - **Resume id** is optional: with one resume it's auto-selected; with several,
54
+ set `FLOWCV_RESUME_ID` or pass `--resume-id <id>` (run `flowcv resumes` to list).
55
+
56
+ ## CLI
57
+
58
+ ```bash
59
+ flowcv resumes # list resumes (id, title, share token)
60
+ flowcv show [section] # sections + entries (ids, labels, dates)
61
+ flowcv dump <section> <id> # one entry, fields + rich text
62
+
63
+ # manage resumes (multi-resume / paid plans)
64
+ flowcv new "My Second Resume" # new resume (same details+style, no content) -> prints id
65
+ flowcv duplicate ["Copy title"] # full copy of the current resume
66
+ flowcv rename "New Title" # rename the current resume
67
+ flowcv delete-resume --yes # permanent (refuses without --yes)
68
+
69
+ # content (markdown mini-format below); `add` creates the section if needed
70
+ flowcv add work --set title="Engineer" --set company="Acme" \
71
+ --set start=01/2022 --set end=Present --text $'- Did a measurable thing.'
72
+ flowcv desc work <id> --file role.md
73
+ flowcv field work <id> employer --text "Acme Corp"
74
+ flowcv rm work <id>
75
+
76
+ # reorder / hide / sections
77
+ flowcv reorder work <id3> <id1> <id2> # set entry order (all of the section's ids)
78
+ flowcv hide work <id> ; flowcv show-entry work <id>
79
+ flowcv rename-section skill "Core Skills"
80
+ flowcv section-icon skill head-side-brain
81
+ flowcv rm-section custom1 --yes # delete a section + its entries
82
+ flowcv reorder-sections profile work skill education # one-column order
83
+
84
+ # header details & links (links are social entries: orcid, googlescholar, github…)
85
+ flowcv pd jobTitle --text "Security Leader"
86
+ flowcv link orcid ORCID https://orcid.org/0000-0000-0000-0000
87
+ flowcv unlink orcid ; flowcv links
88
+
89
+ # avatar
90
+ flowcv avatar set https://example.com/me.png # upload from URL or file
91
+ flowcv avatar on | off | remove
92
+
93
+ # styling (a delta into resume.customization) and templates
94
+ flowcv customize font.fontFamily "Source Sans Pro"
95
+ flowcv customize colors.basic.single '"#0e374e"'
96
+ flowcv templates # lists each as [free] / [PAID] (paid needs a subscription)
97
+ flowcv apply-template <templateId> # warns first if the template is paid
98
+
99
+ # render & share
100
+ flowcv download -o resume.pdf # the rendered PDF
101
+ flowcv download --token <webToken> -o out.pdf # any PUBLIC resume by its share token (no auth)
102
+ flowcv share | publish | unpublish
103
+
104
+ flowcv login # refresh the cached session
105
+ flowcv md2html --file role.md # preview HTML (offline)
106
+ ```
107
+
108
+ Any command takes `--resume-id <id>` to target a specific resume. (From source,
109
+ replace `flowcv` with `python3 flowcv.py`.)
110
+
111
+ ## Library (for scripts & LLM agents)
112
+
113
+ ```python
114
+ from flowcvcli import FlowCV
115
+
116
+ fc = FlowCV() # or FlowCV(resume_id="...")
117
+ fc.set_personal_field("fullName", "Jane Doe")
118
+ fc.add_entry("work", sets={"jobTitle": "Engineer", "employer": "Acme",
119
+ "startDateNew": "01/2022", "endDateNew": "Present"},
120
+ md="- Shipped a thing with **measurable** impact.")
121
+ fc.set("font.fontFamily", "Source Sans Pro") # a customization delta
122
+ fc.set_photo("https://example.com/me.png") # avatar from URL
123
+ fc.apply_template("a3fb6c37-...") # a design from list_templates()
124
+ fc.save_pdf("resume.pdf") # render to PDF
125
+
126
+ # structure & resume management
127
+ fc.reorder_entries("work", ["id3", "id1", "id2"]) # set entry order
128
+ fc.rename_section("skill", "Core Skills"); fc.delete_section("custom1")
129
+ fc.hide_entry("work", "id", hidden=True)
130
+ new_id = fc.create_resume("Second Resume") # or fc.duplicate_resume()
131
+ fc.rename_resume("New Title"); fc.delete_resume() # delete is permanent
132
+ ```
133
+
134
+ ### Build → render → check → improve
135
+
136
+ The PDF *is* the rendered output. An agent can write content, `save_pdf(...)`,
137
+ **open the PDF to see the actual layout**, then adjust and re-render — a closed
138
+ feedback loop for building a resume from raw info.
139
+
140
+ ## Markdown mini-format (`desc` / `add`)
141
+
142
+ | You write | You get |
143
+ |---|---|
144
+ | blank line | block separator |
145
+ | `## Heading` / `**Whole line bold**` | bold subheader |
146
+ | `- item` | bullet (consecutive = one list) |
147
+ | anything else | justified paragraph |
148
+ | `**bold**` inline | `<strong>bold</strong>` |
149
+
150
+ ## How it works
151
+
152
+ - **Read-modify-write**: edits fetch the resume, change one part, and send it
153
+ back — unrelated fields are never touched.
154
+ - New entries append to the bottom of their section; use `reorder` to change order.
155
+ - The on-screen preview is client-side HTML; the **PDF download is a separate
156
+ server render** of the same data (details in [`docs/RENDERING.md`](docs/RENDERING.md)).
157
+
158
+ > **Scope:** this tool covers **resumes**. The same FlowCV account also has Cover
159
+ > Letters, Job Tracker, Email Signatures and Personal Websites (separate APIs —
160
+ > see `docs/API.md` "Other FlowCV products"); documented but not implemented here.
161
+
162
+ ## Project layout
163
+
164
+ ```
165
+ flowcvcli/ # the package (import flowcvcli)
166
+ config.py # resolve resume id + auth from .env / env vars
167
+ client.py # HTTP, login, cookie-jar session, retry, get_resume
168
+ markup.py # markdown <-> FlowCV rich-text HTML
169
+ content.py # sections & entries (add/edit/reorder/hide/sections)
170
+ personal.py # header details & links
171
+ customization.py # styling deltas & templates
172
+ photo.py # avatar upload / toggle
173
+ resume.py # list, create/duplicate/rename/delete, download, publish
174
+ api.py # FlowCV = Client + all mixins
175
+ cli.py / __main__.py # the `flowcv` command
176
+ docs/API.md # reverse-engineered API reference
177
+ docs/RENDERING.md # how the editor renders the preview & debounces saves
178
+ flowcv.py # source-tree entry point (python3 flowcv.py …)
179
+ ```
180
+
181
+ ## License
182
+
183
+ [MIT](LICENSE) © dannyota
@@ -0,0 +1,208 @@
1
+ # FlowCV private API — reference
2
+
3
+ Reverse-engineered from the FlowCV web app (`app.flowcv.com`). Unofficial; for
4
+ personal use. Base: `https://app.flowcv.com/api`. All app endpoints are
5
+ same-origin and authenticated by the **`flowcvsidapp`** session cookie alone
6
+ (other cookies — `i18n`, `loggedin`, `appVersion` — are not needed for auth).
7
+
8
+ Standard JSON envelope: `{ "success": bool, "data": ..., "error": "", "code": int }`.
9
+ A missing endpoint returns `code:404`; an existing endpoint with a bad/empty body
10
+ returns `code:500` (handler ran, validation failed) — useful for probing.
11
+
12
+ For **how the editor renders the live preview and when it persists edits**, see
13
+ [`RENDERING.md`](RENDERING.md). Short version: the preview is client-side React
14
+ HTML (no PDF/canvas), edits update instantly with no network, and saves are
15
+ debounced into the `save_entry` / `save_personal_details` / `save_customization`
16
+ PATCHes documented below — i.e. exactly what this tool sends.
17
+
18
+ **Editor boot sequence** (what the SPA fetches on load): `GET /auth/init_user`,
19
+ `GET /resumes/all`, `GET /letters/all`, `GET /trackers/all`, `GET /signatures/all`,
20
+ `GET /websites/all`, `GET /users/fetch_subscription_infos`,
21
+ `GET /users/invoices/pending_review`, then `GET /resumes/{id}` for the open resume.
22
+
23
+ ## Auth
24
+
25
+ | Method | Path | Body | Notes |
26
+ |---|---|---|---|
27
+ | GET | `/auth/init_user` | — | seeds an (anonymous) session cookie. The web app calls it before login, but it is **optional** — login works standalone (the browser request skips it). |
28
+ | POST | `/auth/login` | `multipart/form-data`: `email`, `password` (+ empty `resumeData=undefined`, `letterData=undefined`, `resumeImg`, `letterImg`) | sets `flowcvsidapp` cookie on success. **Rate-limited per source IP** (≈100/day) — exhausting it on one machine doesn't affect another. |
29
+
30
+ Login flow: (optionally GET `init_user` for an anonymous session) → POST `login`
31
+ on the same cookie jar → the jar now holds the authenticated `flowcvsidapp`.
32
+ `init_user` is best-effort; a failure there must not block the login.
33
+
34
+ ## Resumes (resume-level)
35
+
36
+ | Method | Path | Body / Query | Returns |
37
+ |---|---|---|---|
38
+ | GET | `/resumes/all` | — | `data.resumes[]` (id, title, webToken, webResumeLive, order, …) |
39
+ | GET | `/resumes/{resumeId}` | — | `data.resume` (full resume object) |
40
+ | POST | `/resumes/create` | `{clientResume: {…full resume object…}}` | create a resume. The body must be a **complete** resume object (every NOT-NULL column), so the reliable way is to **clone a full existing resume** (`GET /resumes/{id}`), reassign `id`+`uuid`, set `title`, empty `content` (or keep it for a duplicate), and **drop** `webToken`/`feedbackToken`/`createdAt`/`updatedAt` (server regenerates). A hand-built partial body fails with Postgres `23502` (not-null). Note: the one-resume free-plan cap is **not** enforced on this endpoint. |
41
+ | POST | `/resumes/duplicate` | `{resumeId}` | native duplicate — but returned a generic error in testing; duplicating via `create` (clone, keep `content`) is what this tool does instead. |
42
+ | PATCH | `/resumes/rename_resume` | `{resumeId, resumeTitle}` | rename a resume |
43
+ | DELETE | `/resumes/delete_resume?resumeId={id}` | — | **permanently delete** a resume (irreversible) |
44
+ | PATCH | `/resumes/apply_template` | `{resumeId, templateId, customization: {…template's full customization…}, personalDetails: {…current…}}` | applies a design. `templateId` + `customization` come from the template list (below). |
45
+ | PATCH | `/resumes/publish_web_resume` | `{publish: bool, resumeId}` | toggle the public web resume |
46
+ | GET | `/resumes/download?resumeId={id}&previewPageCount={n}` | — | **PDF bytes** (`application/pdf`). `previewPageCount` does not truncate; any value returns the full doc. |
47
+ | GET | `/api/public/download_resume?token={webToken}` | — | **public** PDF of any *shared* resume by its web token — no auth/ownership needed. (Only when the resume's download is enabled; otherwise 400.) |
48
+ | DELETE | `/resumes/delete_entry?resumeId&sectionId&entryId` | — | delete a content entry (see below) |
49
+
50
+ The full-resume GET also exposes `webToken` (public URL
51
+ `https://flowcv.com/resume/{webToken}`), `webResumeLive`, `feedbackToken`. Top-level
52
+ resume keys (for the `create` clone): `id, userId, mongoId, title, order,
53
+ feedbackToken, webToken, uuid, feedbackEnabled, webResumeLive,
54
+ webResumeDownloadBtn, webResumeSearchIndex, webResumeCached, personalDetails,
55
+ content, customization, feedback, businessDetails, downloads,
56
+ usingBusinessTemplateId, schemaVersion, lastChangeAt, createdAt, updatedAt, lng,
57
+ tags`.
58
+
59
+ ## Content (sections & entries)
60
+
61
+ `data.resume.content` is a map of `sectionId → { entries[], iconKey, displayName,
62
+ sectionType }`. Known sections: `profile` (Summary), `work` (Experience),
63
+ `education`, `skill`, `publication`, `organisation`, `custom1` (sectionType
64
+ `custom`), plus language/certificate/interest/project/course/award/reference/
65
+ declaration.
66
+
67
+ | Method | Path | Body | Notes |
68
+ |---|---|---|---|
69
+ | PATCH | `/resumes/save_entry` | `{resumeId, sectionId, entry}` | **update** an existing entry (send the whole entry object). |
70
+ | PATCH | `/resumes/save_entry` | `{resumeId, sectionId, entry:{id, isHidden:false}, sectionType, sectionDisplayName, sectionIconKey}` | **create** an entry — required extra section-meta fields. If the section doesn't exist yet, this also **creates the section**. New entries append to the bottom. Populate fields with a follow-up update call. |
71
+ | DELETE | `/resumes/delete_entry?resumeId&sectionId&entryId` | — | delete an entry |
72
+ | PATCH | `/resumes/save_entries_order` | `{resumeId, sectionId, newEntriesIdsOrder:[id,…], disableAutoSort:true}` | **reorder entries** within a section (the array order). `disableAutoSort` keeps the manual order (else FlowCV auto-sorts by date). |
73
+ | PATCH | `/resumes/save_section_name` | `{resumeId, sectionId, displayName}` | **rename** a section heading |
74
+ | PATCH | `/resumes/save_section_icon` | `{resumeId, sectionId, iconKey}` | change a section's icon |
75
+ | DELETE | `/resumes/delete_section?resumeId&sectionId` | — | **delete a whole section** and all its entries |
76
+
77
+ To **hide/show** a single entry, `save_entry` it with `entry.isHidden = true|false`
78
+ (it stays in the resume but is omitted from output). **Reorder sections** by
79
+ writing `customization.sectionOrder.<layout>.sectionsSorted` (a list of section
80
+ ids) via `save_customization` — section order lives in `customization`, keyed per
81
+ column layout (`one`, `two`, `mix`), not in `content`. (`save_section` exists too
82
+ but 500s on every body shape tried; the granular `save_section_*` endpoints above
83
+ are what the app actually uses. `reorder_entries`/`reorder_sections`/`rename_section`
84
+ are all 404 — the real names are `save_entries_order`/`save_section_name`.)
85
+
86
+ Section meta (`sectionType`, `displayName`, `iconKey`) for creating sections:
87
+ `profile`→(profile, Summary, address-card), `work`→(work, Professional
88
+ Experience, briefcase), `education`→(education, Education, graduation-cap),
89
+ `skill`→(skill, Skills, head-side-brain), `publication`→(publication,
90
+ Publications, newspaper), `organisation`→(organisation, Organisations,
91
+ house-user), `custom1`→(custom, Custom, star).
92
+
93
+ Rich-text fields are HTML: `<p style="text-align: justify">…</p>` for paragraphs,
94
+ `<p…><strong>…</strong></p>` for bold subheaders, `<ul><li…><p…>…</p></li></ul>`
95
+ for bullets. `profile` entries use a `text` field; `skill` entries use `skill`
96
+ (title) + `infoHtml`; most others use `description`.
97
+
98
+ No reorder endpoint (`reorder_entries` 404s). To reorder, reassign entry content
99
+ across the existing array slots.
100
+
101
+ ## Personal details & header links
102
+
103
+ | Method | Path | Body |
104
+ |---|---|---|
105
+ | PATCH | `/resumes/save_personal_details` | `{resumeId, personalDetails: {…full object…}}` |
106
+
107
+ Always send the **full** `personalDetails` object with only the target field
108
+ changed (it replaces the whole object). Header links live in
109
+ `personalDetails.social` as `{platform: {display, link}}` (e.g. `linkedIn`,
110
+ `orcid`, `googlescholar`) and are shown per `personalDetails.detailsOrder`
111
+ (e.g. `["displayEmail","phone","address","linkedIn","orcid","googlescholar"]`).
112
+ The legacy single link is `personalDetails.website` + `websiteLink`.
113
+
114
+ ## Photo / avatar
115
+
116
+ | Method | Path | Body |
117
+ |---|---|---|
118
+ | POST | `/resumes/upload_profile_pic` | `multipart/form-data`: `resumeId` + `file` (image bytes) → `{data:{imageId:"avatar/….png"}}` |
119
+
120
+ Then save the id into `personalDetails.photo` (via `save_personal_details`):
121
+ `{imageId, shape:"round", xPct, yPct, widthPct, heightPct, originalWidth, originalHeight}`
122
+ (use a whole-image crop: xPct≈yPct≈0.0005, widthPct≈heightPct≈0.999). Toggle
123
+ display with the customization delta `header.photo.show` = `true|false`.
124
+
125
+ ## Customization (styling)
126
+
127
+ | Method | Path | Body |
128
+ |---|---|---|
129
+ | PATCH | `/resumes/save_customization` | `{resumeId, customizationUpdates: [{path, value}, …]}` |
130
+
131
+ **Delta API**: each update is a dot-`path` into `resume.customization` and a new
132
+ `value`. Examples:
133
+ - Columns: `layout.colsFromDetails.top|left|right` = `"one"|"two"`
134
+ - Font: `font.fontFamily` = `"Source Sans Pro"`, `font.selected` = `"serif"|"sans"`
135
+ - Colors: `colors.basic.single` = `"#0e374e"`, `colors.mode` = `"basic"|"advanced"`
136
+ - Spacing: `spacing.fontSize`, `spacing.lineHeight`, `spacing.marginHorizontal`
137
+ - Headings: `heading.style` = `"line"|"box"`, `heading.capitalization`
138
+ - Page: `pageFormat` = `"A4"|"Letter"`
139
+
140
+ The full `customization` schema is visible in `GET /resumes/{id}` (under
141
+ `data.resume.customization`) and in the `create` default.
142
+
143
+ The **Customize** panel groups (each = one or more delta paths under
144
+ `customization`) are: **Document** (page format, date format), **Templates**
145
+ (browse/apply, below), **Layout** (`layout.colsFromDetails…` columns one/two/mix,
146
+ per-section placement), **Font Size**, **Spacing** (`spacing.*`), **Entry Layout**,
147
+ **Section Headings** (`heading.style`, `heading.capitalization`, heading icons),
148
+ **Font** — separate **body font** and **name font** — **Colors**
149
+ (`colors.mode`/`colors.basic.single`, accent, and *Color Area*: full / page /
150
+ header / border), **Header** (text alignment, details arrangement, icon style),
151
+ **Photo** (`header.photo.show`), **Link Styling**, **Footer** (toggle page
152
+ numbers / email / name), and per-**Section** customizations. "Create template"
153
+ publishes the current design as a shareable template. The panel also has
154
+ **undo/redo**. All of these are just `save_customization` deltas — discover exact
155
+ paths by diffing `data.resume.customization` before/after a change in the UI.
156
+
157
+ ## Templates
158
+
159
+ | Method | Path | Returns |
160
+ |---|---|---|
161
+ | GET | `/pubcache/published-resume-templates` | the full template catalog (id, title, customization, `isPremium`, …) |
162
+ | GET | `/api/resume-templates/get_shared_template?resumeId={id}` | the template shared/applied to a resume |
163
+
164
+ Each catalog entry has **`isPremium`** (bool): `false` = free, `true` = needs a
165
+ FlowCV subscription to apply. Show this to users before they apply one.
166
+
167
+ To apply a template: pick its `templateId` + `customization` from the catalog and
168
+ PATCH `apply_template` (above).
169
+
170
+ ## Download & share menu (resume editor)
171
+
172
+ The editor's top-right controls map to these endpoints:
173
+
174
+ | UI control | Endpoint / effect |
175
+ |---|---|
176
+ | **Download** button | `GET /resumes/download` → PDF (server render). Shows a "✅ downloaded" modal after. |
177
+ | ⋯ → **Download via email** | emails the PDF to the account (send-email endpoint; body not captured). |
178
+ | ⋯ → **Get shareable link** | the **web resume**: *Enable sharing* = `publish_web_resume {publish}`; link is `https://flowcv.com/resume/{webToken}`; *Display download button* gates the public `public/download_resume?token=` PDF (off → 400). |
179
+
180
+ ## AI Tools (per resume, Pro plan) — `/resume/ai-tools`
181
+
182
+ Gated behind the **Pro** subscription ("AI features are available on our Pro
183
+ plan"). Two tools observed (both Beta): **Translate resume** (create a translated
184
+ copy in another language, layout intact) and **Check spelling & grammar** (scan +
185
+ fix suggestions). Endpoints not captured (Pro-gated on the test account).
186
+
187
+ ## Other FlowCV products (same account & session, separate APIs)
188
+
189
+ FlowCV is more than resumes. The same `flowcvsidapp` session authenticates these
190
+ sibling products — each with its own `…/all` list endpoint, all fetched on editor
191
+ load. This tool currently covers **resumes only**; these are documented for
192
+ discovery, not yet implemented:
193
+
194
+ | Product | List endpoint | UI |
195
+ |---|---|---|
196
+ | **Cover Letters** | `GET /api/letters/all` | `/cover-letters` |
197
+ | **Job Tracker** | `GET /api/trackers/all` | `/job-tracker` |
198
+ | **Email Signatures** | `GET /api/signatures/all` | email-signature generator |
199
+ | **Personal Websites** | `GET /api/websites/all` | personal-site builder |
200
+
201
+ Account/billing: `GET /api/users/fetch_subscription_infos` (plan + entitlements;
202
+ free accounts get one resume, premium templates and AI gated),
203
+ `GET /api/users/invoices/pending_review`. The user object from `auth/login` also
204
+ carries `paid`, `activePlans`, AB-test flags, and `numberOfLogins`.
205
+
206
+ > Free vs paid recap: first resume is free forever; additional resumes, premium
207
+ > templates (`isPremium`), AI Tools, and likely the public-download button are
208
+ > Pro features. Show users the free/paid split before they hit a 400 or upsell.