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%
4.0 KB · 68 lines python
Raw Blame History
1"""SmartRecruiters postings — public endpoint behind `careers.smartrecruiters.com/<Company>`:2`GET https://api.smartrecruiters.com/v1/companies/{token}/postings?limit=100&offset=N` → {offset, limit, totalFound, content:[{id, name,3uuid, refNumber, releasedDate, location:{city, region, country(iso2 lower), remote, hybrid, fullLocation}, department:{label}, function:{label},4typeOfEmployment:{label}, experienceLevel:{label}}]}. Paginated by offset (bounded). Verified 2026-09-12 against `smartrecruiters`."""5from __future__ import annotations67import re8from collections.abc import Mapping9from typing import Any1011from companyatlas.connectors._ats_base import AtsConnector12from companyatlas.connectors._util import country_code, load_json, merged_result, parse_date, text_of13from companyatlas.fetch import Fetcher, FetchResult14from companyatlas.sdk.connector import ConnectorContext, ConnectorMeta, register15from companyatlas.sdk.models import ExtractedJob16from companyatlas.taxonomy import FetchMode, Surface1718PAGE_SIZE = 10019MAX_PAGES = 10202122@register23class SmartRecruitersConnector(AtsConnector):24    vendor = "smartrecruiters"25    token_re = re.compile(r"api\.smartrecruiters\.com/v1/companies/([a-z0-9_-]+)/postings", re.IGNORECASE)26    meta = ConnectorMeta(connector_id="smartrecruiters-v1", name="SmartRecruiters postings", version="1", category=Surface.JOBS_BOARD,27                         fetch_mode=FetchMode.JSON, default_interval_s=6 * 3600, url_pattern=r"api\.smartrecruiters\.com/v1/companies/",28                         priority=50, pattern_required=True, accept="application/json", description="Public SmartRecruiters posting API with offset pagination")2930    async def fetch(self, ctx: ConnectorContext, sensor: Mapping[str, Any], fetcher: Fetcher) -> FetchResult:31        base = str(sensor["url"]).split("&offset=")[0]32        first = await fetcher.get(base, etag=sensor.get("etag"), last_modified=sensor.get("last_modified"), accept="application/json")33        data = load_json(first)34        content = list(data.get("content") or []) if isinstance(data, dict) else []35        total = int(data.get("totalFound") or len(content)) if isinstance(data, dict) else len(content)36        pages = 137        offset = len(content)38        while offset < total and pages < MAX_PAGES and content:39            page = await fetcher.get(f"{base}&offset={offset}", accept="application/json")40            more = load_json(page).get("content") or []41            if not more:42                break43            content.extend(more)44            offset += len(more)45            pages += 146        if pages == 1:47            return first48        return merged_result(first, {"totalFound": total, "content": content}, pages=pages)4950    def parse_jobs(self, data: Any, sensor: Mapping[str, Any]) -> list[ExtractedJob]:51        token = self.token(sensor) or ""52        out: list[ExtractedJob] = []53        for j in (data.get("content") if isinstance(data, dict) else []) or []:54            if not isinstance(j, dict) or not j.get("name"):55                continue56            loc = j.get("location") or {}57            jid = str(j.get("id") or j.get("uuid") or "")58            url = f"https://jobs.smartrecruiters.com/{token}/{jid}" if token and jid else None59            remote = True if loc.get("remote") else (False if loc.get("hybrid") is not None or loc.get("city") else None)60            out.append(ExtractedJob(61                title=str(j["name"]).strip(), url=url, external_id=jid, department=text_of(j.get("department")),62                location_text=text_of(loc.get("fullLocation")) or ", ".join(x for x in (loc.get("city"), loc.get("region"), loc.get("country")) if x) or None,63                city=text_of(loc.get("city")), region=text_of(loc.get("region")), country=country_code(loc.get("country")), remote=remote,64                employment_type=text_of(j.get("typeOfEmployment")), seniority=None, posted_at=parse_date(j.get("releasedDate")),65                raw={"refNumber": j.get("refNumber"), "function": text_of(j.get("function")), "experienceLevel": text_of(j.get("experienceLevel"))},66            ))67        return out68