{"version":1,"resources":["https://stablejobs.dev/api/coresignal/job-search","https://stablejobs.dev/api/coresignal/job-collect"],"mppResources":["https://stablejobs.dev/api/coresignal/job-search","https://stablejobs.dev/api/coresignal/job-collect"],"description":"Job search APIs powered by Coresignal, protected by x402 micropayments.","instructions":"# StableJobs API\n> Job search via Coresignal. x402/MPP micropayments (USDC on Base/Solana/Tempo).\n\n## Base URL\nhttps://stablejobs.dev\n\n## Happy path (always follow this order)\n1. POST /api/coresignal/job-search — ALWAYS call this first with filters.\n   Returns up to 20 preview records per page (id, title, location, company_name, created, _score).\n   Check pagination.has_more; if true, repeat the SAME filters with pagination.next_page ($0.10/page).\n   Use preview fields to decide which postings matter. Charged even when jobs is empty. No description/salary/URLs here.\n   When jobs are returned, response includes next.collect — use it for step 2 after picking interesting ids.\n2. POST next.collect (or /api/coresignal/job-collect) — ONLY after job-search, with ids from step 1.\n   Pass { \"ids\": [122854839] } (1–10 ids). $0.20 per id quoted upfront. Returns full records\n   (description, url, salary, seniority, employment_type, etc.).\n\nNever call job-collect without ids from a prior job-search response in the same task.\nDo not guess or invent job ids.\n\n## When to collect\n- Need description, apply URL, salary, or seniority → collect those ids.\n- Only need titles/companies/locations to rank or shortlist → stop after job-search.\n\n## Endpoints\n- POST /api/coresignal/job-search $0.10/page — preview search (call first)\n- POST /api/coresignal/job-collect $0.20/id — full record collect (call second, only for ids you need)\n\n## Pagination (job-search only)\n- page 1–5 (default 1), 20 preview jobs per page, $0.10 per page request.\n- Check pagination.has_more; if true, repeat the SAME filters with pagination.next_page.\n- pagination.total_results is total matches; preview caps at 100 jobs (5 pages).\n- offset/limit/per_page/size are ignored — use page only.\n\n## Job Search Request\nCanonical filters: title, keyword_description, employment_type, location, company_id, company_name, company_domain, company_exact_website, company_professional_network_url, deleted, application_active, country, industry, created_at_gte, created_at_lte, last_updated_gte, last_updated_lte.\n\nAliases are accepted and echoed in normalized: job_title -> title, keywords -> keyword_description, company -> company_name. employment_type values are normalized where possible: full_time/fulltime/Full time -> Full-time; part_time -> Part-time; contract -> Contract; intern -> Internship.\n\nDefaults (schema): application_active=true, deleted=false, last_updated_gte=rolling 6 months ago (Coresignal index recency — not the same as LinkedIn post date). Pass application_active:false or deleted:true for historical postings; pass an earlier last_updated_gte (e.g. \"2020-01-01\") for older listings.\n\nUnsupported meaningful filters such as remote, salary, salary_min, salary_max, seniority, and experience_level return a free 400 before payment.\n\nDate filters (created_at_*, last_updated_*) use Coresignal format YYYY-MM-DD HH:mm:ss. Date-only strings such as 2026-05-25 are auto-normalized (gte -> 00:00:00, lte -> 23:59:59). last_updated_* reflects when Coresignal last refreshed the record; preview created is first-scrape date.\n\n## Location (important)\n`location` is matched token-by-token via Elasticsearch — your terms must appear in the stored value, but matching is not literal. Always set `country` alongside `location`.\n\n**US:** stored as \"City, ST\" (2-letter state abbrev) or metro labels (\"San Francisco Bay Area\"). Boroughs work when stored (\"Manhattan, NY\"). Use \"Manhattan, NY\" not \"Manhattan, New York\"; \"San Francisco\" not \"SF\"/\"NYC\". City alone is often broader than metro labels.\n\n**International:** stored as \"City, Region, Country\" with full region names — not abbreviations. \"London\" or \"London, England\" not \"London, UK\". \"Toronto, Ontario\" not \"Toronto, ON\". \"Sydney\" not \"Sydney, NSW\". \"Dubai\" not \"Dubai, UAE\". Use current local spellings (\"Bengaluru\" not \"Bangalore\"). City alone is usually the safest first try.\n\nCommon failures (all regions):\n- Country/region abbrev after comma (\"London, UK\", \"Toronto, ON\", \"Dubai, UAE\") -> 0\n- City abbreviations (\"SF\", \"NYC\") -> 0\n- Assumed metro labels that don't exist in data (\"Greater Los Angeles Area\") -> 0\n\nIf location returns 0 jobs ($0.10 per retry):\n1. Drop region suffix or switch format (US: full state -> \"City, ST\"; intl: abbrev -> full region or city alone)\n2. Abbreviation -> full city name\n3. Metro label -> city name (often more results)\n4. Drop `location`; use `country` + `title`\n\nExample two-step compose:\nStep 1: { \"job_title\": \"Software Engineer\", \"country\": \"United States\", \"employment_type\": \"full_time\" } — last_updated_gte defaults to 6 months ago; see normalized echo.\n→ jobs[0].id = 122854839, next.collect = \"/api/coresignal/job-collect\", pagination.has_more may be true\nStep 1b (optional): same filters + { \"page\": 2 } for next 20 previews\nStep 2 (only if full description/URL needed): POST next.collect with { \"ids\": [122854839] }\n"}