Why Salary Normalization Matters for Job Data
Salary data is one of the most valuable fields in a job listing, but it is also one of the least consistent. Some ATS platforms provide structured compensation ranges. Others embed salary in description text. Some list annual ranges, some list hourly rates, and many provide no number at all.
JobsDataAPI normalizes salary data into fields your application can use while preserving the original text for display.
The Problem
Raw salary text can appear in many forms:
$120,000 - $150,000$60/hr150k-180kCompetitive salary plus equityDOEAnnual base salary up to 200000€80,000 - €100,000
If every client has to parse these strings, salary filters become inconsistent and unreliable. A user searching for jobs over $100k should not depend on your frontend regex.
The JobsDataAPI Salary Shape
The API exposes salary through a small set of predictable fields:
salary_minsalary_maxsalary_currencysalary_text
Example:
{
"title": "Senior Backend Engineer",
"salary_min": 150000,
"salary_max": 180000,
"salary_currency": "USD",
"salary_text": "$150k-$180k"
}
salary_min and salary_max are numeric when the source provides enough structure. salary_currency is normalized to an ISO-style code when available. salary_text preserves the original employer language for display.
What the API Does Not Pretend to Know
Salary data is not always complete. Some postings do not disclose compensation, and some sources provide only text. In those cases, the API keeps numeric fields as null and preserves whatever text exists in salary_text.
For cross-currency or cross-period comparisons, applications should still treat salary carefully. The API gives you structured fields and source text; it does not invent a pay period or currency conversion where the employer did not provide one.
Filtering by Salary
The list endpoint supports salary filtering:
curl "$CLEANJOBDATA_API_BASE_URL/jobs?salary=120000,180000&title=engineer" \
-H "Authorization: Bearer $CLEANJOBDATA_API_KEY"
The backend interprets this as a range filter. It also supports legacy min_salary for simple lower-bound searches.
The filter is designed to be useful even when salary data is incomplete. A job can match a salary range when:
- its maximum salary is at least the requested minimum, or
- its maximum salary is missing but the job has not excluded salary filtering by being absent
That behavior helps avoid hiding potentially relevant jobs just because one endpoint of the range is missing.
Why It Matters for Job Boards
Normalized salary fields power common product features:
- "Jobs paying over $100k"
- salary range chips
- compensation badges on cards
- salary sorting
- market analytics
- location-based compensation comparisons
- remote vs on-site salary filters
Without structured fields, every feature requires custom parsing and every source behaves differently.
Why It Matters for AI and Analytics
Structured salary data is also useful for analytics and AI training pipelines:
- compensation benchmarks by role
- seniority-based salary distributions
- remote salary comparisons
- market coverage analysis
- source-quality scoring
Keeping salary_text alongside numeric fields gives you both machine-readable values and human-readable provenance.
Example UI Usage
function SalaryBadge({ job }: { job: Job }) {
if (!job.salary_min && !job.salary_max) return null;
const currency = job.salary_currency || "USD";
const min = job.salary_min
? Intl.NumberFormat("en-US", { style: "currency", currency }).format(job.salary_min)
: null;
const max = job.salary_max
? Intl.NumberFormat("en-US", { style: "currency", currency }).format(job.salary_max)
: null;
return <span>{[min, max].filter(Boolean).join(" - ")}</span>;
}
This keeps the UI simple while still respecting missing data.
The Takeaway
Salary normalization turns messy compensation text into useful fields. JobsDataAPI gives you numeric ranges, currency, and original text so you can build filters, displays, and analytics without writing source-specific parsers.