CrawlForge MCP
Intermediate Guide

Job Board APIs

Six applicant tracking systems publish a company's open roles through an API they document for public, unauthenticated use. scrape_template reads those APIs directly — exact values, no LLM in the path, and permitted by construction rather than by argument.

Why the platform's own API wins
The six connectors
One job shape
What we do not do, and why

1. Why the platform's own API beats scraping the board

A company's careers page is a rendering of a database. The applicant tracking system behind it — Greenhouse, Lever, Ashby, Workable, Recruitee, Teamtailor — is the system of record the employer actually publishes to, and every one of those six documents a public endpoint that serves the same records as JSON or RSS.

Reading that endpoint is not a cheaper way to scrape. It is a different operation with different failure modes: there is no markup to misparse, no pagination widget to click through, and no language model deciding what a field meant.

The same board, two ways

Scraping the rendered boardReading the platform's API
ValuesParsed out of markup; a redesign changes them silentlyExact, as the employer entered them
CoverageWhatever the page renders on first paintEvery published posting on the board
RequestsOne per page, plus pagingOne request for the board, paged only where the platform pages
InterpretationSelectors or an LLM decide what a field meantNothing infers; an absent field reads as null
PermissionArgued case by caseThe platform documents the endpoint for public use
Prefer a documented API over scraping. This is one of CrawlForge's standing operating rules, not a performance tip. Where a source publishes its own API, that is the path we take — see the crawler operating rules.

2. The six job-board connectors

Each connector is pointed at an endpoint the platform itself documents for public, unauthenticated use, on a host whose robots.txt permits it. Pass a board URL and the connector resolves the API endpoint for you, or pass params with the company's board identifier.

greenhouse-jobs
The Job Board API. Every published job with title, location, ids and timestamps in one request. Descriptions are opt-in via content: true.

Board token from job-boards.greenhouse.io/<token>

lever-postings
The Postings API, with team, commitment, workplace type and a plain-text description already separated out. Supports skip and limit paging.

Company from jobs.lever.co/<company>

ashby-jobs
The Public Job Posting API. Department, team, employment type and workplace type for every listed job. Descriptions are opt-in via descriptions: true.

Jobs page name from jobs.ashbyhq.com/<name>

workable-jobs
The public accounts endpoint. Title, department, employment type, location parts and a telecommuting flag. Descriptions are opt-in via details: true.

Account subdomain from apply.workable.com/<subdomain>

recruitee-offers
The Careers Site API. Title, department, location, employment type code, salary band and a plain-text description for every open role.

Subdomain from <company>.recruitee.com

teamtailor-jobs
The careers site's documented RSS feed, read with its tt: namespace intact so location, department, role and division survive. Returns 100 jobs unless per_page says otherwise.

Subdomain from <company>.teamtailor.com

Descriptions are large, so three platforms make them opt-in. Greenhouse ships summary records by default; content: true adds the full HTML description and takes a large board from 349 KB to 4.2 MB (Stripe's 571-job board, measured 2026-08-28). Workable's details: true behaves the same way, and so does Ashby's descriptions: true (OpenAI's 767-job board is 5.9 MB with them, measured 2026-09-01). Ask for descriptions when you need them, not by habit.
Rate limits travel with the connector. api.lever.co declares Crawl-delay: 1 in its robots.txt, so lever-postings republishes that as crawlDelaySeconds: 1 for the fetching surface to honour. The connectors never fetch anything themselves — they build the URL and parse the response, which keeps timeouts, SSRF policy and rate limiting with the surface that owns them.

3. One job shape across six platforms

All six connectors normalise onto the same twelve fields, so two boards union without a per-source mapping step. A field the platform does not carry comes back null — never a plausible-looking guess.

The shared job shape

FieldMeaning
idThe platform's own identifier, stringified so a merged list has one id type
titleJob title
urlThe public posting
locationLocation as the platform states it
departmentDepartment, where the platform has that level
teamTeam, where the platform has that level
employment_typePassed through in the platform's own words, not collapsed onto one vocabulary
remotetrue, false, or null when the source's word does not answer it
published_atISO 8601, normalised from six different date formats
updated_atISO 8601, where the platform publishes one
descriptionPlain text, with the platform's HTML stripped
sourceWhich connector produced the record
  • Nothing is inferred. Greenhouse publishes no employment type at all, so a Greenhouse job reports null there rather than a confident Full-time.
  • `remote` answers one question: can this job be done from anywhere? Hybrid is genuinely part-remote, so it reads as null and the platform's own word is kept alongside it. Reading a hybrid role as remote would be as wrong as reading it as on-site.
  • `employment_type` keeps the platform's spelling. Deciding what Lever's Regular Full Time (Salary) really is belongs to you, who can see your own data.
  • Recruiter contacts are dropped, not mapped. Recruitee stamps a per-job application mailbox on every offer; that field never reaches the output. A job posting is company data, and that is all these connectors return.

Two boards, one list

Because the shape is shared, merging a Greenhouse board and a Lever board takes no mapping code.

Typescript
// Every job-board connector returns the same twelve fields, so two boards
// concatenate with no per-source mapping step.
const API = 'https://crawlforge.dev/api/v1/tools/scrape_template';

async function board(template: string, params: Record<string, unknown>) {
  const response = await fetch(API, {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.CRAWLFORGE_API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ template, params }),
  });

  const payload = await response.json();
  return payload.data.data.items;
}

const jobs = [
  ...(await board('greenhouse-jobs', { company: 'stripe' })),
  ...(await board('lever-postings', { company: 'matterport' })),
];

// One filter over both boards. `remote` is null wherever the platform's own
// word did not answer the question — hybrid is not remote and not on-site.
const remote = jobs.filter((j) => j.remote === true);

// Cost: 1 credit per call — 2 credits for both boards, however many jobs.

4. Calling it: params and auto

A job-board connector needs the company's identifier on that platform, which is a short string in the board URL rather than the whole URL. Pass it in params.

When you already have a URL and would rather not work out which template handles it, send template: "auto" and CrawlForge picks the template from the URL — deterministically, with a host-anchored pattern outranking one that only matches a path shape.

Both call styles

The same board, reached two ways.

Bash
# A job-board connector needs the company's identifier on that platform,
# not the whole careers-page URL. That is what `params` carries.
curl -X POST https://crawlforge.dev/api/v1/tools/scrape_template \
  -H "X-API-Key: $CRAWLFORGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "greenhouse-jobs",
    "params": {
      "company": "stripe"
    }
  }'

# Add "content": true to the params for full descriptions — it takes a large
# board past 4 MB, so ask for it only when you need the text.
Discover what is available. Send { "template": "list" } with no URL to get every template with its id, description and mode — list for a connector that returns many records from one call, entity for one that returns a single record.

5. The same idea, outside hiring

Job boards are the clearest case, but the principle is the rule everywhere: where an operator publishes its own data, read that.

shopify-collection
Every product in a collection from the store's own products.json — the same exact price, compare-at price and stock that shopify-product returns for one item, so a collection and a product page cannot disagree.
nhtsa-vin
VIN decode through NHTSA's vPIC API. Free, keyless, and built from the manufacturers' own submissions, so nothing is inferred. Partial VINs are accepted and vPIC's own error codes are surfaced rather than swallowed.
npi-provider
The CMS NPPES registry of US health care providers, searched by number, name, taxonomy or location. Free and keyless. A registry lookup that returns one record per NPI and joins nothing to it.

6. What we do not do, and why

The list below is not a roadmap. Each entry is a route we considered, could technically take, and decided against for a stated reason.

Authenticated scraping of LinkedIn or Indeed
Signing in to reach data the logged-out pages do not show breaks both platforms' terms, and LinkedIn actively litigates over it. The connectors on this page are the answer to that request: the same job data, legitimately, and more completely — read from the system of record the employer publishes to, rather than from an aggregator's index of it.

Public, unauthenticated sources only

smartrecruiters-postings
SmartRecruiters documents its Posting API publicly and it needs no key — but api.smartrecruiters.com/robots.txt disallows everything for every agent, with a single carve-out for LinkedInBot (verified 2026-08-28). Reaching that endpoint as anyone else means overriding robots.txt on every call, which is not a decision a connector gets to make on your behalf. Left out pending an agreement with SmartRecruiters.

robots.txt respected — we did not override it

Workday tenants
Workday's wday/cxs endpoint is the careers site's own internal endpoint, not an API Workday documents for public use. Any connector against it is gated on customer attestation, and none ships today.

Not a documented public API

CAPTCHA solving and bot-defence bypass
No CAPTCHA solving, no challenge-token forging, no paywall circumvention, and no evading a block a site has aimed specifically at us. None of these connectors needs any of it, because none of them is fighting a defence in the first place.

Not offered at any tier

Profiles of individuals
These connectors return no personal data beyond what a source publishes publicly, and CrawlForge does not assemble individuals into profiles. npi-provider is a registry lookup for that reason: one record per NPI, with nothing joined to it.

Company data, not people data

The rules these connectors are built to

  • Public, unauthenticated pages and endpoints only.
  • A documented API is preferred over scraping wherever one exists.
  • robots.txt is respected by default on every fetching tool. An override is explicit, per request, and recorded against your API key — and it never reaches a host on CrawlForge's permanent opt-out list.
  • CrawlForge identifies itself honestly: one user agent, the real product name, and a contact URL. See crawler verification.
  • Polite rates by default, with Crawl-delay and Retry-After honoured.
  • Opt-outs and takedowns are honoured permanently, at the platform layer.
Read a whole job board in one call
scrape_template costs 1 credit per call, whichever connector you use and however many jobs come back.
scrape_template referenceCrawler operating rules

Footer

CrawlForge MCP

Enterprise web scraping for AI Agents. 30 specialized MCP tools designed for modern developers building intelligent systems.

Product

  • Features
  • Playground
  • Pricing
  • Use Cases
  • Integrations
  • Alternatives
  • Changelog

Resources

  • Getting Started
  • API Reference
  • Templates
  • Guides
  • Blog
  • Glossary
  • FAQ
  • Sitemap

Developers

  • MCP Protocol
  • Claude Desktop
  • Cursor IDE
  • LangChain
  • LlamaIndex

Company

  • About
  • Contact
  • Privacy
  • Terms
  • Acceptable Use
  • Security
  • Cookies

Stay updated

Get the latest updates on new tools and features.

Built with Next.js and MCP protocol

© 2025-2026 CrawlForge. All rights reserved.