Fountain Jobs API.
Tap high-volume hourly and frontline hiring at the source: pull every active opening, its location, and full HTML description from any Fountain apply directory through one unified endpoint.
Try the API.
Test Jobs and Feed endpoints against https://connect.jobo.world with live request/response examples, then copy ready-to-use curl commands.
What's in every response.
Data fields, real-world applications, and the companies already running on Fountain.
- Full HTML job descriptions
- Job location & country code
- Company & brand names
- Native funnel, account & brand UUIDs
- Locale & position name
- Public apply URLs
- 01Hourly & Frontline Job Aggregation
- 02High-Volume Hiring Market Data
- 03Real-Time Openings Monitoring
- 04Location-Based Job Feeds
How to scrape Fountain.
Step-by-step guide to extracting jobs from Fountain-powered career pages—endpoints, authentication, and working code.
import requests
from bs4 import BeautifulSoup
# Tenants live on region-specific hosts: web, ap-1, eu-1, us-1.
HOST = "web.fountain.com"
tenant = "foodja"
origin = f"https://{HOST}"
directory_url = f"{origin}/{tenant}/apply"
resp = requests.get(directory_url, headers={"Accept": "text/html"}, timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")import re
# Fountain-rendered directories end their <title> with "(Fountain)".
title = (soup.title.string or "").strip()
if not title.endswith("(Fountain)"):
raise RuntimeError("Not a Fountain-rendered apply directory")
# Advertised total, e.g. "5 Openings".
count_match = re.search(r"(\d[\d,]*)\s+Openings?", soup.get_text(), re.IGNORECASE)
advertised_total = int(count_match.group(1).replace(",", "")) if count_match else 0
# One anchor per active opening, each linking to /{tenant}/apply/{slug}.
openings = []
for a in soup.select("ul.positions-published a.app-position-links[href]"):
slug = a["href"].rstrip("/").split("/")[-1]
label = a.get_text(strip=True)
if slug and label:
openings.append({"slug": slug, "url": f"{origin}/{tenant}/apply/{slug}"})
# Fail closed if the board disagrees with its own advertised total.
if advertised_total and len(openings) != advertised_total:
raise RuntimeError(f"Advertised {advertised_total} openings but linked {len(openings)}")import time
jobs = []
for op in openings:
api_url = f"{origin}/internal_api/portal/{tenant}/application_forms/new"
r = requests.get(
api_url,
params={"funnel_id": op["slug"]},
headers={"Accept": "application/json", "Referer": op["url"]},
timeout=30,
)
r.raise_for_status()
data = r.json() or {}
funnel = data.get("funnel") or {}
# Skip funnels the directory linked but the API reports inactive.
if funnel.get("active") is not True:
continue
account = data.get("account") or {}
brand = data.get("brand") or {}
location = funnel.get("location") or {}
jobs.append({
"id": funnel["external_id"], # immutable Fountain job UUID
"title": funnel["title"],
"description_html": funnel["position_description_html"],
"location": location.get("name"),
"country_code": funnel.get("country_code"),
"locale": funnel.get("locale"),
"company": account.get("name") or brand.get("name"),
"apply_url": op["url"],
})
time.sleep(0.1) # crawl politely: one opening request at a time# The route slug is mutable; dedupe on the immutable funnel UUID instead.
unique = {job["id"]: job for job in jobs}
print(f"{len(unique)} unique openings for {tenant}")The route slug is mutable and can be renamed, causing churn and duplicates. Hydrate the application-form API and key each job on funnel.external_id, the immutable UUID.
Treat the mismatch as an incomplete snapshot rather than the real inventory. Compare the advertised count to the app-position-links you parsed and fail closed instead of publishing a partial board.
Skip any opening where funnel.active is not true or position_description_html is blank, and mark the snapshot incomplete — never let a missing detail delete an otherwise-valid job.
Fountain serves tenants across web-, ap-1-, eu-1-, and us-1.fountain.com. Preserve the exact host from the apply URL you discovered rather than defaulting every tenant to web.fountain.com.
Fountain publishes its complete public directory in a single HTML response and does not accept pagination cursors. Read the whole board in one request and iterate the parsed opening links.
- 1Key and dedupe jobs on funnel.external_id (the native UUID), never the mutable route slug.
- 2Preserve the exact regional host (web / ap-1 / eu-1 / us-1) from the source apply URL.
- 3Compare the advertised "N Openings" total against linked openings and fail closed on mismatch.
- 4Hydrate every directory link before trusting a snapshot; a shrinking board must not authorize deletions.
- 5Send Accept: application/json and a Referer of the opening URL when calling the internal application-form API.
- 6Throttle to one application-form request at a time with a short (~100 ms) delay between openings.
One endpoint. All Fountain jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=fountain" \
-H "X-Api-Key: YOUR_KEY" Access Fountain
job data today.
One API call. Structured data. No scraping infrastructure to build or maintain — start with the free tier and scale as you grow.