{"openapi":"3.1.0","info":{"title":"cedarengine","version":"0.1.0","description":"One API over the Cedarville directory, course catalog, printed book, harvested booklists and the campus map."},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"}}},"security":[{"bearer":[]}],"paths":{"/health":{"get":{"summary":"Liveness, and enough of a count to tell an empty database from a full one","tags":["engine"],"security":[],"parameters":[],"responses":{"200":{"description":"ok"}}}},"/v1/people":{"get":{"summary":"Search the directory by name, dorm, department or population","tags":["people"],"security":[{"bearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Name. Two words are read as first then last.","schema":{"type":"string"}},{"name":"dorm","in":"query","required":false,"description":"Dorm name, exact","schema":{"type":"string"}},{"name":"department","in":"query","required":false,"description":"Department description, exact","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Student type: UG, UGO, GS, P4","schema":{"type":"string"}},{"name":"class","in":"query","required":false,"description":"Class: FR, SO, JR, SR","schema":{"type":"string"}},{"name":"gone","in":"query","required":false,"description":"Include people the directory has stopped listing","schema":{"type":"string"}},{"name":"fact.*","in":"query","required":false,"description":"Any fact, exactly: fact.16p.type=INTJ. See /v1/facts/keys for what exists.","schema":{"type":"string"}},{"name":"has","in":"query","required":false,"description":"A fact key they must carry, whatever it says. Repeatable.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1-500, default 25","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Rows to skip","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}":{"get":{"summary":"One person by directory id","tags":["people"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}/major":{"get":{"summary":"Best guesses at a student's major, from their booklists","tags":["people"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"top","in":"query","required":false,"description":"How many guesses, default 3","schema":{"type":"string"}},{"name":"year","in":"query","required":false,"description":"Catalog year to score against","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}/schedule":{"get":{"summary":"A student's week: the sections their booklist names, with times and rooms","tags":["people"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"term","in":"query","required":false,"description":"Term code. Defaults to the current one, then the newest held.","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}/history":{"get":{"summary":"Everything that has changed about a person since we first saw them","tags":["people"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Events to return, default 200","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}/location":{"get":{"summary":"The building a person is listed against, with coordinates","tags":["people"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/dorms":{"get":{"summary":"Population by dorm","tags":["people"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}/facts":{"get":{"summary":"Everything asserted about a person beyond the directory","tags":["facts"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"history","in":"query","required":false,"description":"Return the whole log, tombstones included","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"History entries, default 200","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}},"post":{"summary":"Assert facts about a person","tags":["facts"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":"{ \"source\": \"assassins\", \"facts\": [{ \"key\": \"groupme.id\", \"value\": \"12345\" }] }"}}},"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/facts":{"get":{"summary":"Who carries a fact, or how the population splits on one","tags":["facts"],"security":[{"bearer":[]}],"parameters":[{"name":"key","in":"query","required":true,"description":"Fact key, e.g. groupme.id or 16p.type","schema":{"type":"string"}},{"name":"value","in":"query","required":false,"description":"Only people whose value matches. Omit for everyone.","schema":{"type":"string"}},{"name":"distribution","in":"query","required":false,"description":"Counts per value instead of people. Meaningless for a photograph.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"People to return, default 50","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/facts/keys":{"get":{"summary":"Every fact key in the database, with how many people carry it","tags":["facts"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/people/{id}/facts/retract":{"post":{"summary":"Withdraw a fact, by writing down that it is no longer claimed","tags":["facts"],"security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":"{ \"key\": \"groupme.id\", \"source\": \"assassins\", \"slot\": \"\", \"allSlots\": false }"}}},"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/terms":{"get":{"summary":"Terms held locally, with how much of each","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/timetable":{"get":{"summary":"Sections and enrolment by weekday and hour, plus enrolment by subject","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"term","in":"query","required":true,"description":"Term code","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/sections":{"get":{"summary":"Sections offered in a term","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"term","in":"query","required":true,"description":"Term code, e.g. 2026FA","schema":{"type":"string"}},{"name":"code","in":"query","required":false,"description":"Course code, e.g. CS-1210","schema":{"type":"string"}},{"name":"subject","in":"query","required":false,"description":"Subject code, e.g. CS","schema":{"type":"string"}},{"name":"open","in":"query","required":false,"description":"Only sections with seats left","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Match title, instructor or section name","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1-500, default 50","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Rows to skip","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/courses":{"get":{"summary":"Courses, defaulting to the whole catalog rather than one term","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"term","in":"query","required":false,"description":"Term code, or \"ALL\" for everything","schema":{"type":"string"}},{"name":"subject","in":"query","required":false,"description":"Subject code","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Match title or code","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1-500, default 50","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Rows to skip","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/courses/{code}":{"get":{"summary":"One course by code, falling back to the full catalog when a term is given","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"}},{"name":"term","in":"query","required":false,"description":"Prefer this term's record","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/rules/{requirement}/{subrequirement}/{group}":{"get":{"summary":"Courses that satisfy one requirement group, as Colleague resolved it","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"requirement","in":"path","required":true,"schema":{"type":"string"}},{"name":"subrequirement","in":"path","required":true,"schema":{"type":"string"}},{"name":"group","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/programs":{"get":{"summary":"Degree programs from the printed catalog","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"year","in":"query","required":false,"description":"Catalog year, e.g. 2026-2027. Defaults to the newest held.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Match the program title","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/programs/{page}":{"get":{"summary":"One program by its page in the book","tags":["catalog"],"security":[{"bearer":[]}],"parameters":[{"name":"page","in":"path","required":true,"schema":{"type":"string"}},{"name":"year","in":"query","required":false,"description":"Catalog year. Defaults to the newest held.","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/majors/guesses":{"get":{"summary":"Every student with booklist data, ranked against every program","tags":["majors"],"security":[{"bearer":[]}],"parameters":[{"name":"top","in":"query","required":false,"description":"Guesses per student, default 3","schema":{"type":"string"}},{"name":"year","in":"query","required":false,"description":"Catalog year to score against","schema":{"type":"string"}},{"name":"min","in":"query","required":false,"description":"Only students with at least this much signal","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Students to return, default 200","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/majors/distribution":{"get":{"summary":"What the population appears to be studying, counted by top guess","tags":["majors"],"security":[{"bearer":[]}],"parameters":[{"name":"min","in":"query","required":false,"description":"Distinctive courses required to count, default 2","schema":{"type":"string"}},{"name":"year","in":"query","required":false,"description":"Catalog year","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/majors/model":{"get":{"summary":"What the model is built from","tags":["majors"],"security":[{"bearer":[]}],"parameters":[{"name":"year","in":"query","required":false,"description":"Catalog year","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/majors/schools":{"get":{"summary":"The registrar's taxonomy: every program, its department and its school","tags":["majors"],"security":[{"bearer":[]}],"parameters":[{"name":"level","in":"query","required":false,"description":"major, minor, or leave off for all","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/majors/evaluate":{"post":{"summary":"Score the model against known majors and log the result","tags":["majors"],"security":[{"bearer":[]}],"parameters":[{"name":"source","in":"query","required":false,"description":"Only labels from this file","schema":{"type":"string"}},{"name":"year","in":"query","required":false,"description":"Catalog year","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/majors/churn":{"get":{"summary":"Courses added and dropped across a term, from booklist deltas","tags":["majors"],"security":[{"bearer":[]}],"parameters":[{"name":"term","in":"query","required":true,"description":"Term code","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/campus/map":{"get":{"summary":"Outlines and the walking graph, in metres from the map origin","tags":["campus"],"security":[{"bearer":[]}],"parameters":[{"name":"graph","in":"query","required":false,"description":"Include nodes and edges. Off by default.","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/campus/buildings":{"get":{"summary":"Every building the engine has coordinates for","tags":["campus"],"security":[{"bearer":[]}],"parameters":[{"name":"kind","in":"query","required":false,"description":"dorm, office or both","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/campus/occupancy":{"get":{"summary":"The whole population placed on the map, bucketed","tags":["campus"],"security":[{"bearer":[]}],"parameters":[{"name":"by","in":"query","required":false,"description":"class (default), type or department","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/campus/traffic":{"get":{"summary":"How many people walk each footpath, from dorms to the buildings their courses meet in","tags":["campus"],"security":[{"bearer":[]}],"parameters":[{"name":"term","in":"query","required":false,"description":"Term code. Defaults to the current one.","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/glossary":{"get":{"summary":"What the directory's class and population codes mean","tags":["campus"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/campus/route":{"get":{"summary":"Walking route between two buildings, or two people","tags":["campus"],"security":[{"bearer":[]}],"parameters":[{"name":"from","in":"query","required":true,"description":"Building label or person id","schema":{"type":"string"}},{"name":"to","in":"query","required":true,"description":"Building label or person id","schema":{"type":"string"}},{"name":"path","in":"query","required":false,"description":"Include the node path","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/history/events":{"get":{"summary":"Directory changes, newest first","tags":["history"],"security":[{"bearer":[]}],"parameters":[{"name":"kind","in":"query","required":false,"description":"appeared, changed, vanished or returned","schema":{"type":"string"}},{"name":"field","in":"query","required":false,"description":"Only changes to this field, e.g. dorm_name","schema":{"type":"string"}},{"name":"since","in":"query","required":false,"description":"ISO timestamp","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1-1000, default 100","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/history/population":{"get":{"summary":"Arrivals and departures by month","tags":["history"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/history/sweeps":{"get":{"summary":"Every collection run, whoever ran it","tags":["history"],"security":[{"bearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Default 20","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/history/churn":{"get":{"summary":"Booklist churn per term: courses added and dropped after the first look","tags":["history"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/sync/manifest":{"get":{"summary":"What the extension should collect, and how stale each source is","tags":["sync"],"security":[{"bearer":[]}],"parameters":[{"name":"term","in":"query","required":false,"description":"Term to harvest booklists for","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/sync/start":{"post":{"summary":"Open a sweep and get the first batch of work","tags":["sync"],"security":[{"bearer":[]}],"parameters":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":"{ \"kind\": \"directory\" | \"booklists\", \"refresh\": false, \"term\": \"2027SP\", \"limit\": 100 }"}}},"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/sync/directory":{"post":{"summary":"Post directory results and get the next batch","tags":["sync"],"security":[{"bearer":[]}],"parameters":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":"{ \"sweep\": 12, \"results\": [{ \"last\": \"kl\", \"first\": \"\", \"people\": [...] }] }"}}},"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/sync/booklists":{"post":{"summary":"Post harvested booklists and get the next batch of ids","tags":["sync"],"security":[{"bearer":[]}],"parameters":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object"},"example":"{ \"sweep\": 13, \"term\": \"2027SP\", \"rows\": [{ \"id\": \"1234567\", \"books\": [...] }] }"}}},"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/stats":{"get":{"summary":"Everything the engine holds, counted","tags":["engine"],"security":[{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}},"/v1/analytics":{"get":{"summary":"Request volume and latency","tags":["engine"],"security":[{"bearer":[]}],"parameters":[{"name":"hours","in":"query","required":false,"description":"Window, default 24","schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"401":{"description":"missing or wrong bearer token"}}}}}}