Search Competitor Training Providers in the SkillsFuture Course Directory
Purpose
Search the SkillsFuture for Business Course Directory (skillsfuture.gobusiness.gov.sg/course-directory/search) for training providers and the SSG-supported courses they run, returning a structured, per-provider research list: course themes/titles, funding schemes (SFEC / Industry-Supported / Absentee Payroll), indicative pre- and post-subsidy pricing, ratings, course format/duration, and the course-detail URL. Read-only — never registers, logs in, or submits anything. Every course in this directory is SkillsFuture Singapore (SSG)-supported and eligible for SFEC + Absentee Payroll funding, which makes it the authoritative source for mapping grant-funded training competitors targeting Singapore SMEs.
When to Use
- Building a competitor / market-mapping list of training providers in a capability area (e.g. digital marketing, digital transformation, data analytics, sustainability/ESG) for Singapore SMEs.
- Pulling every SSG-funded course a named provider runs, with funding tags and subsidised pricing.
- Filtering the directory to grant-relevant cohorts (e.g.
keywords=Digital Economy, or theSkillsFuture Enterprise Credit (SFEC)facet) and aggregating by provider. - Anywhere you'd otherwise hand-scrape the directory — the search results are server-rendered into the page HTML, so a plain HTTP fetch returns the full structured dataset without driving a browser.
Workflow
The page is a Next.js (App Router) app, but the search results are server-side-rendered into the initial HTML as a React Server Component (RSC) "Flight" payload (self.__next_f.push([...]) script chunks). That payload carries every field the cards display — provider, title, rating, funding tags, fees at each subsidy tier, course runs, and the course-detail link. Lead with a plain HTTP GET of a deep-link URL and parse that payload. No login, cookies, JS execution, residential proxy, or stealth required (confirmed: a bare browse cloud fetch of the deep-link returns HTTP 200 with all 10/25/50/100 cards plus the total result count). The browser path (below) is a fallback, mainly useful for discovering exact filter-facet values.
-
Build the deep-link URL. Search and paging are entirely URL-driven — you do not need to type into the search box:
https://skillsfuture.gobusiness.gov.sg/course-directory/search ?search_query=digital+marketing # spaces as '+' (or %20) &page_size=100 # 10 | 25 | 50 | 100 (100 = fewest round-trips) &page=0 # ZERO-indexed &sort_by=relevance # see Gotchas for other values &keywords=Digital+Economy # optional facet; exact display string, '+'-encoded -
Fetch it.
browse cloud fetch "<url>"(add--proxiesonly if you ever see a regional block — not needed today). Readcontent(the HTML, ~580 KB for page_size=10). -
Read the total result count. It is rendered as the
"children":["87"," ",{...,"children":"results"}]node near the top of the payload — match\"children\":\["(\d+)\"," "to get the integer (87fordigital marketing+Digital Economy). Use it to decide how many pages to fetch:ceil(total / page_size), incrementingpagefrom0. -
Extract each course object. Each card is an RSC component (
$L63) whose fields appear in order in the payload. The values are JSON-escaped (\"). Per course you get:subtitle→ training provider name (e.g.@ASK TRAINING PTE. LTD.)title→ course titleisIndustrySupported→ boolean (sector-body endorsement)rating(number or$undefined) +ratingDetail(review count, e.g."2,256")tags→ array, e.g.["Digital Economy","SkillsFuture Enterprise Credit (SFEC)"](funding/economy facets)courseDuration,modeOfTraining,language,courseHours,courseRuns[](each with start/end dates, intake size, learning style, registration window)label/labelDataValue→"Full course fee"/"S$300.00"(full pre-subsidy fee, exclusive of GST)secondLabel/secondLabelDataValue→"After subsidy"/"From S$90.00"(after baseline SSG subsidy)supportingDataLabel/supportingDataValue→"After SFEC"/"From S$9.00"(after SFEC tops up 90% of nett)linkUrl→/course-directory/courses/{TGS-id}(canonical course-detail URL)
A robust regex anchor is
\"isIndustrySupported\":(true|false),\"subtitle\":\"...\",\"title\":\"...\", then scan the next ~3.5 KB of the payload for the fee/linkUrl/tagsfields belonging to that card. -
Aggregate by provider. Group courses by
subtitle. Per provider record: course count, sample course titles (themes), the union of fundingtags, the full-fee and after-subsidy price range, and whether any course isisIndustrySupported. This is the competitor row a commercial/product team consumes. -
(Optional) enrich from the course-detail page. GET
https://skillsfuture.gobusiness.gov.sg/course-directory/courses/{TGS-id}(also SSR;browse cloud fetchorbrowse get markdown body). Adds: provider website URL, the recognising industry body (e.g. "recognized and supported by Singapore Computer Society (SCS)"), EIS supporting period, contact person (name / phone / email), full course overview, learning outcomes, and entry requirements. -
(Optional) enumerate the full provider universe. The left-panel Training Provider facet lists every provider in the directory alphabetically (~440 entities, also in the SSR payload). Read it to build a complete provider roster, then drill in per provider with
search_queryempty + the provider checkbox.
Browser fallback
Use only when you need to discover exact filter-facet values, or if the RSC payload format changes. Drive a real session (a bare session is fine — no stealth needed):
browse open "<deep-link url>" --remotethenbrowse wait timeout 4000(results render client-side after hydration).browse get markdown body— each result renders as a markdown link containing provider, title, rating, funding tag, duration, full fee, and "After subsidy" price; the/course-directory/courses/TGS-…href is the course id.- To learn a filter's exact URL value:
browse snapshot, click the facet's checkbox by ref,browse wait timeout 1500, then readbrowse eval "window.location.href"— the applied value is appended to the query string (e.g. clicking Digital Economy appends&keywords=Digital+Economy).
Site-Specific Gotchas
- No public JSON API. The upstream SSG data API is called server-side only; the client never sees clean JSON. The closest thing to an API is the RSC Flight payload embedded in the SSR HTML — parse that. Don't hunt for an
/api/...XHR; there isn't one (the only client fetches are Next.js RSC navigations with an opaqueRSC: 1body, GA/analytics, and the VICA chatbot). pageis zero-indexed.page=0is the first page. Off-by-one here silently skips the first cohort of results.search_queryuses+(or%20) for spaces; the param name matters.search_query=digital+marketingworks (550 results); guessingq=/searchValue=is ignored and returns the full unfiltered directory (20,936 results) — a silent failure that looks like success. Always sanity-check the total count against the keyword.- The
/course-directory/search/{keyword}PATH 404s. Search is a query-string, not a path segment.…/search/digital%20marketingreturns "Page not found". keywordsfacet takes the exact display string. Valid values (each+-encoded):Care Economy,Digital Economy,Green Economy,SkillsFuture Enterprise Credit (SFEC),Critical Core Skills. These are the SSG "economy/credit" facets —Digital Economyis the right cut for MarTech / digital-transformation competitor mapping.areas_of_trainingis a real param but is NOT free-text. The param name is confirmed (areas_of_training=returns0 resultswhen the value doesn't match the site's internal taxonomy, vs. unknown params which are ignored and return everything). The Area-of-Training options render via a virtualised list that does not reliably appear in the a11y snapshot, so don't guess the value — apply the checkbox in the browser fallback and copy the exact value the URL receives. Area labels include "Advertising, Sales & Marketing", "Information and Communications", "Marine & Port Services", "Transportation and Storage", "Wholesale and Retail Trade" (relevant ICP sectors).sort_by=relevanceis confirmed. Other UI sort options (Price low→high / high→low, Most Viewed, Ratings high→low / low→high, Alphabetical A–Z / Z–A) map to othersort_byvalues — capture the exact token via the browser fallback (read the URL after picking the dropdown) rather than guessing.- Fees are exclusive of GST and the "After subsidy" / "After SFEC" figures are "From" floors (best-case, depends on employee profile — SME vs non-SME, SC/PR/LTVP+, age). Treat post-subsidy prices as indicative minimums, not quotes. SMEs get up to 90% baseline subsidy; SFEC defrays a further 90% of the nett.
isIndustrySupported≠ a funding scheme. It means a sector body (e.g. Singapore Computer Society) endorses the course; funding eligibility is conveyed by thetags(SFEC) and the universal SSG-support of every directory course. A card can beisIndustrySupported:falseyet still SFEC-funded.ratingcan be$undefinedin the payload (rendered as "No rating") — handle the non-numeric sentinel when parsing.- Result count is capped per search, not per provider. A keyword search returns courses; one provider can own many cards. Aggregate client-side. To get the complete provider list independent of any keyword, read the Training Provider facet (≈440 entities) rather than paginating a search.
- No anti-bot wall observed. Pre-run probe reported no anti-bots; both the bare HTTP fetch and a non-stealth browser session returned full content. CloudFront fronts the site but did not challenge.
--verified/--proxiesare unnecessary. - Scheduled maintenance banner. The site shows a maintenance notice; certain SkillsFuture-for-Business services can be briefly unavailable per the published schedule — retry rather than treating a transient failure as a block.
Expected Output
A structured, per-provider research list aggregated from the parsed course cards.
{
"source": "skillsfuture.gobusiness.gov.sg/course-directory/search",
"query": {
"search_query": "digital marketing",
"keywords": "Digital Economy",
"sort_by": "relevance",
"page_size": 100,
"page": 0
},
"total_results": 87,
"providers": [
{
"provider": "@ASK TRAINING PTE. LTD.",
"course_count_on_page": 2,
"industry_supported": false,
"course_themes": [
"WSQ Digital Marketing Essentials - (Classroom and Async e-learning)",
"WSQ Digital Marketing Analytics (Google Analytics)"
],
"funding_schemes": ["SkillsFuture Enterprise Credit (SFEC)", "SSG baseline subsidy", "Absentee Payroll"],
"facets": ["Digital Economy"],
"full_fee_sgd_range": ["S$300.00", "S$900.00"],
"after_subsidy_from_sgd_range": ["From S$90.00", "From S$270.00"],
"rating": 4.5,
"sample_course_url": "https://skillsfuture.gobusiness.gov.sg/course-directory/courses/TGS-2023020687"
},
{
"provider": "NTUC LEARNINGHUB PTE. LTD.",
"course_count_on_page": 1,
"industry_supported": false,
"course_themes": ["Empowering Digital Marketers with Essential AI Tools (SF)"],
"funding_schemes": ["SkillsFuture Enterprise Credit (SFEC)", "SSG baseline subsidy", "Absentee Payroll"],
"facets": ["Digital Economy"],
"full_fee_sgd_range": ["S$1,800.00"],
"after_subsidy_from_sgd_range": ["From S$540.00"],
"rating": 5.0,
"sample_course_url": "https://skillsfuture.gobusiness.gov.sg/course-directory/courses/TGS-2024046411"
}
]
}
Per-course shape (before aggregation), exactly as extracted from the RSC payload:
{
"provider": "SKILLS DEVELOPMENT ACADEMY PTE. LTD.",
"title": "Introduction to Digital Marketing",
"course_id": "TGS-2021010044",
"url": "https://skillsfuture.gobusiness.gov.sg/course-directory/courses/TGS-2021010044",
"is_industry_supported": false,
"rating": 5.0,
"rating_count": "3,686",
"tags": ["Digital Economy", "SkillsFuture Enterprise Credit (SFEC)"],
"duration": "2 - 3 Days",
"course_hours": 16,
"mode_of_training": ["Part Time"],
"language": ["English"],
"full_course_fee": "S$800.00",
"after_subsidy_from": "From S$240.00",
"after_sfec_from": "From S$24.00"
}
Notes:
funding_schemesis inferred: every directory course is SSG-supported (baseline subsidy + Absentee Payroll + SFEC-eligible); the SFECtagconfirms SFEC, and any explicit economy/credit facet is surfaced underfacets.ratingmay be absent ($undefined→ "No rating");after_*figures are best-case "From" floors, GST-exclusive.