This guide is for REST and MCP consumers. Start with discovery and shared query conventions, then use the resource sections for examples. Existing clients should read the 3.0 migration guide before upgrading. For local setup and validation, see the development guide.
The examples below use paths relative to https://api.nthusa.tw (local:
http://localhost:5000). See Swagger UI for
complete request and response schemas.
| Task | Read |
|---|---|
| Connect to REST or MCP | Discovery and MCP |
| Understand paths, filters, responses and headers | Query conventions |
| Upgrade an existing client | Version policy and 3.0 migration guide |
| Handle unavailable data or check readiness | Availability and readiness |
| Resource | Main endpoints | Details |
|---|---|---|
| Courses | /courses, /courses/semesters, /courses/{semester} |
Semester selection, GET filters, POST query DSL, MCP |
| Directory | /directory |
Department and people matching |
| Announcements | /announcements, /announcements/sources |
Source and content filters |
| Newsletters | /newsletters, /newsletters/sources |
Source and article filters |
| Locations | /locations |
Name matching |
| Buses | /buses/routes, /buses/stops, /buses/schedule |
Routes and stops, schedule filters, MCP |
| Dining | /dining |
Name and schedule filters |
| Library and energy | /libraries/spaces, /libraries/lost-and-found, /libraries/rss/{rss_type}, /energy/electricity |
Live queries, RSS |
| Calendars | /calendars, /calendars/{calendar_id}/events |
Calendar selection, event filters |
GET / returns a small discovery response without reading datasets or making
upstream requests. The name and version match the FastAPI application metadata.
{
"name": "NTHU Data API",
"version": "3.0.0",
"docs": "/docs",
"openapi": "/openapi.json",
"mcp": "/mcp"
}The endpoint is hidden from OpenAPI and Swagger UI. Use /ping for dataset readiness checks;
the metadata endpoint does not indicate dataset readiness.
The MCP endpoint is https://api.nthusa.tw/mcp (local: http://localhost:5000/mcp).
Each tool explicitly declares a stable English name for calls and a Traditional
Chinese title for display. Clients that support tool titles can display these
labels; other clients may continue to display the English names.
| Tool name | Display title |
|---|---|
search_directory |
搜尋清華通訊錄 |
search_locations |
搜尋校園地點 |
get_bus_schedule |
查詢公車時刻表 |
search_courses |
搜尋課程列表 |
get_announcements |
搜尋校園公告 |
find_dining |
搜尋餐廳列表 |
get_library_info |
查詢圖書館資訊 |
get_newsletters |
搜尋電子報列表 |
get_energy_usage |
查詢校園即時用電 |
The service runs one live version, without versioned URL prefixes or per-request version selection. A release tag identifies deployed source, not an independently supported API. Minor and patch releases preserve public REST/MCP contracts; prefer adding optional filters or metadata over renaming endpoints. Clients should tolerate extra response fields and discover dataset-backed values instead of treating examples as exhaustive lists.
Deprecation is advance notice, not permission to remove an interface in a minor release. Future deprecations must document a replacement and migration differences, appear in OpenAPI/tool documentation as applicable, and be announced before removal in a major release. Necessary security or correctness fixes may be exceptions and must explicitly describe their impact. Removing an endpoint is still a breaking change even after deprecation; there is no promise that old clients keep working after a major upgrade.
3.0.0 is being prepared and includes breaking changes. Endpoint replacements, course field mappings and MCP client changes are collected in the 3.0 migration guide. The unreleased contracts may change before the first stable tag; after publication, the compatibility policy above applies.
Use root GET endpoints without a trailing slash: /announcements, /calendars,
/dining, /locations, and /newsletters. Only slashless operations are
registered and documented. Trailing-slash requests use the framework's automatic
307 redirect, not separate compatibility endpoints. GET /courses and
GET /directory follow the same convention.
Matching rules are resource-specific, not interchangeable. In particular:
| Query | Matching |
|---|---|
| Course REST GET | Regex for text; exact native values for numeric/boolean fields |
| Course REST POST | Typed exact matching by default; opt-in text regex and nested AND/OR |
| Course MCP | Case-sensitive literal substrings, not regex |
| Dining | Fuzzy name matching by default; schedule notes are not live availability |
The resource sections below document defaults, empty-filter behavior and limits. Apply all filters before result limits. Invalid course regex returns 422 even when the dataset is empty.
The exported REST/MCP app applies CORS and returns X-Process-Time. CORS
exposes X-Total-Count, X-Data-Commit-Hash, and X-Process-Time to browser
clients from configured origins. Unexpected failures before a response starts,
including dependency and response validation errors, retain CORS and timing
headers.
Nullable metadata in announcements, newsletters, department contacts, dining
images, and library feeds/calendars may be omitted by publishers. These fields
default to null in REST responses; supplied values still undergo validation.
Required identifiers, titles not declared nullable, event boundaries, and dataset
structure remain required. Course GET search retains the same flat optional query
parameters, with validation errors documented in OpenAPI.
Snapshot-backed endpoints document HTTP 503 with {"detail": "..."} when no
usable snapshot exists. Manual 400/404 errors use the same string-detail schema.
Course 422 responses allow either FastAPI's validation-error array in detail
or a string for invalid GET regular expressions. OpenAPI success responses declare
X-Data-Commit-Hash where supplied (omitted for unknown versions), and
X-Total-Count on course responses and paginated calendar event lists.
The documentation groups these endpoints under Directory.
Use GET /directory to retrieve the directory, or GET /directory?query=...
to match department names, people names, or titles. The optional fuzzy boolean
defaults to true. Set fuzzy=false for case-sensitive full-string equality
against any of those fields, not substring or regular-expression matching.
For example, /directory?query=王小明&fuzzy=false matches that full person name.
An omitted or empty query returns the entire directory; no matches return an
empty array.
The response is always a list of the existing Department model. A department
name match returns that department in full. When only its people match, the
department metadata is retained and details.people contains only matching
people. Fuzzy results are sorted by match score, while nested people retain their
dataset order; exact results retain dataset order. Filtering does not modify the
cached dataset.
MCP search_directory(query, fuzzy=true) uses the same matching fields and modes
but returns separate departments and people arrays, capped at 5 departments
and 10 people after filtering and ranking. A department-name match does not
automatically include its people in the separate people array. It does not search
locations. Unlike REST, an empty MCP query returns no matches in either mode.
A valid empty result is {departments: [], people: []}; an unavailable
dataset is a tool error.
The tool loads only the directory dataset, independently of location searches.
The supported announcement endpoints are GET /announcements for announcement
content and GET /announcements/sources for source metadata without articles.
The sources response is a list[AnnouncementSource] with required title, link
(HTTP/HTTPS URL), language, and department fields, documented in OpenAPI.
Use the department fields in /announcements/sources to discover department
names; clients needing a unique department list can deduplicate those values.
GET /announcements
GET /announcements/sources
GET /announcements?department=教務處
GET /announcements?title=停電
GET /announcements?language=zh-tw
GET /announcements?url=academic.site.nthu.edu.tw
GET /announcements?url=academic.site.nthu.edu.tw/p/403-1007-1504-1.php
The optional url query parameter filters announcement sources by a substring of
their link, ignoring http:// and https:// in both the query and source link.
HTTP(S) schemes and hostnames match case-insensitively; paths, queries, and
fragments remain case-sensitive. It accepts a domain or a URL with a path,
combines with the other filters, and does not use fuzzy matching regardless of
the fuzzy setting. It does not filter individual article links. Existing filters
and the optional department filter on /announcements/sources are unchanged.
Use GET /libraries/spaces for the library space availability collection
(list[LibrarySpace]), GET /libraries/lost-and-found for library lost items, and
GET /energy/electricity for realtime campus electricity usage.
For failure status codes and MCP tool errors, see
live integration errors.
GET /libraries/rss/{rss_type} accepts news, eresources, exhibit, or
branches. Each response is an article array. Article link is nullable text,
not a single URI: publishers may supply multiple comma-separated URLs, and the
API preserves the complete text. Consumers must not assume that the value can be
opened as one URL.
Image url and link remain nullable HTTP(S) URLs. RSS image URL path spaces are
encoded as %20 before validation, without double-encoding existing escapes;
protocol-relative images use HTTPS. This normalization applies to the complete
installed RSS candidate and does not weaken URL validation for other datasets.
An invalid candidate retains the last-known-good snapshot; with no usable snapshot,
requests return 503. A missing feed returns 404 and an invalid rss_type returns 422.
| Endpoint | Dataset or purpose |
|---|---|
GET /courses/semesters |
Discover published semester codes |
GET /courses |
Latest courses with GET filters |
POST /courses/query |
Latest courses with a query body |
GET /courses/{semester} |
Selected semester with GET filters |
POST /courses/{semester}/query |
Selected semester with a query body |
GET /courses and POST /courses/query use the publisher's
courses/latest.json. They do not infer a semester from today's date or the
largest published semester code. Select a semester with GET /courses/{semester}
or POST /courses/{semester}/query. Every course operation returns an array
of the same normalized course objects.
GET /courses/semesters returns five-digit semester codes, newest first, from
the published courses/sources.json catalog:
["11510", "11420", "11410"]This is a shortened example, not the complete list. Discover available semesters
rather than hardcoding them. 11510 means academic year 115, first semester;
the path uses the complete publisher code without shortening it to 1151.
Malformed codes return 422; well-formed, unpublished semesters return 404. An unavailable catalog or course dataset with no usable snapshot returns 503. A stale/unverified catalog cannot establish that an absent semester is unpublished: that case returns 503 instead of 404. Known entries may still use usable snapshots. There is no fallback to another semester or to latest.
Course and semester-list responses include X-Total-Count and, when known,
X-Data-Commit-Hash for the specific file used. latest and a named semester can
have different contents and versions even when their semester codes agree.
GET without filters returns all courses; empty text filters apply no filter.
All supplied field filters and the optional type filter are combined with AND.
Text filters use regex; numbers and booleans use exact typed equality. Zero and
false are real filters, not missing values. Unknown query parameters are rejected
with 422.
GET /courses
GET /courses/semesters
GET /courses/11510
GET /courses/11510?lecturers.name_zh=林福仁
GET /courses/11420?lecturers.label=林福仁
GET /courses?chinese_title=服務
GET /courses?language_code=zh%7Cen
GET /courses/11510?credits=3&is_cancelled=false
GET /courses/11510?meetings.time_codes=M3
GET /courses?type=microcredits
GET /courses?type=xclass&lecturers.name_zh=林福仁
type accepts microcredits (credits with a fractional part, such as 0.5 or 1.5;
not integer credits or null) and xclass (case-sensitive X-Class in note).
Unknown or empty types return HTTP 422.
language_code is a text regex filter (for example, zh|en); published
course values remain limited to zh, en, or null.
Nested filters use leaf paths: lecturers.label, lecturers.name_zh,
lecturers.name_en, meetings.classroom, meetings.time, meetings.time_codes,
expertises.name, expertises.type, curriculum_requirements.target, and
curriculum_requirements.requirement. programs and parsing_issues match
individual string elements. Array predicates match any element. Two ANDed
predicates may match different objects in the same array; same-object grouping
is not supported. Missing teacher-union keys do not count as null values.
For nested AND/OR queries, send a condition object or a condition array to
POST /courses/query or POST /courses/11510/query:
[
{"row_field": "lecturers.name_zh", "matcher": "黃", "regex_match": true},
"or",
{"row_field": "lecturers.name_zh", "matcher": "孫", "regex_match": true}
]regex_match defaults to false (exact matching). matcher must have the
field's native type: {"row_field":"credits","matcher":3},
{"row_field":"is_cancelled","matcher":false}, or
{"row_field":"language_code","matcher":null}. Null only matches an explicitly
unknown nullable value, not a missing union key, empty array, empty string,
zero or false. GET does not have a special "null" query value.
Set regex_match to true only with a string matcher and a text field/leaf.
Wrong matcher types and regex applied to non-text fields return 422, including
on empty datasets. Numeric comparisons other than equality are not supported.
A condition object may also be wrapped in a single-element array. Every non-empty
array, including nested arrays, must have an odd number of elements alternating
between conditions (objects or arrays) and the lowercase operators "and" or
"or". Operators are evaluated left to right with no implicit AND precedence;
use nested arrays to group expressions. An empty array [] matches all courses,
including when used as a nested operand.
Queries allow at most 32 array levels (the outermost array is level 1) and 1,024 nodes across the whole tree. Each array, condition object and operator counts as one node; fields inside condition objects do not count separately. Malformed grammar, exceeded limits and invalid regex syntax return HTTP 422, even when the course dataset is empty.
All course objects expose 33 fields. Numeric and boolean metadata may be null; missing source information is not silently converted to zero, false or Chinese. Arrays remain arrays, including empty arrays. For old-to-new field mappings, see the course migration table; old field names are not aliases. Swagger UI defines the complete response schema.
credits is a nullable nonnegative number; capacity and reserved_for_freshmen
are nullable nonnegative integers; capacity_unlimited and is_cancelled are
nullable booleans. language_code is "zh", "en" or null.
lecturers, meetings, expertises, programs and curriculum_requirements
are arrays; prerequisite_text is a string.
object is the course audience/enrollment restriction, ge_type is the
general-education audience, and ge_category is its category.
Additional fields are semester_code; nullable text department_code,
course_number, department_name, college_name, course_level_code,
course_level, remote_teaching; nullable nonnegative integers enrolled_count,
enrollment_after_selection, enrollment_after_withdrawal; and string-array
parsing_issues. The three enrollment counts represent different source
measurements and must not be substituted for each other. A capacity of zero,
unlimited capacity and unknown capacity are distinct.
Teacher objects preserve their published shape. A historical {label} is not
guessed into Chinese and English names. Nested object values may be null;
meetings.time_codes may also be null when the publisher could not parse the
time text. Parser warnings are preserved, not repaired or used to reject otherwise
valid records. Required fields and invalid structures still fail snapshot
installation; an older usable snapshot is retained if available.
search_courses accepts keyword, teacher, course_id, limit and optional
semester. Filters use case-sensitive literal substrings and are combined with
AND. A keyword can match either the Chinese or English course title; teacher
matches label, Chinese or English lecturer names. Regex characters such as C++
and [AI] are treated literally. limit defaults to 20 and must be 1-100;
zero, negative and larger values are rejected.
The result is {count, courses}, with complete normalized course objects.
Semester failures are tool errors, not successful empty results.
Startup loads latest and the semester catalog. Historical semesters load on
demand, using independent snapshots and refresh locks. REST and MCP in the same
app share these snapshots; switching semesters does not change other requests'
data. A failed refresh may serve only that dataset's last-known-good snapshot.
/ping includes lazily registered semester datasets, so a requested semester
without a usable snapshot also affects readiness. Polling readiness alone does
not trigger recovery.
GET /newsletters supports optional name, title, and fuzzy (default
true) parameters. Name and article-title filters are combined with AND.
Fuzzy matching uses partial similarity with a threshold of 60 for both names and
article titles, sharing the name-search logic with MCP get_newsletters.
With fuzzy=false, names must match exactly and titles use case-sensitive literal
substring matching. Empty filters apply no filtering.
Responses retain source metadata and nested articles; a title filter removes
non-matching articles and sources with no matching articles. Without a title
filter, sources with empty article lists are retained. No matches return [].
GET /newsletters/sources lists name, link, and details without articles.
Its optional name filter matches exactly. Both endpoints return the dataset's
X-Data-Commit-Hash when available.
/newsletters/sources
/newsletters?name=教務處
/newsletters?name=教務處綜合教務組電子報&title=選課&fuzzy=false
MCP get_newsletters accepts an optional search for fuzzy source-name matching
with the same threshold of 60. Omitted or empty searches return all sources.
It returns all matching sources in publisher order, without a source-count limit.
Each source contains name, link, and latest_articles, retaining at most the
first three articles in publisher order with their title and date.
count is the number of matching sources and always equals the length of
newsletters, not the number of articles. No matches return
{"count": 0, "newsletters": []}.
For changes from earlier matching rules and source limits, see newsletter migration.
Use GET /locations to list all campus locations, or supply name to search
by name. fuzzy defaults to true; set fuzzy=false to require an exact
name match. Omitting name or passing an empty value returns all locations
regardless of fuzzy.
MCP search_locations(query, fuzzy=true) searches only locations and returns
{locations}, capped at 5 results after filtering and fuzzy ranking. Set
fuzzy=false for case-sensitive full-string name equality. Unlike the REST
listing endpoint, an empty MCP query does not request all locations. No matches
return {locations: []}; an unavailable dataset is a tool error. Use
search_directory for department or staff contacts instead.
GET /locations
GET /locations?name=台積館
GET /locations?name=台積館&fuzzy=false
Responses are arrays of locations with name, latitude, and longitude,
and include X-Data-Commit-Hash when available. No matches return HTTP 200
with [].
The bus API exposes three resources:
| Endpoint | Purpose |
|---|---|
GET /buses/routes |
Campus route metadata |
GET /buses/stops |
All bus stops with names and coordinates |
GET /buses/schedule |
Timetable query, optionally filtered by stop |
Stop-specific timetable queries use
GET /buses/schedule?stop=台積館, not a stop resource path.
Use GET /buses/routes?route=main to select campus route metadata. Its optional
route (main, nanda) and direction (up, down) filters return all routes
and directions when omitted.
Use GET /buses/schedule with these optional parameters:
| Parameter | Values or purpose | REST default |
|---|---|---|
route |
main, nanda, all |
all |
day |
weekday, weekend, current |
current |
direction |
up, down, all |
all |
stop |
Only buses serving the named stop | No stop filter |
time |
HH:MM threshold; ignored for day=current |
See time filtering below |
details |
Include dep_info and stops_time |
false |
limit |
Result limit, at least 1 | 5 |
A request without parameters returns the next departures across all routes and
directions. The parameter is limit, not limits.
Stop and time filters are applied before the limit; no matches return [].
route selects a campus route; response bus_type identifies the vehicle type
(for example, large-sized_bus), not the campus route. details=false returns
departure schedules; details=true includes dep_info and all stops_time entries.
When stop is supplied, each bus object also includes top-level arrive_time (the
estimated arrival time at that stop) in both details=false and details=true modes;
when stop is omitted, arrive_time is omitted from the response.
The REST endpoint and MCP use campus-qualified line identifiers:
main_red, main_green, nanda_route_1, and nanda_route_2. With details=true,
the identifier is in dep_info.line; otherwise it is in line.
Nanda route selection uses the publisher's explicit line (route1/route2),
including when description is empty. Only records without a line use the legacy
description heuristic. The same selection determines stop membership and arrival times.
time is in HH:MM format and filters by estimated arrival time at the selected
stop, including buses that
have departed but have not arrived at that stop yet. Arrivals exactly at the
requested time are included; buses that have already passed the stop are excluded.
Without stop, filtering still uses departure time. Both detail modes use the
same time filter and retain departure-time ordering. day=current uses the
current day and time and ignores time.
/buses/schedule
/buses/schedule?stop=台積館
/buses/schedule?route=main&direction=up
/buses/schedule?route=main&limit=5
/buses/schedule?route=all&day=weekday&direction=up&stop=台積館&details=true
get_bus_schedule accepts route, direction, limit, stop, day,
time, and details, using the same schedule filters as REST. Defaults are
route=all, direction=all, day=current, limit=5, and details=true.
Its buses list uses the REST schedule format; stop_name and stop_info
(including coordinates) are included when stop is supplied.
If supplied, MCP time must be a zero-padded HH:MM value from 00:00 to 23:59;
malformed values are rejected during argument validation, even for day=current.
GET /dining supports optional building_name, restaurant_name, fuzzy
(default true), and schedule parameters. All supplied filters are combined
with AND. schedule accepts today, weekday, saturday, or sunday; omitting
it applies no opening-day filter. Invalid or empty schedule values return 422.
For example, find potentially open restaurants in a building today:
/dining?building_name=小吃部&schedule=today
The response is always a list of buildings with nested restaurants, regardless
of whether schedule is supplied. With a schedule filter, buildings with no
matching restaurants are omitted; no matches return []. Without it, existing
search behavior is unchanged. With fuzzy=false, building names use exact
matching and restaurant names use case-sensitive literal substring matching.
today uses the Asia/Taipei date: Monday-Friday maps to weekday, with
separate Saturday and Sunday categories. Filtering only excludes restaurants
whose notes indicate a closure on that day. It does not check current opening
hours, holidays, or live availability. MCP find_dining(check_open="today")
uses the same Taiwan-date interpretation.
MCP find_dining always returns {"buildings": [...]}, with each building's
matching restaurants nested under restaurants, whether or not check_open
is supplied. Restaurant entries contain name, phone, schedule, and note
(empty notes become null). All filters apply before limiting results.
Without a non-empty building filter, each building includes at most 10
matching restaurants. Supplying building returns all matching restaurants
in every fuzzy-matched building, not just the first matching building.
Buildings with no matches are omitted; no matches return {"buildings": []}.
For example, find_dining(building="水木", check_open="today") lists all
restaurants matching the opening-day heuristic in matching buildings, using
the same grouped structure as a query without check_open.
MCP find_dining applies building and restaurant-name fuzzy filters together
with check_open, before limiting results.
For the previous response format and limits, see
MCP client migration.
Campus and library calendars share the same endpoints and response schemas.
The API combines https://data.nthusa.tw/calendars.json and
https://data.nthusa.tw/libraries/calendars.json through the shared dataset cache;
no publisher changes are required. Campus ids such as academic are unchanged.
Library source ids main, hss, and nanda become library-main, library-hss,
and library-nanda. Use the list endpoint to discover available calendars.
Calendar metadata includes optional category, source, and url fields.
Library calendars have category: "library" and source: "NTHU Library", and
retain their source calendar URL. Other calendars preserve publisher-supplied
metadata; missing optional metadata is null. Future campus calendars can add
categories and sources without new routers. The library- namespace is reserved
for library calendars; duplicate ids within a dataset are rejected.
| Endpoint | Response |
|---|---|
GET /calendars |
All calendar metadata, without events |
GET /calendars/{calendar_id} |
One calendar's metadata, without events |
GET /calendars/{calendar_id}/events |
Filtered, paginated events |
GET /calendars/{calendar_id}/events/{event_id} |
One event |
For example, get academic events overlapping October 2026:
/calendars/academic/events?start=2026-10-01&end=2026-10-31&limit=100&offset=0
Library opening hours use the same query parameters:
/calendars/library-main/events?start=2026-10-01&end=2026-10-31
startandendare optional inclusive dates (YYYY-MM-DD), interpreted using the local dates in the source calendar (Asia/Taipeiforacademic). Events overlapping any part of the range are included, not just events starting in it. All-day eventendvalues remain exclusive, as in the source; a timed event ending at midnight does not cover the following day.keywordis an optional case-insensitive literal substring in the title or description. Whitespace is trimmed; blank keywords apply no filter. All supplied filters are combined with AND.- Events are sorted by
start, thenid.limitdefaults to 100 (1-1000);offsetdefaults to 0 (nonnegative).X-Total-Countreports the filtered count before pagination. For individual calendars/events,X-Data-Commit-Hashis the source dataset's version. For the combined list, it is the SHA-256 of the JSON-encoded[campus_version, library_version]array, and is omitted if either version is unknown. - Missing calendars/events return 404, reversed date ranges return 400, and
malformed dates or invalid pagination return 422. No matches return
[]. Unavailable data without a usable snapshot returns 503; refresh failures retain the last-known-good snapshot. - The list requires usable snapshots for both datasets and returns 503 rather than a partial list if either is unavailable. Individual calendar/event requests only load their own source, so an outage in the other source does not block them.
The published datasets at NTHU_DATA_URL are the persistent source of truth.
Each process keeps in-memory snapshots shared by REST and MCP. Requests drive
freshness checks, controlled by FILE_DETAILS_CACHE_EXPIRY (default 300 seconds).
Failed refreshes retain the last-known-good snapshot; without a usable snapshot,
REST returns 503 and MCP returns a controlled tool error. Valid empty datasets
still succeed, and unknown versions omit X-Data-Commit-Hash.
Dataset versions come only from each entry's version in file_details.json;
last_commit is ignored, including when version is missing or null. Versions
are opaque equality tokens. An unchanged version avoids downloading, while a
changed version triggers a download and validation. Optional sha256 verifies
downloaded bytes; it is not a version identifier, and checksum-only changes do
not trigger a download. Missing or null versions retain an existing snapshot as
unverified; without a snapshot, a direct load is attempted and remains unverified.
The public X-Data-Commit-Hash header retains its name but carries the dataset
version (or the combined calendar version described above).
Snapshots are not durable or synchronized across instances. A restart or scale-to-zero loses cached data, and different instances can temporarily serve different versions. Startup prefetch failures do not prevent other datasets from being served.
Live electricity, library space, and lost-and-found integrations are not published
datasets and retain their existing on-demand behavior and separate HTTP clients.
They are not covered by snapshot freshness or last-known-good guarantees.
Their REST endpoints return 502 for upstream connection/HTTP failures or
invalid JSON/HTML/data, and 504 for upstream timeouts. Unexpected internal
errors return 500. Error bodies retain a detail field with fixed, safe
messages; upstream URLs and exception details stay in server logs. These responses
and their JSON schema are documented in OpenAPI. MCP live tools report failures
as tool errors (isError: true), not successful {"error": ...} results.
Valid empty space lists and lost-item results still succeed. Lost-item pages must
contain exactly the expected table columns (in any order, without duplicates) or
the library's explicit empty-result message on its lost-and-found page. Unexpected
columns, missing tables, and malformed rows are upstream errors.
GET /ping reports the current process's cached dataset health. It is excluded
from Swagger UI and /openapi.json. It never fetches upstream data, refreshes a
snapshot, or calls other API endpoints; responses have Cache-Control: no-store.
HEAD /ping supports uptime monitors such as UptimeRobot. It uses the same
readiness status codes as GET (200 or 503), returns no body, and retains
Cache-Control: no-store. It is also excluded from the API schema and never
fetches upstream data.
| HTTP | ready |
status |
Meaning |
|---|---|---|---|
| 200 | true |
ok |
Every dataset is usable, recorded freshness is current, and no check is due |
| 200 | true |
degraded |
Every dataset is usable, but some data is stale, unverified or due for a check |
| 503 | false |
unavailable |
At least one dataset has no usable snapshot, including datasets not loaded yet |
Valid empty snapshots count as usable.
checked_at is the UTC report time, not an upstream verification time.
datasets is keyed by published JSON path and includes usable, freshness,
check_due, version, loaded_at, published_at, last_checked_at,
last_refresh_attempt_at, last_refresh_success_at, and last_error.
Errors expose only category, upstream HTTP status (when known), and UTC time;
payloads, exception messages, and upstream URLs are not returned.
freshness is the last recorded refresh result; check_due separately indicates
that its TTL has expired or it has never been checked. Unknown metadata is null.
The report includes all configured datasets, even before their first request,
plus any additional datasets registered in the manager.
This is a readiness, not a liveness, check. Do not use it as a Cloud Run
startup/liveness probe. Failed startup loads are retried by normal dataset
requests after the TTL, not by /ping; a traffic gate that only calls /ping
cannot recover a missing snapshot by itself. Live energy, library space,
lost-and-found, and MCP transport health are not probed or certified by this
report. Multiple instances may report different cached states.