Partner API

By request

Read access to the Schemes.sg catalogue, so partner organisations can surface the right scheme inside their own product.

Request accesshttps://asia-southeast1-schemessg.cloudfunctions.net/partner_api

Getting access

This is not a self-serve API. We work with a small number of partner organisations, and each key is issued by hand so we know who is using the data and can reach them if something changes.

Before a key is issued, we confirm our terms of use and privacy policy cover sharing scheme data with your organisation. You will get a sandbox key first, pointed at our development data, so you can build and test before touching anything live.

Quick start

One request, to check your key works and see the shape of a scheme.

Request
cURL
curl "https://asia-southeast1-schemessg.cloudfunctions.net/partner_api/v1/schemes?limit=1" \
  -H "X-API-Key: $SCHEMES_API_KEY"

Authentication

Every request carries your key in a header. There is no token exchange, no OAuth dance, and no expiry. Send it as X-API-Key.

Keep the key on your server. It is not safe in a browser, a mobile app, or anything a user can read, because it grants your whole quota to whoever holds it.
Header
HTTP
X-API-Key: sk_schemes_xxxxxxxxxxxxxxxxxxxxxxxx

Base URL and versioning

The version is a path segment, so a future version can ship alongside this one without changing any URL you have already built against.

There is one version today. A request with no version, or an unknown one, returns 404 rather than quietly falling back to v1.

base
HTTP
https://asia-southeast1-schemessg.cloudfunctions.net/partner_api/v1

Operations

Three read operations. One key covers all of them.

List schemes

GET/v1/schemes

Page through active schemes. Filters combine (a scheme must match each one), and repeating a filter matches any of its values: ?category=Education&category=Financial%20Assistance&area=BEDOK. Results exclude retired and inactive schemes, except with updated_since.

Query parameters

categorystringoptional
One of: Financial Assistance, Family & Children, Health & Wellbeing, Housing & Food, Education, Employment & Training, Seniors & Caregiving, Disability & Transport, Legal & Safety, Community Support. Case-insensitive, but the name must match exactly — a slug such as "financial-assistance" is rejected. Repeat to match any of several categories. For example Financial Assistance
agencystringoptional
Filter by the agency that runs the scheme. Case, punctuation and a bracketed abbreviation are ignored, so "Agency for Integrated Care" also matches "Agency for Integrated Care (AIC)". Repeat to match any of several agencies. For example Agency for Integrated Care
areastringoptional
Filter by planning area, case-insensitive. Repeat to match any of several areas. For example BEDOK
updated_sincestringoptional
ISO 8601 date or time; without an offset it is read as UTC. Returns only schemes whose updated_at is at or after it, including retired and inactive ones with their status, so an incremental sync can drop them. Combines with the other filters. For example 2026-10-01T00:00:00Z
limitintegeroptional
Schemes per page. Defaults to 10, capped at 50. For example 20
cursorstringoptional
Opaque cursor from a previous response's next_cursor. Pass it back exactly as received; a modified or truncated cursor returns 400 rather than silently restarting from the first page. Omit for the first page.
Request
cURL
curl "https://asia-southeast1-schemessg.cloudfunctions.net/partner_api/v1/schemes?category=Financial%20Assistance&limit=2" \
  -H "X-API-Key: $SCHEMES_API_KEY"
Response
JSON
{
  "data": [
    {
      "scheme_id": "0kZ2mQ1xVbN4pR7t",
      "scheme": "ComCare Short-to-Medium-Term Assistance",
      "description": "Monthly cash assistance for households ...",
      "summary": "Monthly cash help while you get back on your feet.",
      "eligibility": "Singapore Citizen or PR, little or no income ...",
      "who_is_it_for": ["Low-income households"],
      "what_it_gives": ["Monthly cash assistance", "Household support"],
      "scheme_type": ["Financial Assistance"],
      "agency": "Ministry Of Social And Family Development",
      "link": "https://www.msf.gov.sg/...",
      "address": "512 Thomson Road",
      "phone": "1800 222 0000",
      "email": "enquiries@msf.gov.sg",
      "service_area": "Nationwide",
      "planning_area": ["BEDOK", "TAMPINES"],
      "image": "https://.../comcare.png",
      "status": "active",
      "updated_at": "2026-10-12T09:06:27.534414+00:00"
    }
  ],
  "next_cursor": "eyJkb2NfaWQiOiIwa1oy...",
  "has_more": true,
  "total_count": 214
}

Retrieve a scheme

GET/v1/schemes/{scheme_id}

Fetch one scheme by id. Retired and inactive schemes return 404; a scheme retired by merging into another carries the new id.

Path parameters

scheme_idstringrequired
The scheme's id, as returned in scheme_id by list or search. Ids are stable and the same in the development and production APIs, so store them as your key. For example 0kZ2mQ1xVbN4pR7t
Request
cURL
curl "https://asia-southeast1-schemessg.cloudfunctions.net/partner_api/v1/schemes/0kZ2mQ1xVbN4pR7t" \
  -H "X-API-Key: $SCHEMES_API_KEY"
Response
JSON
{
  "data": {
    "scheme_id": "0kZ2mQ1xVbN4pR7t",
    "scheme": "ComCare Short-to-Medium-Term Assistance",
    "description": "Monthly cash assistance for households ...",
    "eligibility": "Singapore Citizen or PR, little or no income ...",
    "agency": "Ministry Of Social And Family Development",
    "link": "https://www.msf.gov.sg/...",
    "status": "active",
    "updated_at": "2026-10-12T09:06:27.534414+00:00"
  }
}

Search schemes

POST/v1/schemes/search

Rank schemes against a natural-language description of someone's situation. Same retrieval the Schemes.sg assistant uses, without the conversational layer.

Body parameters

querystringrequired
Plain-language description of the situation, not keywords. Longer is better. For example retired, living alone, struggling with medical bills
limitintegeroptional
Results per page. Defaults to 20, capped at 50. For example 10
cursorstringoptional
Opaque cursor from a previous response's next_cursor. Pass it back exactly as received; a modified or truncated cursor returns 400 rather than silently restarting from the first page. Omit for the first page.
Request
cURL
curl -X POST "https://asia-southeast1-schemessg.cloudfunctions.net/partner_api/v1/schemes/search" \
  -H "X-API-Key: $SCHEMES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "retired, living alone, struggling with medical bills",
    "limit": 10
  }'
Response
JSON
{
  "data": [
    {
      "scheme_id": "7bQ4rT2yWcM9nL3v",
      "scheme": "MediFund",
      "summary": "Help with hospital bills you still cannot afford.",
      "agency": "Ministry Of Health",
      "scheme_type": ["Health & Wellbeing"],
      "status": "active"
    }
  ],
  "next_cursor": null,
  "has_more": false,
  "total_count": 8
}

Scheme fields

Every scheme we return carries exactly these fields, whether it came from list, retrieve, or search. A field with no value is null rather than absent, so the set of keys never changes. A few fields hold either a single string or an array of strings, depending on the scheme — check the type column and handle both.

Scheme fields
FieldType
scheme_idstring
schemestring
descriptionstring
summarystring
eligibilitystring
who_is_it_forstring[]
what_it_givesstring[]
scheme_typestring[]
agencystring
linkstring
addressstring | string[]
phonestring | string[]
emailstring | string[]
service_areastring
planning_areastring | string[]
imagestring
statusstring
updated_atstring | null

We hold other fields internally, for review workflow and ranking. Those are deliberately not part of this contract, so our internal changes cannot break your integration.

Errors

Every failure returns the same envelope, so you can branch on error.code and never parse a message.

Errors
StatusCodeWhat it means
400invalid_requestThe parameters are wrong. The message names which one.
401missing_keyNo X-API-Key header was sent.
401invalid_keyThe key is not one we issued.
403revoked_keyThe key was valid and has been turned off. Talk to us.
404not_foundNo such scheme, or it is no longer published.
404scheme_retiredThe scheme was retired and merged. The body carries merged_into.
404unsupported_versionThe version segment is missing or unknown.
405method_not_allowedRight path, wrong HTTP method.
429rate_limitedYour per-minute budget is spent. Retry-After says when.
500internal_errorSomething broke on our side. Safe to retry.
Response
JSON
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit of 600 requests/minute exceeded"
  }
}

Rate limits

Each partner gets a per-minute budget, shared across all three operations: spending it on list also spends it for search. Every response tells you where you stand, so you never have to discover the limit by hitting it.

X-RateLimit-Limit
Your budget, in requests per minute.
X-RateLimit-Remaining
What is left in the current minute.
Retry-After
Seconds to wait. Only sent with a 429.

Keeping in sync

Scheme ids are stable and the same in the development and production APIs, so use scheme_id as your key. Every scheme carries updated_at: the latest of its last scrape, its last status change and its last bulk re-tag. Do one full sync, then ask only for what changed with updated_since. Those results also include schemes that were retired or went inactive, with their status, so you know what to drop. A bulk re-tagging run moves updated_at too, so updated_since picks up the new tags.

cURL
curl "https://asia-southeast1-schemessg.cloudfunctions.net/partner_api/v1/schemes?updated_since=2026-10-01T00:00:00Z&limit=50" \
  -H "X-API-Key: $SCHEMES_API_KEY"

When a scheme goes away

Schemes close, and some are folded into a replacement. If you store scheme ids, handle this case: a retired scheme that was merged returns 404 with the id that replaced it, so you can follow the change instead of silently dropping the record.

404
JSON
{
  "error": {
    "code": "scheme_retired",
    "message": "Scheme was retired and merged into another scheme",
    "merged_into": "9fH1jK8sZxC5vB2n"
  }
}