Columbia University · API Governance Rules
Columbia University API Rules
Spectral linting rules defining API design standards and conventions for Columbia University.
0 Rules
Spectral Ruleset
x-generated: '2026-08-19'
x-method: derived
x-source: >-
Derived from openapi/columbia-library-hours-openapi.yml and from defects observed live on
Columbia University surfaces on 2026-08-19. Columbia publishes no governance ruleset, style
guide or design standard for any of its APIs.
x-provider: Columbia University
x-providerId: columbia
x-operator: institution
x-format: spectral
description: >-
A governance ruleset for the one contract Columbia University actually operates. Every rule
below was written against a real observation on hours.library.columbia.edu, not imported from a
generic ruleset — the failing ones are failing right now.
rules:
- id: columbia-required-param-must-not-soft-200
severity: error
status: failing
surface: columbia:library-hours
statement: >-
An operation whose handler requires a query parameter must reject a request that omits it
with 400, not fall through to an HTML page with HTTP 200.
observed: >-
GET /api/v1/locations/butler with no date parameter returns HTTP 200 and the service's HTML
web page. The caller is told the request succeeded.
evidence: https://hours.library.columbia.edu/api/v1/locations/butler
remediation: >-
Declare date / start_date / end_date as a required oneOf at the operation level and return
the service's existing 400 envelope when neither form is supplied.
- id: columbia-error-must-carry-machine-code
severity: warn
status: failing
surface: columbia:library-hours
statement: >-
An error body must carry a stable machine-readable code, not only a human message with the
HTTP status repeated as a text prefix.
observed: '{"error":{"msg":"400: invalid date"},"data":null}'
evidence: https://hours.library.columbia.edu/api/v1/locations/butler?date=notadate
remediation: >-
Add a stable `code` member (e.g. invalid_date, date_range_inverted, location_not_found)
alongside msg, or adopt RFC 9457 problem+json.
- id: columbia-nullable-collection-should-be-empty-not-null
severity: warn
status: failing
surface: columbia:library-hours
statement: >-
A collection response must return an empty collection when there is nothing to return, not
null, so that a consumer has one shape to handle.
observed: >-
/locations/open_now returns "data": null when no library is open, and a keyed object
otherwise. Confirmed in Columbia's own source: `open_locations_hash.empty? ? nil : ...`.
evidence: https://hours.library.columbia.edu/api/v1/locations/open_now
remediation: Return an empty object.
- id: columbia-field-name-must-describe-content
severity: warn
status: failing
surface: columbia:library-hours
statement: A field name must describe what the field contains.
observed: >-
`formatted_date` contains a time or a time range ("Until 09:00PM", "09:00AM-09:00PM"), never
a date.
remediation: Rename to formatted_hours, or move display strings out of the contract entirely.
- id: columbia-unset-string-should-be-null-not-empty
severity: info
status: failing
surface: columbia:library-hours
statement: An unset optional string should be null or absent, consistently, not an empty string.
observed: short_note and short_note_url return "" when unset while note returns a value or "".
- id: columbia-collection-must-be-enumerable
severity: warn
status: failing
surface: columbia:library-hours
statement: >-
A resource addressed by an identifier must have an operation that lists the valid
identifiers.
observed: >-
/locations/{code} requires a location code, but the only listing operation is open_now, which
returns exclusively the locations open at that moment. There is no way to discover the code
for a library that is currently closed.
remediation: Add GET /locations returning the full location set.
- id: columbia-timezone-must-be-explicit
severity: warn
status: failing
surface: columbia:library-hours
statement: A time value must carry or declare its timezone.
observed: >-
open_time and close_time are bare HH:MM strings. America/New_York is inferable from context
and stated nowhere in the response, the documentation or any schema.
- id: columbia-api-must-publish-a-contract
severity: error
status: failing
surface: all
statement: A published API must publish a machine-readable description of itself.
observed: >-
Columbia publishes no OpenAPI, no schema and no reference documentation for the Hours API.
The contract in this repository was reverse-engineered from live probes and Columbia's own
source code.
remediation: >-
Publish openapi/columbia-library-hours-openapi.yml, or its equivalent, from
hours.library.columbia.edu.
- id: columbia-api-hostname-must-serve-something
severity: warn
status: failing
surface: api.library.columbia.edu
statement: A hostname reserved for an API must publish at least one route or a catalogue.
observed: >-
api.library.columbia.edu serves a page titled "API Home | Columbia University Libraries" whose
body is the words "API Home. CUL API." Every path beneath it 404s with a placeholder whose
body reads "A custom errorhandler for 404 responses".
evidence: https://api.library.columbia.edu/
- id: columbia-machine-access-must-not-contradict-open-licence
severity: warn
status: failing
surface: columbia:academic-commons, columbia:clio-opendata
statement: >-
An institution that releases data under an open licence should not proof-of-work-challenge
machine access to the same data.
observed: >-
Columbia University Libraries publishes the CLIO catalogue as CC0 MARCXML bulk downloads on
lito.cul.columbia.edu while serving an Anubis anti-crawler challenge to every automated
request against clio.columbia.edu, academiccommons.columbia.edu and
geodata.library.columbia.edu. The bulk route is the sanctioned one; nothing says so anywhere
a machine would look.
remediation: >-
Publish an llms.txt or a robots.txt that names the bulk extract path as the machine route.
Columbia currently publishes neither — both return 404 on www.columbia.edu.
- id: columbia-passing-anonymous-read
severity: info
status: passing
surface: columbia:library-hours
statement: A public read API should be callable with no credential and no click-through.
observed: >-
Both operations skip authentication explicitly in Columbia's own source and set
Access-Control-Allow-Origin: *. No key, no quota, no registration.
- id: columbia-passing-correct-status-codes
severity: info
status: passing
surface: columbia:library-hours
statement: Error conditions should use the correct HTTP status code.
observed: >-
404 for an unknown location code, 400 for an unparseable date and for an inverted range, each
with a JSON body and the correct content type. Only the missing-parameter path is wrong.
x-summary:
total: 12
failing: 10
passing: 2
errors: 2
warnings: 6
info: 4
x-notes: >-
Ten failing rules against one 2-operation API reads harsher than it is. The service does the
hard part right — it is genuinely public, genuinely CORS-enabled and genuinely returns correct
status codes — and fails almost entirely on things nobody at Columbia was ever asked to do,
because there is no institutional API governance function to ask.
Work with this as data
Every ruleset here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for spectral rules
4 MCP tools reach this
find_rulesBrowse and filter every ruleset in the catalog.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This ruleset
curl "https://apis.io/api/v1/rules/columbia-rules"
All spectral rules
curl "https://apis.io/api/v1/rules?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.