# Workable Jobs API

> Query jobs from Workable boards through one API — and why the endpoint you'd reach for first will get your IP locked out for most of a day.

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

Workable is hiring and onboarding software used mostly by small and mid-sized
companies, with boards at `apply.workable.com/<account>`. It has a public API and
a widget endpoint, and choosing the wrong one of the two is the difference between
a whole board in one request and your IP being locked out for the rest of the day.
This is the platform where we have the sharpest measurement of that difference, so
this page is mostly about it.

## Try it

```bash
curl -G https://api.hyperjobs.io/v1/jobs \
  -H "Authorization: Bearer $HYPERJOBS_KEY" \
  --data-urlencode "source=workable" \
  --data-urlencode "time_frame=7d" \
  --data-urlencode "limit=2"
```

```json title="Illustrative — the shape, not a live response"
{
  "data": [
    {
      "id": "workable:A1B2C3D4E5",
      "title": "Registered Nurse — Night Shift",
      "company": {
        "slug": "cedar-health",
        "name": "Cedar Health",
        "domain": "cedarhealth.org",
        "website": "https://cedarhealth.org",
        "logo": "https://media.licdn.com/dms/image/…",
        "industry": "Hospital & Health Care",
        "employee_count": 640
      },
      "location": {
        "remote": false,
        "countries": ["United States"],
        "cities": ["Portland"],
        "regions": ["Oregon"],
        "raw": ["Portland, Oregon, United States"]
      },
      "employment_type": ["FULL_TIME"],
      "work_arrangement": "On-site",
      "seniority": "Associate",
      "education_requirements": ["bachelor degree"],
      "salary": null,
      "skills": ["patient care", "triage"],
      "taxonomies": ["Healthcare"],
      "apply_url": "https://apply.workable.com/cedar-health/j/A1B2C3D4E5/",
      "url": "https://apply.workable.com/cedar-health/j/A1B2C3D4E5/",
      "source": "workable",
      "posted_at": "2026-07-09T00:00:00.000Z",
      "expires_at": null,
      "updated_at": "2026-07-13T22:08:41.000Z"
    }
  ],
  "total": 2,
  "limit": 2,
  "offset": 0
}
```

## How Workable boards actually work

There are two ways in. Take the second one.

```bash
# The obvious path: v3 list + v2 detail per posting. Don't.
# The one that works: the embed widget — whole board, descriptions included, ONE request.
curl "https://apply.workable.com/api/v1/widget/accounts/<account>?details=true"
```

`<account>` is the slug from `apply.workable.com/<account>` or
`<account>.workable.com`. The response is `{ name, description, jobs: [...] }`,
and with `details=true` every job carries its full HTML `description` inline.

### Why the widget instead of the list-plus-detail

Because we measured both.

The N+1 path — one list call, then a detail fetch per posting at concurrency 5 —
produces a flood of new TLS connections per account. Workable's WAF **resets new
TLS connections under burst**: curl reported `000`, not even a 429. There is no
status code to catch, no `Retry-After` to respect, nothing that looks like rate
limiting. It just fails.

| Path | Result |
| --- | --- |
| v3 list + v2 detail, concurrency 5 | **2,677 of 2,951 boards errored** — 153 jobs total |
| Widget, one request per account | **18 of 18 boards returned 200** |

That's the entire argument. One request per account, and Node's in-process
keep-alive reuses the connection across accounts, so the whole run shares a small
pool instead of opening thousands of sockets. The practical rule that follows:
keep concurrency on this provider **low** — one or two — because the fix isn't the
endpoint alone, it's the connection count.

<Warning title="A burst gets your IP locked for hours, not seconds">
  Push it and Workable hard-locks the source IP. The observed `Retry-After` was
  roughly **19 hours**. Not a backoff — a lockout.

  Once that trips, failing through thousands of boards one at a time burns the
  entire run for nothing. Our adapter opens a circuit breaker on the first big
  `Retry-After` and skips every remaining Workable board instantly. Skipped boards
  are left unmarked, so they stay stale and retry next run rather than being
  recorded as empty.
</Warning>

This is why Workable sits in the **proxy-only** tier here. It works fine direct at
low volume — which is exactly what makes it dangerous, because your test against
three boards will succeed and tell you nothing about what 3,000 will do.

### What the widget gives you

Per job: `shortcode`, `title`, `description` (full HTML), `employment_type`,
`telecommuting`, `experience` (LinkedIn's seniority vocabulary), `department`,
`function`, `industry`, `education`, `published_on`, `locations[]`, and
`application_url`. `industry` is populated on close to every posting; `function`
on roughly 30%, and `education` about the same.

## What bites you

**The same job appears once per location.** The widget repeats a `shortcode` for
each of a job's locations, each copy carrying one location. Key by id and you keep
one location and throw the rest away — a three-city role becomes a one-city role,
quietly. Group by `shortcode` and union the locations into one document.

**`telecommuting` is a clean binary with no hybrid.** Unlike
[Ashby](/ats/ashby)'s hybrid-inclusive flag, this one is honest — but Workable has
no hybrid concept at all, so `false` genuinely means on-site and hybrid roles are
simply not expressible. A null `work_arrangement` on a Workable posting often
means the platform had nowhere to put it.

**A location can be hidden.** `locations[].hidden` is a real flag. Include those
and you publish a location the employer suppressed.

**The account slug isn't always the first path segment.** `apply.workable.com/j/<code>`,
`/api`, `/view`, `/embed` and friends look structurally identical to an account
URL. Derive a board token by taking segment one and you'll add `j` and `api` to
your board list as if they were companies.

**`employment_type` can be the literal string `Other`.** It maps to nothing
useful; storing it gives you an employment type of `Other`, which is worse than
null.

## Or use ours

The honest summary of this platform: the endpoint is easy and the traffic
discipline is not. One request per board, low concurrency, connection reuse, and a
breaker for the day you get it wrong anyway. Our queue tracks 5,794 Workable
boards — enough that the difference between the two paths is a run that works and
a run that returns 153 jobs.

You also get the per-location duplicates collapsed into one posting with all its
locations, hidden locations dropped, and the [company
join](/docs/objects/company).

<Note title="One caveat we'd rather you hear from us">
  Workable's `shortcode` is not the same identifier as the token in a
  `jobs.workable.com/view/<token>` URL. If you're reconciling our Workable rows
  against a dataset keyed on that other id, they won't match on the key. Every
  other source here deduplicates cleanly against external ids; this one doesn't.
</Note>

Workable is in the proxy-only refresh tier, attempted every third cycle — a new
posting can take several hours to appear, and expiry runs on the same clock.
[Coverage](/docs/reference/coverage) has the detail.

[Quickstart](/docs/quickstart) · [Pricing](/pricing)
