Install
openclaw skills install @scavio-ai/scavio-indeedSearch Indeed job postings, pull one posting in full with its original ATS link, and read employer profiles and employee reviews. 4 endpoints, 2 credits each, structured JSON for recruiting and labour-market research.
openclaw skills install @scavio-ai/scavio-indeedSearch Indeed job postings, read one posting in full, and pull an employer's profile and employee reviews. All endpoints return structured JSON.
Use this skill when the user asks to:
Get a free API key at https://scavio.dev (50 free credits to get started, no card required):
export SCAVIO_API_KEY=sk_live_your_key
Every request is a POST with a JSON body and:
Authorization: Bearer $SCAVIO_API_KEY
Base URL: https://api.scavio.dev. Every Indeed endpoint costs 2 credits.
| Endpoint | Credits | What it returns |
|---|---|---|
POST /api/v1/indeed/search | 2 | Job postings: title, employer, rating, location, salary range, job type, benefits, posting age, apply route |
POST /api/v1/indeed/job | 2 | One posting in full: description text and HTML, structured salary, employment types, benefits, geocoded address, applicant count, original ATS link |
POST /api/v1/indeed/company | 2 | Employer profile: description, industry, HQ, size, revenue, CEO approval, per-category ratings, reported salaries, open roles, locations |
POST /api/v1/indeed/company/reviews | 2 | Employee reviews with per-category ratings, pros/cons, reviewer job title and location, plus aggregated sentiment and topic/location/title breakdowns |
/indeed/search with query, location, or both. A location-only search is valid - it returns every posting in that metro./indeed/job with job_id - the 16-hex job key, or any indeed.com URL carrying jk= (/viewjob, /rc/clk, /pagead/clk)./indeed/company with the indeed.com/cmp/<slug> slug or a full profile URL. Slugs are untidy - Tata-Consultancy-Services-(tcs) is a real one, so copy it rather than constructing it./indeed/company/reviews with the same company value.Indeed does not reject a filter value it does not recognise - it ignores it and serves the unfiltered set under a 200, which you still pay for.
radius is one of 0, 5, 10, 15, 25, 35, 50, 100 miles. Ask for 7 miles and Indeed bills you for a search covering fifty. Indeed's own default is 50.max_age_days is one of 1, 3, 7, 14.min_salary filters on Indeed's own ESTIMATE for the role, not on a posted figure. Postings that publish no salary at all still match. Never tell the user a result set is "jobs paying at least $X" - it is "roles Indeed estimates at $X or more"./search takes a 1-based page that steps in Indeed's ten-result offsets (page 2 starts at result 10). Read count for what the page actually returned, total_results for Indeed's headline count, and unique_results for its de-duplicated figure./company/reviews takes a 1-based page at 20 reviews per page./job and /company return a single object and take no paging parameter./search)| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | one of | Keyword (1-500 chars) |
location | string | one of | City+state, postal code, state, country or Remote (1-200 chars). Usable without a query |
page | integer | -- | 1-based, ten-result offsets |
radius | integer | 50 | Miles. Only 0, 5, 10, 15, 25, 35, 50, 100 |
max_age_days | integer | -- | Only 1, 3, 7, 14 |
job_type | string | -- | full_time, part_time, contract, temporary, internship |
min_salary | number | -- | Filters on Indeed's estimate for the role, not a posted figure |
remote | boolean | -- | Remote postings only |
query or location is required.
/job)| Parameter | Type | Default | Description |
|---|---|---|---|
job_id | string | required | 16-hex job key, or any indeed.com URL carrying jk= |
/company) and company reviews (/company/reviews)| Parameter | Type | Default | Description |
|---|---|---|---|
company | string | required | indeed.com/cmp/<slug> slug or full profile URL (1-200 chars) |
page | integer | -- | (reviews only) 1-based, 20 reviews per page |
import os, requests
BASE = "https://api.scavio.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}"}
# 1. Keyword + location, inside a radius Indeed actually honours
found = requests.post(f"{BASE}/api/v1/indeed/search", headers=HEADERS,
json={"query": "data engineer", "location": "Austin, TX",
"radius": 25, "max_age_days": 7, "job_type": "full_time"}).json()
job_key = found["data"]["jobs"][0]["job_key"]
# 2. One posting in full, including the original ATS link
job = requests.post(f"{BASE}/api/v1/indeed/job", headers=HEADERS,
json={"job_id": job_key}).json()
print(job["data"]["title"], job["data"]["original_job_url"])
# 3. Everything hiring in a metro - no query at all
metro = requests.post(f"{BASE}/api/v1/indeed/search", headers=HEADERS,
json={"location": "Austin, TX", "remote": False}).json()
# 4. The employer, then its reviews (copy the slug, do not build it)
company = requests.post(f"{BASE}/api/v1/indeed/company", headers=HEADERS,
json={"company": job["data"]["company_slug"]}).json()
reviews = requests.post(f"{BASE}/api/v1/indeed/company/reviews", headers=HEADERS,
json={"company": job["data"]["company_slug"], "page": 1}).json()
Every response uses the envelope { data, response_time, credits_used, credits_remaining }. Key data fields:
query, location, url, page, total_results, unique_results, radius, count, jobs[], related_queries.
A job row: pos, job_key, title, url, company, company_url, company_reviews_url, company_rating, company_review_count, company_logo, location, city, state, postal_code, country, remote, location_count, salary_text, salary_min, salary_max, salary_period, salary_currency, salary_source, job_types, shifts, schedules, benefits, remote_types, attributes, posted_at, posted_relative, is_new, urgently_hiring, hiring_event, indeed_apply, third_party_apply, expired, snippet, sponsored, featured_employer, top_rated_employer, apply_count.job_key, title, url, description, description_html, company, company_url, company_slug, company_reviews_url, company_rating, company_review_count, location, street_address, latitude, longitude, city, state, postal_code, country, remote, salary_text, salary_min, salary_max, salary_currency, salary_period, salary_source, job_types, employment_types, benefits, highlighted_attributes, posted_at, valid_through, posted_relative, applicant_count, urgently_hiring, indeed_apply, expired, source, original_job_url, phone, related_links.company, name, url, logo, website, description, industry, sectors, headquarters, founded, employee_range, revenue, ceo_name, ceo_approval_percent, rating, review_count, reviews_url, rating_categories, ratings_by_year, happiness_score, happiness_ratings, total_jobs, jobs_url, job_locations, jobs, job_titles, locations, salaries, reviews, interview_difficulty, interview_duration, faqs, questions, similar_companies.company, name, url, page, count, total_results, total_ratings, rating, rating_categories, pros, cons, topics, locations, job_titles, reviews[] (review_id, title, text, pros, cons, rating, rating_categories, job_title, location, country, current_employee, submitted_on, helpful_count, unhelpful_count, url).{
"data": {
"query": "data engineer",
"location": "Austin, TX",
"page": 1,
"radius": 25,
"total_results": 1237,
"unique_results": 1198,
"count": 15,
"jobs": [
{
"pos": 1,
"job_key": "a1b2c3d4e5f60718",
"title": "Senior Data Engineer",
"company": "Example Corp",
"company_rating": 3.8,
"location": "Austin, TX",
"salary_min": 135000,
"salary_max": 165000,
"salary_period": "YEARLY",
"salary_source": "ESTIMATED",
"job_types": ["Full-time"],
"posted_relative": "3 days ago",
"url": "https://www.indeed.com/viewjob?jk=a1b2c3d4e5f60718"
}
]
},
"credits_used": 2,
"credits_remaining": 998
}
radius or max_age_days outside the closed sets. Round the user's number to the nearest allowed value and tell them you rounded; the alternative is a billed search that quietly ignored the filter.min_salary is in play, describe results as roles Indeed estimates at that level. Check salary_source on each row before quoting a figure as the employer's./job is a per-posting call. Do not loop it over a whole search page - search already returns title, employer, location, salary range, job type and posting age.company_slug or a profile URL. Hand-built slugs 404, and that 404 is billed.url (or original_job_url) so the user can apply through a real link.400 means an invalid or missing parameter - neither query nor location, or a radius / max_age_days / job_type outside its set. Fix and retry.401 means the API key is invalid or missing. Check SCAVIO_API_KEY.404 means the job key or company slug does not exist. It is a real billed 404 - re-derive the id from a search result rather than retrying it.429 means rate or usage limit exceeded. Wait before retrying. See https://scavio.dev/docs/rate-limits.502 / 503 mean upstream is temporarily unavailable - wait a few seconds and retry, up to a few times.radius, drop max_age_days, or remove min_salary.SCAVIO_API_KEY is not set, prompt the user to export it before continuing.pip install scavio
from scavio import ScavioClient
client = ScavioClient() # reads SCAVIO_API_KEY
found = client.indeed.search(query="data engineer", location="Austin, TX", radius=25)
job = client.indeed.job(found["data"]["jobs"][0]["job_key"])
company = client.indeed.company(job["data"]["company_slug"])
reviews = client.indeed.company_reviews(job["data"]["company_slug"], page=1)
npm install scavio
import { Scavio } from "scavio";
const client = new Scavio(); // reads SCAVIO_API_KEY
const found = await client.indeed.search({ query: "data engineer", location: "Austin, TX", radius: 25 });
const reviews = await client.indeed.companyReviews({ company: "example-corp", page: 1 });