Kula Jobs API
How Kula's job boards actually work, why its board page is the wrong thing to scrape, and what ?source=kula returns in the HyperJobs API.
Kula is a sourcing and recruiting CRM suite that also hosts real public job
boards, mostly for engineering-heavy companies in India, the US and Southeast
Asia. Boards live at careers.kula.ai/<account>, and the underlying feed is one
of the cheapest in this pool: an unauthenticated JSON call returns up to 100
postings with every description already inline. No detail pass, no auth, no
cookies.
What the board looks like#
The board page at careers.kula.ai/<account> is a Next.js app-router app, and
it renders into an RSC flight payload. That payload is the tempting thing to
scrape, and it is the wrong one. The right target is the plain JSON endpoint the
board's own filter dropdown calls:
curl "https://careers.kula.ai/api/internal/ats_job_posts\
?accountName=<account>&type=ats_job_post.index&items=100&page=1"type is mandatory — without it the API returns a 400 with
err_invalid_type. items is clamped to 100 server-side (ask for 1,000 and
you get 100, honestly echoed back in the response's meta), and meta.pages
tells you when to stop.
Some Kula boards render client-side, so the flight payload is empty
This is the failure that looks like success. sarvam-ai's board page ships a
layout-only payload with zero postings in it — while the JSON endpoint
returns its 34 jobs, all with descriptions. A scraper built on the flight
payload reads that board as empty at HTTP 200 and never errors.
The flight payload is worse in three quieter ways too: it is coupled to the
Next.js build id and re-escapes between releases; its descriptions are lazy
$60-style references into byte-length-delimited chunks that chain with no
newline between them, so a line-anchored parse finds exactly one of 48; and
that declared length is in bytes rather than characters, so character-slicing
resolves none of them. All of that work, to get less data than the documented
route hands over for free.
What bites you#
ats_department is an object, not a string. It's {id, name, depth}. This
is the exact shape that, on another platform in this pool, wrote an object into
a scalar column and zeroed a whole board while a truthiness probe cheerfully
reported the department field at 100% fill. Take .name.
office.name is a label, not a location. An office literally named
"Remote" has location: "Toronto, Ontario, Canada". Others read "Kula Chennai",
"New york", "San Francisco ,USA". The location field beside it is
Google-Places-normalised and was filled on 149 of 149 office rows, so name is
only ever a last resort for a board that leaves the structured parts blank.
The job-level workplace field contradicts the office-level one, and the job
is right. workplace is a clean three-way office | remote | hybrid. An
office named "Kula Chennai" carries workplace: "remote" alongside
remote: false. Read the job's field, not the office's boolean.
There is no posting date. At all. Not on the list, not on the job page, no
JSON-LD anywhere. The only created_at in the payload belongs to the board's
font theme. Any date you see on a Kula posting was inferred by whoever
indexed it, not published by Kula.
A churned customer returns 403, not 404. The body says
err_account_terminated. That is a permanently dead board rather than a block —
Kula fronts no WAF — so treating the 403 as a bot wall means retrying a
cancelled account forever.
Salary has a real stated period, which is rare and worth using. interval
is an enum running from hourly to yearly, plus quarterly and one_time.
The amounts arrive as decimal strings. The last two are the interesting ones: a
quarterly figure annualised naively lands 4x too low, and a one-off bonus isn't
a salary at all, so both keep their text and no structured number.
Where this stands#
Query it with ?source=kula. Measured across live tenants: 238 jobs and 238
descriptions, averaging around 6,000 characters each. On the boards measured end
to end, descriptions, country and department all come back at 100%, and Kula is
one of the stronger platforms for the company join.
That is a measurement rather than a ceiling, and it moves as boards are added —
read the live numbers off /v1/meta → sources,
which is the only vocabulary worth coding against.
See all platforms · coverage
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key