SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
3.0 KB · 48 lines python
Raw Blame History
1"""Teamtailor career sites publish a JSON Feed at `/jobs.json` on the career-site host (`<token>.teamtailor.com` or a custom2`career.<company>.com`): {version, title, home_page_url, items:[{id, title, url, date_published, _jobposting:{identifier:{value}, datePosted,3jobLocation:[{address:{addressLocality, addressRegion, addressCountry}}], employmentType?, jobLocationType?}}]}.4Verified 2026-09-12 against https://career.teamtailor.com/jobs.json (fixture trimmed to 20 items, descriptions removed). When a career5site has no feed (404) discovery keeps the HTML careers sensor instead."""6from __future__ import annotations78import re9from collections.abc import Mapping10from typing import Any1112from companyatlas.connectors._ats_base import AtsConnector13from companyatlas.connectors._util import country_code, dig, parse_date, text_of14from companyatlas.sdk.connector import ConnectorMeta, register15from companyatlas.sdk.models import ExtractedJob16from companyatlas.taxonomy import FetchMode, Surface171819@register20class TeamtailorConnector(AtsConnector):21    vendor = "teamtailor"22    token_re = re.compile(r"https?://([a-z0-9-]+)\.teamtailor\.com/jobs\.json", re.IGNORECASE)23    meta = ConnectorMeta(connector_id="teamtailor-v1", name="Teamtailor jobs feed", version="1", category=Surface.JOBS_BOARD, fetch_mode=FetchMode.JSON,24                         default_interval_s=6 * 3600, url_pattern=r"/jobs\.json(\?|$)", pattern_required=True, priority=45, accept="application/feed+json,application/json",25                         description="Teamtailor career-site JSON Feed (/jobs.json)")2627    def parse_jobs(self, data: Any, sensor: Mapping[str, Any]) -> list[ExtractedJob]:28        out: list[ExtractedJob] = []29        for it in (data.get("items") if isinstance(data, dict) else []) or []:30            if not isinstance(it, dict) or not it.get("title"):31                continue32            jp = it.get("_jobposting") or {}33            locs = jp.get("jobLocation") or []34            if isinstance(locs, dict):35                locs = [locs]36            addr = dig(locs[0], "address", default={}) if locs and isinstance(locs[0], dict) else {}37            city, region, country = text_of(addr.get("addressLocality")), text_of(addr.get("addressRegion")), addr.get("addressCountry")38            ident = dig(jp, "identifier", "value")39            loc_type = (jp.get("jobLocationType") or "").lower()40            out.append(ExtractedJob(41                title=str(it["title"]), url=it.get("url"), external_id=str(ident or it.get("id") or ""),42                location_text=", ".join(x for x in (city, country_code(country) or text_of(country)) if x) or None, city=city,43                region=region if region and region.lower() not in ("europe", "emea", "apac", "americas") else None, country=country_code(country),44                remote=True if loc_type == "telecommute" else None, employment_type=text_of(jp.get("employmentType")),45                posted_at=parse_date(jp.get("datePosted") or it.get("date_published")), raw={"locations": len(locs)},46            ))47        return out48