# Homerun Jobs API

> Query jobs from Homerun boards through one API with ?source=homerun — plus how Homerun's Atom feed really behaves.

Source: https://hyperjobs.io/ats/homerun

Homerun is a hiring tool for small European companies, Netherlands-heavy, sold on
the promise that a careers page can have some personality. Boards live at
`<tenant>.homerun.co` or behind the customer's own domain.

It is the one platform in this pool where the feed isn't a convenience, it's the
only door.

## The feed is the only route

Homerun boards render their jobs **client-side**, via a script from
`static.homerun.co`, and the served HTML carries **zero JSON-LD**. There is no
markup to parse, no structured block in the head, nothing a fetch-and-scrape pass
can use. HTML scraping is simply not viable here, which is unusual enough to be
worth knowing before anyone tries to "improve" on the feed.

What there is instead is one unauthenticated Atom feed per tenant, and it's good:

```bash
curl "https://feed.homerun.co/<tenant>"
```

Homerun markets a hashed feed URL (`jobs_atom_<sha256>.xml`) that reads like a
credential. The plain `feed.homerun.co/<tenant>` alias serves **identical
content with no key**, so the tenant slug is all you need. The slug stays the
Homerun slug even behind a custom domain, and every board links its own feed URL,
so it can be recovered from either.

One call returns the whole board with descriptions inline. On the tenant we
tested that was **4 jobs, 4 descriptions**. Four. That is not a typo and it isn't
a bad board — it's the shape of this platform. Homerun sells to small companies
that are hiring for one or two roles at a time, and a per-tenant feed of a
handful of jobs is exactly what you should expect. The fetch is trivial and it is
still four jobs. The work here is finding the tenants, not reading them.

## What bites you

<Warning title="`<summary>` and `<content>` are both a teaser">
  Atom's own two body elements are the wrong ones. Both carry a one-line teaser —
  *"Are you ready to enhance Dopper's brand visibility?"* — while the real posting
  lives in Homerun's custom **`<description>`** child.

  Measured on a live board: `summary` and `content` were **104 characters** each;
  `description` ran **4,553 to 7,365**.

  This is the dangerous kind of wrong. You read the field Atom says is the body,
  you get 200, you get plausible text, and you ship a corpus of teasers forever.
  Nothing errors. Use `<description>`.
</Warning>

**Homerun attributes its children.** Elements arrive as `<description type="html">`
rather than bare tags, and several fields are nested objects rather than
attributes: `<department><name>Marketing</name></department>`, and the same for
`location` and `type`. A tag matcher that assumes bare elements finds nothing.

**Salary is free text, in the local idiom.** `salary_indication` reads
`"€3.000 - €4.000 per maand"` — a Dutch thousands separator, a Dutch period. Kept
verbatim rather than guessed at by a parser expecting US formatting.

**Dead tenants redirect to `404.homerun.co`.** That's a genuinely clean liveness
signal, and rarer than it sounds: an empty result here means "no board", not
"scrape failed", so a churned tenant can be reaped instead of retried forever.

## Or use ours

The fetch is one unauthenticated call and it is not the work. The work is that
Atom's own two body elements are decoys, the children arrive attributed and
nested, the salary is in an idiom no US-shaped parser survives, and — mostly —
that a per-tenant feed of four jobs is trivial to read and tells you nothing about
which tenants exist.

You get the body read from `<description>` rather than the teaser Atom labels as
the body, attributed elements and the nested `department`/`location`/`type`
children matched as they actually arrive, `salary_indication` kept verbatim rather
than guessed at, slugs recovered from behind custom domains, `404.homerun.co`
read as a churned tenant and reaped rather than retried forever, and the [company
join](/docs/objects/company).

Query it with `?source=homerun` and the rows arrive normalised onto the same
[`Job`](/docs/objects/job) shape as every source on [`/ats`](/ats) — with the body
read from the field that actually holds it. `sources` on
[`/v1/meta`](/docs/api-reference/meta) is the live vocabulary: the values
`?source=` accepts at the moment you read it, with counts.
[Quickstart](/docs/quickstart), then [pricing](/pricing).
