TheirStack Jobs - open-req hiring signals (04)

SkillWeb & browsing

Pulls open job postings from the TheirStack API and turns them into structured hiring signals - open roles per domain, titles, seniority, posting dates, hiring team, plus free firmographics (headcount, funding stage, industry). Step 04 (signals) of the API-first GTM chain, and the discovery entry for hiring-defined ICPs. Use when the user wants hiring signals, open reqs, job postings, "who is hiring", "which of these companies are hiring X", "find companies hiring Y right now", open-role checks on a domain list or records.jsonl, or anything TheirStack. Sizing counts bound every pull before spend - free without company filters, ~1 credit against a domain list - then 1 credit per job returned. For people who already joined - recent hires, headcount growth, funding rounds - use 04-crustdata-signals; for scoring and campaign angles, hand the records to 05-signal-builder.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the TheirStack Jobs - open-req hiring signals (04) skill

What this skill tells your AI

The instructions your AI receives, as published by zevenue/headless-gtm in skills/04-theirstack-jobs/SKILL.md and read by ahel’s review.

Open job postings are the "what they're about to do" signal: a company hiring its first SDRs is building outbound, a VP Sales req is a strategy shift, three engineering reqs after a raise is product investment. This skill pulls those postings per company and emits chain records ready for judgment. Its sibling 04-crustdata-signals covers what already happened (funding, joins, headcount); this one covers what's open right now.

Two modes

ModeInputUse when
checkDomains (--domains or an upstream --records records.jsonl)You have a list and want hiring evidence per company - the standard step-04 position in the chain
discoverFilters only (title/seniority/tech + geo/size/funding)The ICP is the req itself: "companies hiring SDRs in the US right now". This is the direct entry for hiring-defined ICPs - no firmographic superset needed first

Prerequisites

  • THEIRSTACK_API_KEY in env or .env - get one at app.theirstack.com/settings/api
  • No Python packages required (stdlib only; python-dotenv is optional - the script falls back to reading .env itself)

Credit rules

Credits are money. The script's whole flow is built around four facts verified live against the balance ledger (2026-07-24):

OperationCost
Count without company filters (blur_company_data + include_total_results + limit 1)0 credits - exact totals of matching jobs AND companies, free. Discover mode sizing is genuinely free
Count WITH company_domain_orBlur silently disables on company-identifier filters (HTTP 200, unblurred, billed per returned job). At limit 1 that is ~1 credit per count - and 0 when the domain has no matches. There is no zero-credit domain-scoped count
Job fetch1 credit per job returned, exactly
Re-fetching a job you already pulledCharged again - TheirStack has no charge dedup

Because re-pulls cost full price, every fetched domain is cached as domains/<domain>.json keyed by a query hash. Re-running the same query reuses the cache for 0 credits (any prior run folder counts, not just the current one). --no-cache forces a refetch.

Every paid pull is preceded by a sizing count, and anything estimated over the gate (default 500 credits, --credit-gate to change) stops for confirmation. --count-only answers "how many / what would it cost" for 0 credits in discover mode and ~1 credit against a domain list. When the user says "spend nothing", say the ~1-credit floor out loud instead of silently eating it. Ledger postings can lag by minutes - read actual spend from meta.json's balance_before/balance_after delta after it settles, not from assumptions.

Process

All paths relative to this skill's folder. The script prints the free count and estimate before any spend - no separate estimation step needed.

# Size it first when the user only wants numbers (0 credits)
python3 scripts/theirstack_jobs.py count \
  --domains stripe.com,notion.so --title "SDR,BDR" --days 30

# Check a domain list (per-domain pulls, cached, resumable)
python3 scripts/theirstack_jobs.py check \
  --domains stripe.com,notion.so --title "SDR,BDR,Sales Development" \
  --days 30 --per-domain 10

# Chain position: read domains from an upstream records.jsonl -
# every upstream field carries through into the output records
python3 scripts/theirstack_jobs.py check \
  --records ./runs/<run-id>/records.jsonl \
  --title "Head of Sales,VP Sales,CRO" --days 30

# Discover companies currently hiring for a role
python3 scripts/theirstack_jobs.py discover \
  --title "Revenue Operations,RevOps" --country US \
  --min-employees 50 --max-employees 500 --days 14 --max-jobs 200

# Resume an interrupted run (no double spend - cache + tracker)
python3 scripts/theirstack_jobs.py check --resume --run-dir runs/<run-id>

# Balance
python3 scripts/theirstack_jobs.py credits

Spend levers, in order of effect: --title (narrows what counts as a match), --per-domain (check-mode cap, default 10), --max-jobs (discover-mode ceiling, default 200), --days (window, default 30). A company with 50 open roles costs 50 credits uncapped - the caps exist so it costs 10.

Building title filters

--title is keyword matching on the title (comma-separated, OR). Compose from what the client's offer makes relevant:

Signal targetTitle keywords
Outbound buildoutSDR, BDR, Sales Development, Outbound
Sales leadership shiftHead of Sales, VP Sales, CRO, Sales Director
Marketing motionMarketing, Growth, Demand Gen, Product Marketing
RevOps maturityRevenue Operations, RevOps, Sales Operations, GTM Operations
Product investmentEngineer, Founding Engineer, CTO, Head of Engineering

Interpretation stays downstream: multiple same-department reqs = team buildout, a single senior req = strategy change, one junior backfill = weak signal. 05-signal-builder scores this; this skill only gathers the facts.

Output contract

Each run writes runs/<run-id>/ per headless-gtm-shared/CONVENTIONS.md:

runs/<run-id>/
├── records.jsonl     # one line per domain - what the chain consumes
├── records.csv       # human export (derived)
├── domains/*.json    # per-domain cache: curated jobs + query hash
├── tracker.json      # resume state
├── filters.json      # the exact API filters used
└── meta.json         # counts, credits estimate, balance before/after

Records are additive: upstream fields pass through untouched, domain is normalized (lowercase, no www., no scheme), filters_matched unions. Fields this skill adds: open_roles_count, roles_window_days, roles_title_filter, jobs[] (title, url, date_posted, location, remote, seniority, salary, hiring_team, description_snippet), plus free firmographics when present: employee_count, funding_stage, total_funding_usd, industry, company_hq. Real example (trimmed):

{"company": "Stripe", "domain": "stripe.com", "person": null, "open_roles_count": 2, "roles_window_days": 30, "jobs": [{"title": "Sales Development Representative", "url": "https://stripe.com/jobs/search?gh_jid=8083685", "date_posted": "2026-07-24", "location": "Dublin", "seniority": "mid_level"}], "employee_count": 16807, "funding_stage": "secondary_market", "filters_matched": ["2 open 'SDR,BDR' role(s) in last 30d (latest 2026-07-24)", "hiring: Sales Development Representative; Head of Sales Development, AMER"]}

Domains checked with zero matches still get a record (open_roles_count: 0, a "no open roles" line in filters_matched) - downstream needs to distinguish checked-and-quiet from never-checked.

Key behaviors

  • The API requires at least one of: a posting-age filter, a company domain filter, or a company name filter. The script always sends posted_at_max_age_days, so any filter combination on top is valid.
  • Domain matching is exact; name matching (--names is deliberately not exposed - pass domains) is fuzzy and pulls false positives.
  • Discover mode refuses to run without at least --title, --seniority, or --tech - an unfiltered pull of every recent posting is never worth credits.
  • Observed enum values: seniority = junior / mid_level / senior / c_level; funding_stage includes seed, series_a..., and secondary_market for late-stage. Treat as observed, not exhaustive.
  • hiring_team (name, title, LinkedIn) is present on some postings - when it is, that person is a resolution shortcut for 06.
  • The balance endpoint (/v0/billing/credit-balance) is undocumented but live; if it ever disappears the script degrades to a dashboard pointer.
  • Results order newest-first; discover mode's --max-jobs ceiling therefore keeps the freshest postings.

Chain handoff

  • 05-signal-builder: pass the run's records file via --records runs/<run-id>/records.jsonl. Open-req evidence usually lands as a WHEN/timing signal; 05 owns the 1-10 score and approach call.
  • 04-crustdata-signals: run both when the play needs open reqs AND recent joins/funding - same records.jsonl in, additive fields out, 05 merges by domain.
  • 06-resolution-email-person: hiring_team entries and senior req titles ("Head of Sales") tell 06 who to resolve.
  • email-writer: filters_matched lines are situation-line raw material ("saw you're hiring your first SDRs...").

Scope boundaries

Not this skillUse instead
Who already joined, headcount growth, funding rounds04-crustdata-signals
Firmographic TAM building (industry + size + geo)01-prospeo-discover
Scoring signals / picking the campaign approach05-signal-builder
Tech-stack lookups without job postingsNot built yet - TheirStack's technographics endpoint (3 credits/company) is a natural sibling if needed

References

  • references/jobs-api.md - full filter list, response fields, free-count recipe, curl examples, verified credit mechanics
  • TheirStack API docs - the vendor source of truth

Signals

GitHub stars
28
Forks
6
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
x-04-theirstack-jobs
Source
github.com/zevenue/headless-gtm