Developer guide
The Reach Vacancy API is a RESTful toolkit for surfacing live recruitment data across your career site or back-office workflows. Each request is built from simple URL segments, making it easy to fetch vacancies, metadata and supporting content in the format your application needs.
Every account has a unique API key that is supplied to them upon request. During development you can use our dummy data key in lieu of recieving the live data key.
a83d66d29041c407e8abf4187c533053.
The API key forms part of the hostname, all requests should be over HTTPS. For example, development requests can target
https://a83d66d29041c407e8abf4187c533053.reach-ats.com/.
A condensed reference to the most commonly used JSON endpoints.
All live vacancies
External feed (use internalListing for internal sites).
https://a83d66d29041c407e8abf4187c533053.reach-ats.com/get/job/listing/-/-/-/-/JSON
Radius search listing
Add postcode and range; internal variant available via internalListingByPostcode.
https://a83d66d29041c407e8abf4187c533053.reach-ats.com/get/job/listingByPostcode/B610GD/250/-/-/-/-/JSON
Vacancy information
Use internalInformation for intranet flows.
https://a83d66d29041c407e8abf4187c533053.reach-ats.com/get/job/information/129436/JSON
Vacancy advert text
Raw HTML copy for the advert.
View advert JSONhttps://a83d66d29041c407e8abf4187c533053.reach-ats.com/get/job/advert/129436/JSON
Vacancy files
Supporting documents and metadata.
View file listhttps://a83d66d29041c407e8abf4187c533053.reach-ats.com/get/job/fileurls/129436/JSON
Embed via iframe to collect job alert registrations.
Load iframe demohttps://a83d66d29041c407e8abf4187c533053.reach-ats.com/jobalert/load/6/2
Populate dropdowns and filters with live account metadata.
https://a83d66d29041c407e8abf4187c533053.reach-ats.com/get/job/offices
Also available: locations, roles, types, counties.
You can query the API from any modern language, including PHP, Python, .NET, Perl and Java. and recieve the payload in the format that works best for you.
Returns all live jobs in XML format.
View exampleN.B. View the page source in your browser; XML responses render as raw markup.
Each request is constructed from five segments:
get.job.listing or advert.-) for empty values.XML, JSON, CSV, PIPE or RAW. This is always the last parameter of the URL.
Example: /get/job/listing/-/-/-/-/JSON fetches the job listing,
skips optional filters, and requests JSON.
<response generated="2011-06-30T16:48:07+01:00" records="3"> <record> <id><![CDATA[25618]]></id> <title><![CDATA[Assistant Store Manager - Camden]]></title> <category><![CDATA[Sales]]></category> <role><![CDATA[Sales]]></role> <type><![CDATA[Permanent]]></type> <hours><![CDATA[35]]></hours> <salarydescription><![CDATA[25,000]]></salarydescription> <closingdate><![CDATA[2013-01-04]]></closingdate> </record> </response>
The vacancy module exposes all live job data. Calls are grouped into packages, with the
job package covering everything from vacancy listings to supporting media.
Use filters and alternate encodings to power different channels such as career sites, aggregators or partner feeds.
Expand any endpoint below for its response schema. Where a value is typed in by your team, the schema names the panel and field it comes from on the Edit Vacancy screen — for example Key Information → Salary Description. Those labels are configurable per account, so yours may be worded differently.
The LOCATION, TYPE,
ROLE and COUNTY
slots accept one value or many. Separate multiple values with the pipe character
| (URL-encoded as
%7C). Each value is matched as a substring with LIKE '%value%'; multiple values are OR'd together within the same slot. For example, /get/job/listing/-/-/Permanent%7CContract/-/JSON returns vacancies whose type matches either Permanent or Contract. Single-value calls remain unchanged. Applies to every listing endpoint below except listing2, which is single-value only for backward compatibility.
- to skip and append the desired format.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Records are ordered by vacancy ID descending. Vacancies whose closing date and time have passed are dropped before the response is built.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
/get/lists/offices. Vacancy editor: Vacancy Location → Address Book
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
internal is true. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
external is true. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"officeAddressId": "8",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"lastAdded": "2026-07-01 08:00:00",
"longitude": "-1.512",
"latitude": "52.396",
"iwfId": "17",
"ewfId": "42"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
?include= querystring accepts one or more of NATIONAL, ONLINE, VIRTUAL separated by the pipe character | (e.g. ?include=NATIONAL or ?include=NATIONAL|ONLINE). Jobs whose postcode matches any listed value are always returned, ignoring the radius filter. Values are case-insensitive; unknown values are ignored.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
The radius is applied as a filter only — no distance value is returned. Use longitude/latitude if you need to calculate one yourself.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"longitude": "-1.512",
"latitude": "52.396"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Same shape as listing, restricted to the sources you pass in.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
/get/lists/offices. Vacancy editor: Vacancy Location → Address Book
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
internal is true. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
external is true. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"officeAddressId": "8",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"lastAdded": "2026-07-01 08:00:00",
"longitude": "-1.512",
"latitude": "52.396",
"iwfId": "17",
"ewfId": "42"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
?include= querystring accepts one or more of NATIONAL, ONLINE, VIRTUAL separated by the pipe character | (e.g. ?include=NATIONAL or ?include=NATIONAL|ONLINE). Jobs whose postcode matches any listed value are always returned, ignoring the radius filter. Values are case-insensitive; unknown values are ignored.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
This variant does not return the internal or external workflow flags.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"longitude": "-1.512",
"latitude": "52.396"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Internal feed: applyurl and applicationtype resolve against internal apply sources, and only the external flag is returned.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"lastAdded": "2026-07-01 08:00:00",
"longitude": "-1.512",
"latitude": "52.396",
"hiring_manager": "Jane Doe, Sam Patel"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
?include= querystring accepts one or more of NATIONAL, ONLINE, VIRTUAL separated by the pipe character | (e.g. ?include=NATIONAL or ?include=NATIONAL|ONLINE). Jobs whose postcode matches any listed value are always returned, ignoring the radius filter. Values are case-insensitive; unknown values are ignored.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Internal apply sources only. The radius is applied as a filter — no distance value is returned.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"longitude": "-1.512",
"latitude": "52.396"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Internal apply sources only, restricted to the sources you pass in.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"lastAdded": "2026-07-01 08:00:00",
"longitude": "-1.512",
"latitude": "52.396",
"hiring_manager": "Jane Doe, Sam Patel"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
?include= querystring accepts one or more of NATIONAL, ONLINE, VIRTUAL separated by the pipe character | (e.g. ?include=NATIONAL or ?include=NATIONAL|ONLINE). Jobs whose postcode matches any listed value are always returned, ignoring the radius filter. Values are case-insensitive; unknown values are ignored.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Internal apply sources only. The radius is applied as a filter — no distance value is returned.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"longitude": "-1.512",
"latitude": "52.396"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/get/lists/counties.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
The county feed leaves out the modification and activation timestamps that the other listings carry.
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"longitude": "-1.512",
"latitude": "52.396"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
applyurl resolves against the vacancy’s referral source.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
referral or online, depending on the referral source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"longitude": "-1.512",
"latitude": "52.396",
"applicationtype": "online"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/get/lists/types filters.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
applyurl resolves against the vacancy’s speculative source.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"longitude": "-1.512",
"latitude": "52.396"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Same shape as listing, restricted to vacancies published through an open day source.
YYYY-MM-DD HH:MM:SS, Europe/London).
lastModified as a Unix timestamp.
lastModified when no activation date is stored. Vacancy editor: Optional Information → Activation Date
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
true when an internal workflow is attached to the vacancy, otherwise false. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
true when an external workflow is attached, otherwise false. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
Registration are never returned. Vacancy editor: Key Information → Vacancy Type
Weekday, Do Month YYYY). Vacancy editor: Optional Information → Closing Date
YYYY-MM-DD. Use this one for sorting and comparisons. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means the vacancy closes at 23:59:59 on the closing date. Vacancy editor: Optional Information → Closing Time
logo when no CDN URL is stored, and empty when there is no logo at all. Vacancy editor: Vacancy Location → Address Book (value comes from the office record)
/get/lists/offices. Vacancy editor: Vacancy Location → Address Book
featured property. Empty when the vacancy is not featured. Vacancy editor: Key Information → Featured?
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
/get/lists/counties. Vacancy editor: Vacancy Location → County
/get/lists/locations. Vacancy editor: Vacancy Location → City/Location
https://. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
online or manual, depending on the apply source attached to the vacancy. Vacancy editor: Candidate Attraction → Vacancy Candidate Sources
internal is true. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
external is true. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
[
{
"id": "1024",
"title": "Senior Care Assistant",
"lastModified": "2026-07-14 09:12:03",
"timestamp": "1784020323",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"category": "Care",
"role": "Care Assistant",
"internal": "true",
"external": "true",
"group": "Operations",
"type": "Permanent",
"clientreference": "REF-1024",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "Friday, 28th August 2026",
"isodate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"shortdescription": "Join our award-winning care team in Coventry.",
"officename": "Coventry",
"officegroup": "Midlands",
"division": "Residential",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"logo_url": "https://www.acme.co.uk",
"officeAddressId": "8",
"featured": "1",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"address": "1 Example Street, Coventry, Warwickshire, CV3 1ET, United Kingdom",
"applyurl": "jobs.acme.co.uk/1024ab",
"applicationtype": "online",
"lastAdded": "2026-07-01 08:00:00",
"longitude": "-1.512",
"latitude": "52.396",
"iwfId": "17",
"ewfId": "42"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
All single-vacancy endpoints below only return data for vacancies whose status is live. By default they also exclude vacancies whose closing date/time has passed; pass the optional ?show_closed=1 querystring to lift the closing-date check (the live status check is always enforced). Inaccessible vacancies return an empty response.
/JSON etc. to change format.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Always a single record. Note that internal and external are raw workflow IDs here, not the true/false flags the listing endpoints return.
0 when none is attached. Note this is the raw ID here, not the true/false flag the listing endpoints return. Vacancy editor: Candidate Attraction → the internal source behind “Candidates apply using”
0 when none is attached. Vacancy editor: Candidate Attraction → the external source behind “Candidates apply using”
YYYY-MM-DD. This endpoint does not pre-format the date. Vacancy editor: Optional Information → Closing Date
HH:MM. Empty means end of the closing day. Vacancy editor: Optional Information → Closing Time
NATIONAL, ONLINE or VIRTUAL. Vacancy editor: Vacancy Location → Postcode
activationDate as a Unix timestamp. Vacancy editor: Optional Information → Activation Date
activationDate above and both are returned.
[
{
"id": "1024",
"title": "Senior Care Assistant",
"category": "Care",
"role": "Care Assistant",
"type": "Permanent",
"internal": "17",
"external": "42",
"hours": "37.5",
"salarydescription": "£25,000 - £28,000 per annum",
"closingdate": "2026-08-28",
"closingtime": "17:00",
"interviewdate1": "2026-09-04",
"interviewdate2": "2026-09-11",
"hrcontact": "Jane Doe",
"recruiterEmail": "recruitment@acme.co.uk",
"contactnumber": "024 7612 3456",
"address1": "1 Example Street",
"postcode": "CV3 1ET",
"region": "West Midlands",
"county": "Warwickshire",
"country": "United Kingdom",
"location": "Coventry",
"clientreference": "REF-1024",
"activationDate": "2026-07-01 08:00:00",
"activation_timestamp": "1782806400",
"hiring_manager": "Jane Doe, Sam Patel",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"division": "Residential",
"office": "Coventry",
"activationdate": "2026-07-01 08:04:11",
"applyurl": "https://jobs.acme.co.uk/1024ab",
"shortdescription": "Join our award-winning care team in Coventry."
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/text, /description. Default encoding is RAW
Formats: RAW (default) · XML · JSON · CSV · PIPE
Returns the advert HTML on its own with no wrapper. Ask for another encoding to receive it inside a record under the advertText key. The /text and /description aliases behave identically.
<p>We are looking for a Senior Care Assistant to join our Coventry team.</p><ul><li>37.5 hours per week</li></ul>
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/JSON for client apps.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
One record per attached file. Files marked as hidden and files with no description are excluded.
type when the stored file has no description of its own. Vacancy editor: Vacancy Files → the file’s own description, when it has one
/get/job/file/{FILE_UID} on your API host.
[
{
"type": "Job Description",
"file_description": "Senior Care Assistant JD",
"url": "https://your-key.reach-ats.com/get/job/file/98213"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/file/{FILE_UID} to download content.
Formats: XML (default) · JSON · RSS · CSV · PIPE · RAW
Alias of fileurls — identical response.
type when the stored file has no description of its own. Vacancy editor: Vacancy Files → the file’s own description, when it has one
/get/job/file/{FILE_UID} on your API host.
[
{
"type": "Job Description",
"file_description": "Senior Care Assistant JD",
"url": "https://your-key.reach-ats.com/get/job/file/98213"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
RAW
Formats: Binary stream — the ENCODING segment is ignored.
Streams the vacancy’s web image using the stored Content-Type header. Nothing is returned when the vacancy has no image.
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/video_embed for iframe-ready content. Default encoding is RAW
Formats: RAW HTML — the ENCODING segment is ignored.
Returns the stored embed markup, HTML-entity decoded. Empty when the vacancy has no video. /video_embed/{JOB_ID}/ENCODING is a different call: it returns the advert’s advertVideo field and does honour the encoding segment.
<iframe src="https://player.vimeo.com/video/123456789" width="640" height="360" frameborder="0" allowfullscreen></iframe>
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
/applyurlsource/{JOB_ID}/{TYPE} for internal vs external sources. Default encoding is RAW
Formats: RAW (default) · XML · JSON · CSV · PIPE
The response-domain apply link for the vacancy, with no source short code. /applyurlsource/{JOB_ID}/{TYPE} returns the same link with the short code for ext (default) or int sources appended, and /applysource/{JOB_ID}/{TYPE} returns just the short code.
https://jobs.acme.co.uk/1024
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
RAW
Formats: RAW (default) · XML · JSON · CSV · PIPE
Built from the vacancy’s referral source. Returned without a scheme — prefix it with https://. Empty when the vacancy has no referral source.
jobs.acme.co.uk/1024rf
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct location across every vacancy on the account, whatever its status. Populated from Vacancy Location → City/Location on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
listingCounty for search.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct county across every vacancy on the account. Feed these values straight into listingCounty. Populated from Vacancy Location → County on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct role in use on vacancies. Populated from Key Information → Role on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct type. Vacancies of type registration are excluded. Populated from Key Information → Vacancy Type on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
As locations, limited to vacancies with status live. Populated from Vacancy Location → City/Location on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
As roles, limited to vacancies with status live. Populated from Key Information → Role on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Read from the managed role list rather than from vacancies, so roles with no vacancies against them are included. These are the options behind Key Information → Role, managed under account settings.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Distinct region values taken from your office addresses. Comes from the office record, not the vacancy — the vacancy picks an office at Vacancy Location → Address Book.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
A trimmed office row, sorted by office name. Use /get/lists/offices when you need the full record. Office records, not vacancy fields — a vacancy is tied to one of these at Vacancy Location → Address Book.
logo when no CDN URL is stored.
[
{
"value": "Coventry",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Every department/group on the account, sorted by name. Unlike /get/lists/groups this call does not filter on status. Populated from Key Information → the department/group field on each vacancy.
[
{
"id": "6",
"value": "Operations"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Distinct category values in use on vacancies, whatever the vacancy status. Populated from Key Information → Categories on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
The managed category list, sorted by its configured sort order. value holds the category name. These are the options behind Key Information → Categories, managed under account settings.
[
{
"id": "6",
"value": "Operations"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
The lists module provides lightweight
endpoints for populating filters, dropdowns and form pickers. Each call mirrors a metadata query from the Job controller but exposes it under the
/get/lists namespace, returning JSON by default for easy client-side consumption.
These endpoints are related to the vacancies set up within the system - only values used will be included in the list
Formats: JSON (default) · XML · CSV · PIPE · RAW
Distinct category values in use on vacancies. Populated from Key Information → Categories on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct location across every vacancy on the account. Populated from Vacancy Location → City/Location on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct county. Feed these values straight into /get/job/listingCounty. Populated from Vacancy Location → County on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct role in use on vacancies. Populated from Key Information → Role on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
registration placeholder.
Formats: JSON (default) · XML · CSV · PIPE · RAW
One row per distinct type. Vacancies of type registration are excluded. Populated from Key Information → Vacancy Type on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Use the live_* endpoints to limit filter options to values that have active vacancies.
Formats: JSON (default) · XML · CSV · PIPE · RAW
As locations, limited to vacancies with status live. Populated from Vacancy Location → City/Location on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
As roles, limited to vacancies with status live. Populated from Key Information → Role on each vacancy.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Returns the whole office record for every active office, so columns beyond those listed here may appear as the office record grows. Sorted by office name. Office records, not vacancy fields — a vacancy is tied to one of these at Vacancy Location → Address Book.
officeAddressId on listing records.
/get/lists/regions.
0 is an active office.
[
{
"id": "8",
"accountId": "12",
"office": "Coventry",
"officeGroup": "Midlands",
"division": "Residential",
"address1": "1 Example Street",
"address2": "Foleshill",
"town": "Coventry",
"county": "Warwickshire",
"region": "West Midlands",
"country": "United Kingdom",
"postcode": "CV3 1ET",
"logo": "acme-logo.png",
"logo_cdn": "https://cdn.reach-ats.com/images/logos/12/acme-logo.png",
"url": "https://www.acme.co.uk",
"status": "0"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Distinct region values taken from your office addresses. Comes from the office record, not the vacancy — the vacancy picks an office at Vacancy Location → Address Book.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Active departments/groups only, sorted by name. value holds the group name. Populated from Key Information → the department/group field on each vacancy.
[
{
"id": "6",
"value": "Operations"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
The managed category list, sorted by its configured sort order. value holds the category name. These are the options behind Key Information → Categories, managed under account settings.
[
{
"id": "6",
"value": "Operations"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
Formats: JSON (default) · XML · CSV · PIPE · RAW
Read from the managed role list rather than from vacancies, so roles with no vacancies against them are included. These are the options behind Key Information → Role, managed under account settings.
[
{
"value": "Coventry"
}
]
Every value is serialised as a string — the type column describes the underlying value. XML responses wrap the records in
<response generated="…" records="…"> with one <record> per row.
listing) to offer contextual filtering.Job alert sign-up forms typically ask candidates to opt into roles, locations or employment types they are interested in. The vacancy metadata endpoints provide the lookup data you need to build these filters dynamically, ensuring your alert preferences always reflect the current catalogue.
Reach ships a responsive HTML page for job alert sign-up. Embed it inside an iframe or surface it within a modal/popup to collect alert registrations.
Preview alert pageIt may be possible for us to create a bespoke sign up template for you mimicing your branding styles, please let us know if this is something you require.
The Jobalert module exposes helper routes for pre-built signup forms that you can iFrame or load in a modal. Swap the rows, version, client, or group parameters as needed.
| Endpoint | Purpose | Key Options |
|---|---|---|
| /jobalert/load/{ROWS}/{VERSION} | Default alert form with type/role filters. | ROWS controls number item in the multple select boxes; VERSION picks template (1 legacy, 2 updated). |
| /jobalert/loadWithLocation/{ROWS}/{VERSION} | Signup including location dropdown. | VERSION 1 (legacy), 2 (jobalertlocation2), 3 (jobalertlocation3). |
| /jobalert/loadWithTown/{ROWS}/{VIEW} | Signup using town list instead of region. | Set VIEW to a template name (e.g. jobalertlocation2) or leave numeric for defaults. |
| Bespoke template endpoints | ||
| /jobalert/loadClient/{ROWS}/{CLIENT}/{LIVE} | Serve a named client template with live or cached data. | CLIENT view name (e.g. jobalert2); set LIVE=true for lists to only use current live vacancy data. |
| /jobalert/loadClientGroup/{ROWS}/{GROUP}/{CLIENT} | Pre-filter form by group/department. | GROUP slug or - for all; CLIENT selects the view template. |
The PHP snippets below demonstrate how to consume listing data and specific vacancy detail. Swap in your account’s API key and adjust the rendering code to match your templating approach.
<?php
class ReachApiClient {
private string $host;
public function __construct(string $apiKey) {
$this->host = sprintf('https://%s.reach-ats.com', $apiKey);
}
public function get(string $path): string {
$url = $this->host . $path;
if (ini_get('allow_url_fopen')) {
$response = @file_get_contents($url);
} elseif (function_exists('curl_init')) {
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($curl);
curl_close($curl);
} else {
throw new RuntimeException('Unable to fetch data; enable allow_url_fopen or install cURL.');
}
if ($response === false) {
throw new RuntimeException("Reach ATS request failed for {$url}");
}
return $response;
}
public function getJson(string $path): array {
$payload = trim($this->get($path));
if ($payload === '' || strcasecmp($payload, 'null') === 0) {
return [];
}
try {
$decoded = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
error_log(sprintf('Reach ATS JSON decode failed for "%s": %s', $path, $exception->getMessage()));
return [];
}
return is_array($decoded) ? $decoded : [];
}
public static function renderOptions(array $values, ?string $selected = null): string {
$items = array_unique(array_filter(array_map('strval', $values)));
sort($items, SORT_NATURAL | SORT_FLAG_CASE);
return implode('', array_map(static function (string $value) use ($selected): string {
$isSelected = $selected !== null && strcasecmp($selected, $value) === 0;
return sprintf(
'<option value="%s"%s>%s</option>',
rawurlencode($value),
$isSelected ? ' selected' : '',
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
);
}, $items));
}
public function listing(
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/listing/', [
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function listingByPostcode(
string $postcode,
string $range,
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/listingByPostcode/', [
$postcode,
$range,
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function listingBySource(
string $sources,
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/listingBySource/', [
$sources,
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function listingBySourceAndPostcode(
string $sources,
string $postcode,
string $range,
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/listingBySourceAndPostcode/', [
$sources,
$postcode,
$range,
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function internalListing(
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/internalListing/', [
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function internalListingByPostcode(
string $postcode,
string $range,
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/internalListingByPostcode/', [
$postcode,
$range,
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function internalListingBySource(
string $sources,
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/internalListingBySource/', [
$sources,
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function internalListingBySourceAndPostcode(
string $sources,
string $postcode,
string $range,
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/internalListingBySourceAndPostcode/', [
$sources,
$postcode,
$range,
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function listingByCounty(
string $keywords = '-',
string $county = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/listingCounty/', [
$keywords,
$county,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function referralListing(
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/referralListing/', [
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function speculativeListing(
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/speculativeListing/', [
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function openDayListing(
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/openDayListing/', [
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
public function jobInformation(int $jobId, string $encoding = 'JSON'): array {
return $this->getJson($this->buildPath('/get/job/information/', [
$jobId,
$this->formatEncoding($encoding),
]))[0] ?? [];
}
public function jobAdvert(int $jobId): string {
return $this->get($this->buildPath('/get/job/advert/', [$jobId]));
}
public function jobFileUrls(int $jobId, string $encoding = 'JSON'): array {
return $this->getJson($this->buildPath('/get/job/fileurls/', [
$jobId,
$this->formatEncoding($encoding),
]));
}
private function buildPath(string $prefix, array $segments): string {
return $prefix . implode('/', array_map([$this, 'segment'], $segments));
}
private function segment(string $value): string {
if ($value === null || $value === '') {
return '-';
}
if ($value === '-') {
return '-';
}
return rawurlencode((string) $value);
}
private function formatEncoding(?string $encoding): string {
return strtoupper($encoding ?? 'JSON');
}
}
?>
<?php
try {
include_once('reach_api.php');
$client = new ReachApiClient('a83d66d29041c407e8abf4187c533053');
$listing = $client->listing();
// Apply simple, client-side filtering beyond the API segments.
$filters = [
'division' => $_GET['division'] ?? null,
'officename' => $_GET['officename'] ?? null,
'hours' => $_GET['hours'] ?? null,
'role' => $_GET['role'] ?? null,
];
$filtered = array_filter($listing, static function (array $job) use ($filters): bool {
foreach ($filters as $key => $value) {
if ($value === null || $value === '') {
continue;
}
if (!isset($job[$key]) || strcasecmp($job[$key], $value) !== 0) {
return false;
}
}
return true;
});
$options = [
'division' => ReachApiClient::renderOptions(array_column($listing, 'division'), $filters['division']),
'officename' => ReachApiClient::renderOptions(array_column($listing, 'officename'), $filters['officename']),
'hours' => ReachApiClient::renderOptions(array_column($listing, 'hours'), $filters['hours']),
'role' => ReachApiClient::renderOptions(array_column($listing, 'role'), $filters['role']),
];
} catch (Throwable $exception) {
error_log($exception->getMessage());
echo $exception->getMessage();
}
?>
<form method="get" action="">
<label>
Division
<select name="division" onchange="this.form.submit()">
<option value="">All divisions</option>
<?= $options['division']; ?>
</select>
</label>
<label>
Office
<select name="officename" onchange="this.form.submit()">
<option value="">All offices</option>
<?= $options['officename']; ?>
</select>
</label>
<label>
Role
<select name="role" onchange="this.form.submit()">
<option value="">All roles</option>
<?= $options['role']; ?>
</select>
</label>
<label>
Hours
<select name="hours" onchange="this.form.submit()">
<option value="">Any hours</option>
<?= $options['hours']; ?>
</select>
</label>
<noscript><button type="submit">Apply filters</button></noscript>
</form>
<p>Showing <strong><?= count($filtered); ?></strong> of <strong><?= count($listing); ?></strong> vacancies.</p>
<?php foreach ($filtered as $job): ?>
<?php
try {
$jobId = $job['id'];
$job = $client->jobInformation($jobId);
$advertHtml = $client->jobAdvert($jobId);
$attachments = $client->jobFileUrls($jobId);
} catch (Throwable $exception) {
error_log($exception->getMessage());
echo $exception->getMessage();
}
?>
<section class="vacancy-summary" style="border:1px solid #000;padding:5px 10px;margin-bottom:5px;">
<h3><?= htmlspecialchars($job['title'], ENT_QUOTES, 'UTF-8'); ?></h3>
<div class="vacancy-advert"><?= $advertHtml; ?></div>
<?php if (!empty($attachments)): ?>
<h4>Attachments</h4>
<ul>
<?php foreach ($attachments as $file): ?>
<li>
<a href="<?= htmlspecialchars($file['url'] ?? '#', ENT_QUOTES, 'UTF-8'); ?>" target="_blank">
<?= htmlspecialchars($file['type'] ?? 'Download', ENT_QUOTES, 'UTF-8'); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</section>
<?php endforeach; ?>
<?php
try {
include_once('reach_api.php');
$client = new ReachApiClient('a83d66d29041c407e8abf4187c533053');
$jobId = 129436;
$information = $client->jobInformation($jobId);
$advertHtml = $client->jobAdvert($jobId);
$fileAttachments = $client->jobFileUrls($jobId);
} catch (Throwable $exception) {
error_log($exception->getMessage());
echo $exception->getMessage();
}
if (!empty($information)) {
echo '<h3>' . htmlspecialchars($information['title'] ?? 'Vacancy', ENT_QUOTES, 'UTF-8') . '</h3>';
echo $advertHtml; // Already HTML
if (!empty($fileAttachments)) {
echo '<h4>Supporting documents</h4><ul>';
foreach ($fileAttachments as $file) {
$label = htmlspecialchars($file['type'] ?? 'Download', ENT_QUOTES, 'UTF-8');
$url = htmlspecialchars($file['url'] ?? '#', ENT_QUOTES, 'UTF-8');
echo '<li><a href="' . $url . '" target="_blank">' . $label . '</a></li>';
}
echo '</ul>';
}
}
?>
The official Reach ATS WordPress plugin is the fastest way to embed live vacancies on a WordPress site — no theme edits, no PHP. Install the zip, set your API key, and use Gutenberg blocks or shortcodes anywhere. Prefer to wire it up by hand? See WordPress Shortcodes below.
Drop-in plugin with a Settings → Reach ATS admin page, Gutenberg blocks for listings, filtered listings and vacancy detail, plus transient caching of API responses.
Alpha release — please report issues to support@reach-ats.com.
If you’d rather not install the official plugin, the snippets below
give you the same shortcodes via your theme’s
functions.php file and register shortcodes for listings, vacancy detail pages. The examples use strict typing,
shared network helpers and defensive error handling.
<?php
class Reach_Api_Helper {
private string $host;
public function __construct(private readonly string $apiKey) {
$this->host = sprintf('https://%s.reach-ats.com', $apiKey);
}
public function get(string $path): string {
$url = $this->host . $path;
if (ini_get('allow_url_fopen')) {
$response = @file_get_contents($url);
} elseif (function_exists('curl_init')) {
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($curl);
curl_close($curl);
} else {
throw new RuntimeException('Reach request requires allow_url_fopen or cURL support.');
}
if ($response === false) {
throw new RuntimeException("Reach request failed for {$url}");
}
return $response;
}
public function getJson(string $path): array {
return json_decode($this->get($path), true, 512, JSON_THROW_ON_ERROR);
}
public function listing(
string $keywords = '-',
string $location = '-',
string $type = '-',
string $role = '-',
string $encoding = 'JSON'
): array {
return $this->getJson($this->buildPath('/get/job/listing/', [
$keywords,
$location,
$type,
$role,
$this->formatEncoding($encoding),
]));
}
/**
* Fetch the full live listing then filter it locally with array_filter()
* against any field in the job payload (e.g. role, location, type,
* county, region, group, officename, category, etc.).
*
* $filters is keyed by field name. Each value may be a single token
* or an array of tokens. Within a field the tokens are OR'd; between
* fields they are AND'd. Empty / missing fields skip that filter.
* Matching is case-insensitive substring (null payload values coerce
* to '' and never match a non-empty token).
*
* Example:
* $api->filteredListing([
* 'role' => ['Home Care Worker', 'Nurse'],
* 'county' => 'Greater Manchester',
* 'type' => 'Permanent',
* ]);
*/
public function filteredListing(
array $filters = [],
string $keywords = '-',
string $encoding = 'JSON'
): array {
$jobs = $this->listing($keywords, '-', '-', '-', $encoding);
$normalised = [];
foreach ($filters as $field => $values) {
$values = is_array($values) ? $values : [$values];
$tokens = array_values(array_filter(
array_map(static fn ($value): string => strtolower(trim((string) $value)), $values),
static fn (string $value): bool => $value !== ''
));
if ($tokens !== []) {
$normalised[(string) $field] = $tokens;
}
}
if ($normalised === []) {
return $jobs;
}
$matchesAny = static function (string $haystack, array $needles): bool {
foreach ($needles as $needle) {
if (str_contains($haystack, $needle)) {
return true;
}
}
return false;
};
return array_values(array_filter(
$jobs,
static function (array $job) use ($normalised, $matchesAny): bool {
foreach ($normalised as $field => $tokens) {
$haystack = strtolower((string) ($job[$field] ?? ''));
if (!$matchesAny($haystack, $tokens)) {
return false;
}
}
return true;
}
));
}
public function jobInformation(int|string $jobId, string $encoding = 'JSON'): array {
return $this->getJson($this->buildPath('/get/job/information/', [
$jobId,
$this->formatEncoding($encoding),
]));
}
public function jobAdvert(int|string $jobId): string {
return $this->get($this->buildPath('/get/job/advert/', [$jobId]));
}
public function jobFileUrls(int|string $jobId, string $encoding = 'JSON'): array {
return $this->getJson($this->buildPath('/get/job/fileurls/', [
$jobId,
$this->formatEncoding($encoding),
]));
}
private function buildPath(string $prefix, array $segments): string {
return $prefix . implode('/', array_map([$this, 'segment'], $segments));
}
private function segment(null|int|string $value): string {
if ($value === null || $value === '') {
return '-';
}
if ($value === '-') {
return '-';
}
return rawurlencode((string) $value);
}
private function formatEncoding(?string $encoding): string {
return strtoupper($encoding ?? 'JSON');
}
}
?>
Usage: [reach-listing key="YOUR_KEY" detail_slug="/vacancies/detail" qs_param="vacancy_id"]
function reach_listing_shortcode(array $atts): string {
$atts = shortcode_atts([
'key' => 'a83d66d29041c407e8abf4187c533053',
'detail_slug' => '/vacancies/detail',
'qs_param' => 'vacancy_id',
], $atts, 'reach-listing');
$api = new Reach_Api_Helper($atts['key']);
try {
$jobs = $api->listing();
} catch (Throwable $exception) {
error_log($exception->getMessage());
return '<p>Vacancies are unavailable right now.</p>';
}
if (empty($jobs)) {
return '<p>No live vacancies at the moment.</p>';
}
$output = '';
foreach ($jobs as $job) {
$advertExcerpt = !empty($job['shortdescription'])
? $job['shortdescription']
: substr(strip_tags($api->jobAdvert($job['id'])), 0, 200) . '...';
$detailUrl = esc_url(add_query_arg($atts['qs_param'], $job['id'], $atts['detail_slug']));
$applyUrl = esc_url($job['applyurl']);
$output .= sprintf(
'<article class="vacancy-card" data-group="%s">
<header>
<h3><a href="%s">%s</a></h3>
<p><strong>Location:</strong> %s</p>
<p><strong>Function:</strong> %s</p>
</header>
<p>%s</p>
<p>
<a class="btn btn-primary" target="_blank" href="%s">Apply now</a>
<a class="btn btn-outline" href="%s">More detail</a>
</p>
</article>',
esc_attr($job['group'] ?? ''),
esc_url($detailUrl),
esc_html($job['title']),
esc_html($job['location'] ?? 'Not specified'),
esc_html($job['role'] ?? 'Not specified'),
esc_html($advertExcerpt),
$applyUrl,
esc_url($detailUrl)
);
}
return $output;
}
add_shortcode('reach-listing', 'reach_listing_shortcode');
Usage: [reach-filtered-listing key="YOUR_KEY" role="Nurse|Doctor" county="Greater Manchester"]
Any attribute other than the reserved set (key,
detail_slug,
qs_param,
keywords) is treated as a filter on the
job payload field of the same name (role, location, type, county, region, officename, group,
category, applicationtype, etc.). Multi-value uses
| or
, as the separator (interchangeable).
Tokens within a field are OR'd, fields are AND'd, matching is case-insensitive substring.
keywords still routes through the
API's server-side text search and is cheaper than in-memory filtering on large accounts.
function reach_filtered_listing_shortcode(array $atts): string {
$atts = $atts ?: [];
// Reserved shortcode attributes - everything else is treated as a filter.
$reserved = ['key', 'detail_slug', 'qs_param', 'keywords'];
$config = shortcode_atts([
'key' => 'a83d66d29041c407e8abf4187c533053',
'detail_slug' => '/vacancies/detail',
'qs_param' => 'vacancy_id',
'keywords' => '-',
], array_intersect_key($atts, array_flip($reserved)), 'reach-filtered-listing');
// Build the filter map. Pipe OR comma separated values become multi-token OR filters.
$filters = [];
foreach (array_diff_key($atts, array_flip($reserved)) as $field => $value) {
$tokens = preg_split('/[|,]/', (string) $value, -1, PREG_SPLIT_NO_EMPTY);
$tokens = array_values(array_filter(array_map('trim', $tokens), 'strlen'));
if ($tokens !== []) {
$filters[(string) $field] = $tokens;
}
}
$api = new Reach_Api_Helper($config['key']);
try {
$jobs = $api->filteredListing($filters, $config['keywords']);
} catch (Throwable $exception) {
error_log($exception->getMessage());
return '<p>Vacancies are unavailable right now.</p>';
}
if (empty($jobs)) {
return '<p>No live vacancies match those filters.</p>';
}
$output = '';
foreach ($jobs as $job) {
$advertExcerpt = !empty($job['shortdescription'])
? $job['shortdescription']
: substr(strip_tags($api->jobAdvert($job['id'])), 0, 200) . '...';
$detailUrl = esc_url(add_query_arg($config['qs_param'], $job['id'], $config['detail_slug']));
$applyUrl = esc_url($job['applyurl']);
$output .= sprintf(
'<article class="vacancy-card" data-group="%s">
<header>
<h3><a href="%s">%s</a></h3>
<p><strong>Location:</strong> %s</p>
<p><strong>Function:</strong> %s</p>
</header>
<p>%s</p>
<p>
<a class="btn btn-primary" target="_blank" href="%s">Apply now</a>
<a class="btn btn-outline" href="%s">More detail</a>
</p>
</article>',
esc_attr($job['group'] ?? ''),
esc_url($detailUrl),
esc_html($job['title']),
esc_html($job['location'] ?? 'Not specified'),
esc_html($job['role'] ?? 'Not specified'),
esc_html($advertExcerpt),
$applyUrl,
esc_url($detailUrl)
);
}
return $output;
}
add_shortcode('reach-filtered-listing', 'reach_filtered_listing_shortcode');
Usage: [reach-detail key="YOUR_KEY" qs_param="vacancy_id"]
function reach_detail_shortcode(array $atts): string {
$atts = shortcode_atts([
'key' => 'a83d66d29041c407e8abf4187c533053',
'qs_param' => 'vacancy_id',
], $atts, 'reach-detail');
$vacancyId = isset($_GET[$atts['qs_param']]) ? sanitize_text_field($_GET[$atts['qs_param']]) : null;
if (!$vacancyId) {
return '<p>Please select a vacancy.</p>';
}
$api = new Reach_Api_Helper($atts['key']);
try {
$info = $api->jobInformation($vacancyId);
$detail = $info[0] ?? [];
$advert = $api->jobAdvert($vacancyId);
$files = $api->jobFileUrls($vacancyId);
} catch (Throwable $exception) {
error_log($exception->getMessage());
return '<p>We can’t load that vacancy right now.</p>';
}
if (empty($detail)) {
return '<p>Sorry, we can’t find that vacancy in our system.</p>';
}
ob_start();
?>
<article class="vacancy-detail">
<h2><?= esc_html($detail['title']); ?></h2>
<p>
<strong>Location:</strong> <?= esc_html($detail['location'] ?? ''); ?><br />
<strong>Category:</strong> <?= esc_html($detail['category'] ?? ''); ?><br />
<strong>Closing date:</strong> <?= esc_html($detail['closingdate'] ?? ''); ?>
</p>
<div class="vacancy-advert"><?= wp_kses_post($advert); ?></div>
<p><a class="btn btn-primary" target="_blank" href="<?= esc_url($detail['applyurl'] ?? '#'); ?>">Apply now</a></p>
<?php if (!empty($files)): ?>
<h3>Supporting documents</h3>
<ul>
<?php foreach ($files as $file): ?>
<li><a href="<?= esc_url($file['url'] ?? '#'); ?>" target="_blank"><?= esc_html($file['type'] ?? 'Download'); ?></a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</article>
<?php
return ob_get_clean();
}
add_shortcode('reach-detail', 'reach_detail_shortcode');
Usage: [reach-jobalert-url key="YOUR_KEY" account_id="6" template_id="2"]. Returns a ready-to-embed URL.
function reach_jobalert_url_shortcode(array $atts): string {
$atts = shortcode_atts([
'key' => 'a83d66d29041c407e8abf4187c533053',
'account_id' => '6',
'template_id' => '2',
], $atts, 'reach-jobalert-url');
return sprintf(
'https://%s.reach-ats.com/jobalert/load/%d/%d',
rawurlencode($atts['key']),
(int) $atts['account_id'],
(int) $atts['template_id']
);
}
add_shortcode('reach-jobalert-url', 'reach_jobalert_url_shortcode');