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:

ChangeMy 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

  • pandoc can’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.