All platforms

Rival Jobs API

What Rival (formerly SilkRoad) boards publish, how their HTML is read, and what ?source=rival returns today.

?source=rivalsource reference

Rival is the recruiting platform formerly known as SilkRoad, and like several renames in this pool the plumbing kept the old name: the boards are still served from jobs.silkroad.com/<tenant>/Careers. Its customers are mid-size US employers, in community services, healthcare and regional operations, with small boards and low posting volume per company.

What a Rival board looks like#

Two GETs, no auth, HTML at both levels — there's no JSON API and no feed:

BASH
# List — 10 per page, fixed
curl "https://jobs.silkroad.com/<tenant>/Careers?page=1"
 
# Detail — the body lives here
curl "https://jobs.silkroad.com/<tenant>/Careers/jobs/<id>"

The board token is the tenant path segment, and it's case-insensitive — the server canonicalises it, so two casings of one tenant are one board and have to be folded to stay that way.

What the adapter reads, and what it has to work around#

There is no JSON-LD, and the way there isn't matters. The pages carry zero ld+json blocks. What makes that dangerous rather than merely inconvenient is that a generic JobPosting extractor doesn't return null here. It returns {description: ""}. An adapter that trusts it stores hundreds of empty postings at HTTP 200, each one looking like a successfully scraped job with a blank body. The body is in the HTML; it has to be read from the DOM.

Pagination is real, which is rare in this pool. ?page=N genuinely works — pages 1, 2 and 3 return ten ids each with zero overlap. PageSize and pageSize are ignored; the page size is always 10.

But the pager UI under-reports. It only ever renders links for pages 1 and 2, even when page 3 has jobs on it. Derive the page count from the pager and you truncate every board bigger than twenty postings, silently. The only correct termination is crawling until a page comes back empty.

The list's location beats the detail's. The board row carries a clean, geocodable Jewett City, CT. The detail carries the street address, 1 Sylvandale Road, Jewett City, Connecticut. The row is the better field, which is the opposite of the usual arrangement and worth knowing before you reach for the detail out of habit.

Detail blocks are tenant-configurable. Every field is optional by tenant rather than by job, so a board that ships no category ships none anywhere, and a sample of one board tells you nothing about the next.

No posting date exists anywhere on either level, checked. And DisplayMinSalary is a bare amount with no period: $70,000.00 (annual) and $20.00 (hourly) are indistinguishable, so it stays text rather than becoming a wrong number.

Verified against a live board at 26 of 26 descriptions.

Or use ours#

Two GETs and no auth makes Rival look like a board you could read yourself. The parts that cost you are the ones that fail quietly at HTTP 200: the extractor that hands back a blank body, the pager that stops advertising at page two, the tenant casing that splits one board in half.

You get the description read from the DOM rather than from JSON-LD that isn't there, pages crawled until one comes back empty rather than counted off the pager, the board row's geocodable location preferred over the detail's street address, the tenant token folded so two casings stay one board, and DisplayMinSalary left as text rather than guessed into a wrong number. No posting date, because the platform doesn't publish one anywhere.

Then the usual: dedup and the company join, normalised onto the same schema as every other source.

Query it with ?source=rival. /v1/meta is the live vocabulary and the live counts — one cheap request, never stale, and the thing to read rather than hardcode. An unknown source value isn't a 400; it simply matches nothing.

Quickstart · All platforms

Every source above normalises onto the same schema, with the hiring company already joined onto each posting.

Get an API key