Want all of these in ONE API call?
Try the Live Aggregated Job API
One endpoint, 5+ sources, no auth. Updated hourly.

Free Remote Job Board APIs

JSON & RSS endpoints you can hit without registration or API keys. Tested September 2026.

๐Ÿ”ญ Want a ready-made tool?

GigWatch watches all these sources automatically, dedupes results, and AI-ranks them against your skill profile. Self-hosted, free, no account.

GitHub โ†’ ยท Live Demo โ†’

JSON APIs (no auth)

BoardEndpointNotes
Remotiveremotive.com/api/remote-jobs?limit=50Title, company, tags, salary, location, contract type
RemoteOKremoteok.com/apiCompany, category, tags, salary range
Jobicyjobicy.com/api/v2/remote-jobs?count=50Aggregates 50+ boards; industry, level, geo
Hacker Newshn.algolia.com/api/v1/search?query=hiring&tags=storyAlgolia-backed; filter by date, points

RSS / Atom Feeds

BoardFeed URLNotes
We Work Remotelyweworkremotely.com/remote-jobs.rssLargest remote-only board; dev, design, marketing, sales
Working Nomadsworkingnomads.com/feedCurated across categories
Jobspressojobspresso.co/rss.xmlStartup-focused

Niche / Key-Required (free tier)

SourceEndpointNotes
Adzunaapi.adzuna.com/v1/api/jobs/search/ee?app_id=KEY250 req/day free; register at adzuna.com
Jooblejooble.org/api/search?keyword=remote&api_key=***Aggregates many boards; free key

SPA / No Clean API

BoardWhy it's hard
JustRemoteNext.js SPA; /api/jobs returns HTML shell
Arc.devCloudflare-protected; blocks non-browser UAs
Toptal / TuringAuth-walled; enterprise-focused

Python Quick Start

import requests, time
from dataclasses import dataclass, field

@dataclass
class Job:
    title: str
    company: str | None
    url: str
    tags: list[str] = field(default_factory=list)
    salary: str | None = None
    source: str = ""

def fetch_all(limit=50):
    jobs = []

    r = requests.get("https://remotive.com/api/remote-jobs",
                     params={"limit": limit}, timeout=15)
    for j in r.json().get("jobs", []):
        jobs.append(Job(j["title"], j.get("company_name"),
                       j.get("url",""), j.get("tags",[]),
                       j.get("salary"), "remotive"))

    r = requests.get("https://jobicy.com/api/v2/remote-jobs",
                     params={"count": limit}, timeout=15)
    for j in r.json():
        jobs.append(Job(j.get("title",""), j.get("company"),
                       j.get("link",""),
                       [j["industry"]] if j.get("industry") else [],
                       j.get("salary"), "jobicy"))

    r = requests.get("https://hn.algolia.com/api/v1/search",
                     params={"query":"hiring","tags":"story",
                             "numericFilters":f"created_at_i>{int(time.time())-7*86400}",
                             "hitsPerPage":limit}, timeout=15)
    for h in r.json().get("hits",[]):
        jobs.append(Job(h.get("title",""), None,
                       h.get("url") or f"https://news.ycombinator.com/item?id={h['objectID']}",
                       ["hn"], None, "hn"))
    return jobs

for job in fetch_all(10):
    print(f"[{job.source:10}] {job.title} @ {job.company}")

Tips

๐Ÿ’ก Cache aggressively. These are public marketing endpoints, not SLA-backed APIs. Cache 15-60 min minimum.
๐Ÿ’ก Set a User-Agent. Some boards block the default python-requests UA.
๐Ÿ’ก Dedupe. Same job appears on multiple aggregators. Match on normalized title + company.
๐Ÿ’ก Salary is sparse. Most boards don't publish it. Don't build features that depend on it.