Skip to main content

One typed, async Python interface to six public job boards: Remotive, Arbeitnow, Greenhouse, RemoteOK, We Work Remotely, and Hacker News Who is hiring

Project description

📡 jobfeeds

One typed, async Python interface to six public job boards.

pip install jobfeeds

PyPI Python Tests Typed License


Fetching job postings should not require writing six API clients. jobfeeds gives you Remotive, Arbeitnow, Greenhouse boards, RemoteOK, We Work Remotely, and the Hacker News "Who is hiring" thread through one interface, normalized into one pydantic model, with failures isolated per source.

Extracted from, and used in production by, JobRadar, a live job-market intelligence dashboard.

Quickstart

import asyncio
from jobfeeds import Remotive, Greenhouse, fetch_all

async def main():
    # One source
    jobs = await Remotive().fetch()               # list[Posting]
    print(jobs[0].company, jobs[0].title, jobs[0].url)

    # Configured source
    jobs = await Greenhouse(boards=["stripe", "anthropic"]).fetch()

    # Everything, concurrently, failure-isolated
    results = await fetch_all()                   # list[SourceResult]
    for r in results:
        print(r.source, len(r.postings) if r.ok else f"FAILED: {r.error}")

asyncio.run(main())

The model

Every source normalizes into the same Posting:

Field Notes
source adapter name ("remotive", "greenhouse", ...)
external_id the source's own id
company, title, url always present
location_raw as the source states it, unparsed
posted_at datetime or None, when the source provides one
raw source-specific payload; descriptions truncated to 8,000 chars
.hash cross-source dedupe key (normalized company + title + location)

Boards syndicate each other's postings; posting_hash() collapses the duplicates without trusting any board-specific id.

Sources and their quirks

Adapter Config Worth knowing
Remotive() Curated remote listings, JSON API
Arbeitnow(pages=3) page count DACH-market coverage; paginates explicitly because the API's own links.next can point at filtered views
Greenhouse(boards=[...], per_board_limit=40, title_pattern=None) board tokens, cap, optional title regex Direct-employer postings (Stripe, Anthropic, GitLab, ...). No global search endpoint exists, you choose boards. A default starter set is included
RemoteOK() Their terms require a visible link back to remoteok.com in what you build. The API's first element is a legal notice, already skipped
WeWorkRemotely(categories=[...]) category slugs RSS per category; defaults to programming, devops, design, product
HNWhoIsHiring() Finds the latest monthly thread via Algolia, parses only structured "Company | Role | Location" comments; freeform replies are ignored

All adapters accept timeout= and user_agent=. Public APIs change; every adapter parses defensively and skips malformed entries instead of raising.

Failure isolation

fetch_all() never lets one dead API take down the run:

results = await fetch_all([Remotive(), Greenhouse(boards=["stripe"])])
postings = [p for r in results if r.ok for p in r.postings]
errors = {r.source: r.error for r in results if not r.ok}

Your own adapter joins the party by satisfying the SourceAdapter protocol: anything with a name and an async fetch() -> list[Posting].

Design notes

  • httpx + pydantic only. No framework, no ORM, no surprises in your dependency tree.
  • Fully typed, ships py.typed.
  • No product logic. Filtering, enrichment, and storage belong to your application; this package only does feed access done well. (JobRadar's tech-role filter, LLM enrichment, and database live in its repo, on top of this package.)
  • Tests never touch the network. Every adapter is tested against mocked responses (respx) in CI on Python 3.11, 3.12, and 3.13.

License

MIT. Built by Nandor Nagy as part of a public portfolio; the ingestion layer of jobradar.nagysolution.com.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jobfeeds-0.1.0.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jobfeeds-0.1.0-py3-none-any.whl (16.4 kB view details)

Uploaded Python 3

File details

Details for the file jobfeeds-0.1.0.tar.gz.

File metadata

  • Download URL: jobfeeds-0.1.0.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jobfeeds-0.1.0.tar.gz
Algorithm Hash digest
SHA256 430e823815d679821e47a31defcc7025e2a8f4c4a42f6901ad7bec25c7d512ef
MD5 ac0f50cb502efff44f78ff7c1bdc231c
BLAKE2b-256 e2120637e2d38092a187e31915b432ff9fdf2abb5eaee642f1b3727f8db7f275

See more details on using hashes here.

Provenance

The following attestation bundles were made for jobfeeds-0.1.0.tar.gz:

Publisher: release.yml on n3ndor/jobfeeds

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jobfeeds-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: jobfeeds-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jobfeeds-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2389014ba988e9249c5a68a5450c1e5872b1ee02fcb4d510d790ba0d4ee915b0
MD5 e3f20cbf294d35c6387bbfcf7248927a
BLAKE2b-256 8285d2892dee64956dba9ada82b126e362f69cceb8244dea680cfdf99b9995e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for jobfeeds-0.1.0-py3-none-any.whl:

Publisher: release.yml on n3ndor/jobfeeds

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page