No Fluff Jobs changed its T&C. I rebuilt the changelog
No Fluff Jobs asked me to accept new Terms and Conditions. New version is dated 15.10.2026. There’s no list of changes. Only “please accept”.
I don’t accept things I can’t compare. So I rebuilt the changelog myself.
The setup
- The T&C is one PDF, 87 pages. Polish and English in one file.
- The Polish version prevails (§22.8). So both languages must be checked, not only the English one.
- Goal: convert to Markdown, find the previous version, diff both, read it as a candidate who cares about privacy.
Step 1: PDF to Markdown
First idea was pandoc. It doesn’t work. pandoc can’t read PDF. PDF is output-only for it.
pdftotext gives plain text, but no structure. PyMuPDF was already installed, so I used it. The font sizes tell you what is a heading:
(10.0, False) 204054 # body text
(10.0, True) 2737 # bold = section headings (§ 1., § 2., ...)
(8.0, False) 15825 # page footer with company data
(32.0, True) 53 # document title
So the rules are simple:
- size >= 30 →
# - bold and short →
## - size 8 → footer, drop it
- digits only → page number, drop it
import fitz, re, sys
doc = fitz.open(sys.argv[1])
out = []
for p in doc:
for b in p.get_text('dict')['blocks']:
spans = [s for l in b.get('lines', []) for s in l['spans'] if s['text'].strip()]
if not spans: continue
text = ' '.join(''.join(s['text'] for s in l['spans']).strip() for l in b['lines'])
text = re.sub(r'\s+', ' ', text.replace('', '')).strip()
if re.fullmatch(r'\d+', text): continue # page number
size = max(s['size'] for s in spans)
bold = all('Bold' in s['font'] or s['flags'] & 16 for s in spans)
if size >= 30: text = '# ' + text
elif bold and len(text) < 200: text = '## ' + text
elif size <= 8.5: continue # footer
out.append(text)
open(sys.argv[2], 'w').write('\n\n'.join(out) + '\n')
One catch. The PDF was exported from Google Docs, and every printed line is a separate block. A second pass joins blocks into paragraphs: if a block doesn’t start with a list marker (1., a., i)), append it to the previous one. 6229 lines went down to 1819. Good enough to read.
Step 2: find the old version
The PDF lives at a fixed URL: https://nofluffjobs.com/static/No_Fluff_Jobs_Terms_and_Conditions.pdf. The Wayback Machine CDX API lists every capture. collapse=digest drops duplicates:
curl -s "https://web.archive.org/cdx/search/cdx?url=nofluffjobs.com/static/*&output=txt&fl=timestamp,original,mimetype,digest&collapse=digest&limit=200" \
| grep -i terms
About 20 captures between December 2025 and September 2026. Download the raw file with the id_ suffix, so you get the original bytes and not the Wayback HTML wrapper:
curl -sL -o 20260916112500.pdf \
"https://web.archive.org/web/20260916112500id_/https://nofluffjobs.com/static/No_Fluff_Jobs_Terms_and_Conditions.pdf"
Gotcha: pdftotext returned nothing for all of them. file said gzip compressed data. Wayback stored them gzipped. gunzip first, then it works.
Then read the version line from each:
20240317192159 Version of 17.05.2023
20251217164659 Version of 15.09.2025
...
20260916112500 Version of 15.09.2025
So the previous version is 15.09.2025. It was live until at least 16.09.2026. There may have been something in between, but I see no sign of it.
Step 3: diff
A paragraph diff was useless. The two PDFs have a different block layout, so almost every paragraph showed as changed. Also the old file had English first and Polish second. The new one is the other way around. Split by language before diffing.
A word-level diff on normalized text works:
import difflib
a = open('old_en.txt').read().split()
b = open('new_en.txt').read().split()
sm = difflib.SequenceMatcher(None, a, b, autojunk=False)
for op, i1, i2, j1, j2 in sm.get_opcodes():
if op == 'equal': continue
print(f'[{op}] ...{" ".join(a[max(0, i1-15):i1])}')
print(' -', ' '.join(a[i1:i2]))
print(' +', ' '.join(b[j1:j2]))
Result: 77 hunks in English, 85 in Polish. The second half of the Polish diff is whitespace only (non-breaking spaces). The real changes are the same in both languages.
What changed (15.09.2025 → 15.10.2026)
From a candidate’s point of view:
| Change | My reading |
|---|---|
| Application retention: before, max 6 months, backup 365 days from submission. Now the employer sets 1–24 months. Default 6 months, or 12 with “future recruitments” consent. Backups 365 days from the date the backup was made. | Worse. Worst case goes from about 12 months to about 36 months. |
| Advertisement Matching definition now includes your activity: subscriptions, applications, viewed ads. | Behavioural profiling is now in the definition. |
| New file attached while logged in is also saved in your Account. Deleting it there doesn’t delete the employer’s copy. | Files pile up by default. No opt-out mentioned. |
| Account deletion by email: 24 hours → 7 days. Self-delete in settings added. | Use the settings button. |
| AI interview recordings: “voice and image” → “voice”. Still used for “improvement of AI Services”. | Smaller scope. Your voice can still be used to improve their AI. |
| Public profile: only fields marked visible, at the profile URL, also for logged-out visitors. | Clearer. Old text said all Account info is public. |
| Apple “Hide My Email” login. Separate e-mail for applications. Twitter login removed. | Better. |
| Employer can require extra documents. Not a photo, not sensitive data. | Mixed. A required document blocks the application. |
| ”Mystery shopper” ads that make candidates open accounts in their own name are banned. Salary can’t change after 48 hours, except up. | Better. |
Problems in the whole document, not only the changes
- Real identity required (§2.6). No nicknames.
- Social login (§12.A.2). You give a “proxy” for as long as the Account exists to download and update your data from the social network.
- Profile Matching (§12.C). NFJ decides which employers get your profile. It doesn’t say if the profile is anonymised, how long the employer keeps it, or how to withdraw.
- AI on the employer side (§4.E, §7.23). CV Scoring and an AI Screener. The only GDPR Art. 22 protection is the employer’s promise to NFJ. Nothing you can enforce from that text.
- No AI Act in the text. Recruitment AI is high-risk under Annex III. No vendor names either.
- Personalised marketing comes with accepting the T&C (§20.5). Not a separate consent.
- Broken references. “Send an email to the address in §1.2 point d” (account deletion, §12.A.16). Point d is the definition of “Insights 360”. The deletion address is never given. Use the settings or
info@nofluffjobs.com(§22.2). - B2B contractors (§11.2). You may not count as a consumer. Then disputes go to the court in Gdynia (§22.9).
- Changes (§22.1). Allowed for broad reasons. In force 14 days after notice. No duty to show what changed. You can only accept or terminate within 14 days by e-mail.
What I do with it
- Before accepting:
- turn off Profile Matching
- set the profile to minimum visibility
- turn off marketing consents
- delete stored documents
- When applying:
- check the retention period on the form
- don’t tick “future recruitments”
- prefer ads that redirect to the employer’s own form
- Send a GDPR Art. 15 request. Ask for my data and for the list of employers who got my profile through Profile Matching.
- Don’t trust “please accept” without a diff. The Wayback Machine has it, if the document lives at a fixed URL.
Summary
pandoccan’t read PDF. PyMuPDF with font-size rules can.- Wayback CDX API +
id_URLs give you old versions. Check for gzip. - Diff words, not paragraphs, when the layout changed.
- Diff the language that prevails, not only the one you read.
- One gap left: the Privacy Policy is a separate PDF. The legal bases and AI vendors live there. Next one to diff.