PrototypeOpen Learning Feed is a concept in development. The sample API and validator work, but no school publishes a feed yet, and examples use a fictional school.
Open Learning Feed
For developers

Verified university program data, kept current for you

Open Learning Feed is designed to collect the program and tuition feeds that universities publish on their own .edu domains. The plan is one API for all of them, and a notification within the hour when a school changes a price, a deadline or a program's status.

The school's feed is the source of truth. Each university writes and hosts its own feed. Open Learning Feed indexes it and is designed to tell you when it changes. If a value from Open Learning Feed and the school's own feed ever disagree, the school's feed wins.

A working sample API runs at /v1 on this site with no key required. Everything marked Planned below (API keys and the dashboard, pricing for large-scale use, subscriptions and notifications, the bulk export, the embed and WordPress plugin, the MCP server and the api.olfeed.org domain) describes how it is designed to work; none of it is available yet. Examples use Example University, a fictional school; every figure for it is invented.

Quickstart

  1. Get an API key Planned

    Keys will come from your dashboard once accounts open. The sample API on this site needs no key.

  2. Pull a school's programs

    Ask for one school by its domain. The response lists every program accepting applications (add ?status=all for the rest), with its prices, source and verified date.

    curl https://api.olfeed.org/v1/institutions/example.edu/programs \
      -H "Authorization: Bearer $OLF_API_KEY"
  3. Subscribe to changes Planned

    Register a URL, and Open Learning Feed will send it a signed notification whenever a field you care about changes, so your pages update without re-scraping. Until subscriptions open, poll /changes.

    curl -X POST https://api.olfeed.org/v1/subscriptions \
      -H "Authorization: Bearer $OLF_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://yoursite.example/hooks/olf",
        "institutions": ["example.edu"],
        "events": ["field.changed", "program.status_changed"],
        "fields": ["tuition.*"]
      }'

Authentication and pricing Planned

Once keys launch, you'll send yours as a bearer token on every request. Keys are planned to be scoped to one account and rotatable from the dashboard without downtime. The sample API ignores the header.

header
Authorization: Bearer olf_live_xxxxxxxxxxxxxxxxxxxx

The data stays open. Anyone can read a school's feed directly from its .edu domain, and affiliate and comparison sites pay nothing to use Open Learning Feed. Schools are the paying side: the more sites read their feeds, the more the feeds are worth to them.

Free

Affiliate and comparison sites, with no plan to charge
  • Every school's feed, read directly or through the API
  • Change notices for every school you list
  • Price history and the change stream
  • Embed and WordPress plugin
  • The "Verified by the institution" badge

Priced separately

Large aggregators and AI companies
  • Nightly bulk export
  • MCP server for AI assistants
  • Uptime commitments and dedicated support

Only uses far beyond a typical affiliate site. Prices are not set yet.

Finding schools

Every participating school publishes one file at the same address: https://<school-domain>/olf.json, served as application/json. It is plain JSON that follows schema.org (JSON-LD), so any JSON parser reads it. Consumers should also accept the earlier name /olf.jsonld: try /olf.json first, then that. There is nothing else to discover. To check a school directly, fetch that address and follow any redirect. To find schools at scale, use the directory at GET /institutions.

fetch a school's feed directly
curl -L https://www.example.edu/olf.json

The feed's publisher.contactPoint says who to contact at the school.

API reference

All responses are JSON. Prices are numbers in the stated currency and unit. Every tuition rate, fee and discount carries a source and a verifiedDate. Add ?format=json to a school's programs endpoint or to one program to get the school's own feed entries (plain JSON that follows schema.org) instead.

MethodPathReturns
GET/The API's name, version and a link to these docs
GET/institutionsDirectory of participating schools, with feed status, version and program count
GET/institutions/{domain}One school: name, accreditation and feed status
GET/institutions/{domain}/programsEvery program at a school, with current prices
GET/programs/{id}One program in full
GET/institutions/{domain}/tuition/historyPast rates with valid-from and valid-through dates. Add ?field= for one rate
GET/changes?since={cursor}Every change across subscribed schools since a cursor. ?limit= up to 500
GET/validate?url={feed}Fetch a feed by its https address and check it against the specification
POST/validateCheck the feed sent as the request body
POST/subscriptionsCreate a notification subscription (not available yet: the sample API answers 501)
DELETE/subscriptions/{id}Remove a subscription (not available yet: the sample API answers 501)
Try a request against the sample API 200 OK
response

These requests go to the working sample API on this site, which needs no key yet. It serves sample data for Example University, a fictional school.

Query parameters for /programs

ParameterTypeDescription
levelstringassociate, bachelor, master, doctoral, certificate
modestringonline, onsite, hybrid
statusstringDefaults to accepting_applications. Use all to include discontinued programs.
cipstringCIP 2020 code or prefix, such as 43.0401 or 43
audiencestringReturn only the prices and discounts one group can use: rates for everyone plus rates for that group, such as active_duty_military or veteran
fieldsstringComma-separated top-level fields to keep, such as tuition,transferCredit. id and name are always included

Bulk export and change stream

Large consumers load everything once, then follow changes. The nightly export (planned) is to be one gzipped JSON Lines file with a record per program. The change stream, which the sample API already serves, returns every change after a cursor, oldest first, so you can replay anything you missed.

curl "https://api.olfeed.org/v1/changes?since=c_2026-01-13&limit=500" \
  -H "Authorization: Bearer $OLF_API_KEY"

Change notifications Planned

As designed, Open Learning Feed checks each feed hourly. When a school publishes a new version, it compares it with the last one and sends a notification for each subscribed change. Your endpoint must answer with any 2xx status within 10 seconds. Failed deliveries retry with backoff for 72 hours, and every event stays available in the change stream.

Each change names the value with a path such as tuition.undergraduate.standard: the field, the program level and the rate. The same paths are used by /changes, by ?field= on the history endpoint, by subscription filters (tuition.* matches every rate) and by the embed.

EventSent when
field.changedAny subscribed value changes, such as a tuition rate or an application deadline
program.createdA school adds a program to its feed
program.status_changedA program moves to paused, teach-out or discontinued. Remove it from your listings.
feed.invalidA school's new feed fails validation. Open Learning Feed keeps serving the last valid version.
feed.unreachableA school's feed has not answered for 48 hours

Headers

HeaderContains
OLF-Event-IdUnique event ID. Store it and ignore repeats.
OLF-TimestampUnix seconds when Open Learning Feed signed the event
OLF-Signaturev1= followed by an HMAC-SHA256 of timestamp.body using your signing secret

Verifying notifications

Check the signature before you trust a notification. Reject events older than five minutes to block replays. For extra assurance, fetch the school's own feed and confirm the new value appears there.

import crypto from "node:crypto";

export function verify(rawBody, headers, secret) {
  const ts = headers["olf-timestamp"];
  const sig = headers["olf-signature"];
  if (!ts || !sig || !/^\d+$/.test(ts)) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // too old

  const expected = Buffer.from(crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex"));
  const sent = Buffer.from(sig.replace("v1=", ""));
  if (sent.length !== expected.length) return false;     // timingSafeEqual throws on unequal lengths
  return crypto.timingSafeEqual(sent, expected);
}
Send a sample notification Signed in your browser with the demo secret whsec_demo_example
request body
Your endpoint checks

Embed and WordPress Planned

If you run a content site and don't want to store data at all, you'll be able to embed a live block instead. It is designed to read Open Learning Feed each time the page loads, so the numbers on your page stay current. A WordPress plugin will wrap the same block in a shortcode.

<div data-olf-school="example.edu"
     data-olf-show="tuition.undergraduate.standard,tuition.masters.standard,tuition.masters.military"></div>
<script src="https://embed.olfeed.org/v1/block.js" async></script>
What readers see on your page
Example University tuitionVerified by the institution
Undergraduate, per credit hour$365 Master's, per credit hour$480 Military rate, per credit hour$250
Source: example.edu · verified Oct 1, 2026

AI assistants (MCP) Planned

A planned Open Learning Feed MCP server will let AI companies and agent builders connect an assistant directly to verified data. Model Context Protocol is a standard way to give an AI assistant tools it can call. Answers would then come from the school's own feed, with the source and date attached, instead of from whatever affiliate page a crawler found.

ToolWhat the assistant can ask
search_programsFind programs by field of study, level, mode and price range
get_tuitionCurrent rates for a school, including audience-specific prices and discounts
compare_programsSide-by-side values for two to five programs, each with its source
get_change_historyWhen and how a school's published values changed
MCP client configuration
{
  "mcpServers": {
    "olf": {
      "url": "https://mcp.olfeed.org/v1",
      "headers": { "Authorization": "Bearer ${OLF_API_KEY}" }
    }
  }
}

Display and badge rules

Sites that follow these rules may show the "Verified by the institution" badge. Open Learning Feed plans to audit badge sites, and a site that breaks a rule would lose the badge until it is fixed.

  1. Update within 24 hours

    Apply every subscribed change within 24 hours of the notification, or poll the change stream at least daily.

  2. Show the verified date and source

    Display the verifiedDate and link to the school's source page next to any price.

  3. Never edit values

    Show the number exactly as the school published it. Rounding, "starting at" rewording or mixing in third-party figures without labeling them removes the badge.

  4. Remove discontinued programs

    Take down or clearly mark a program within 24 hours of program.status_changed.

  5. Label audience prices

    Never show a military, veteran or first-responder rate as the general price.

Field reference

Each API field maps to a schema.org term or to the olf extension. Required fields must be present for a school's feed to pass validation. School feeds never include salary, career, job or occupation data, so the API doesn't return them. The table lists the main fields. tuition[] and fees[] list current and announced rates; earlier rates come from the history endpoint. Responses also carry timeToComplete, prerequisites, transferCredit.sources, accreditation[], the program's source page and updated date; each discount's percent or amount and combinable; each fee's type; and each schedule's every and description.

FieldTypeTierMaps toMeaning
program.namestringRequirednameProgram name as the school publishes it
program.statusenumRequiredolf:programStatusaccepting_applications, launching, paused, teach_out, discontinued
program.levelenumRequirededucationalCredentialAwarded.educationalLevelCredential level awarded
program.modeenumRequirededucationalProgramModeOnline, onsite (in person) or hybrid
tuition[].amountnumberRequiredpricePrice in currency per basis
tuition[].basisenumRequiredolf:tuitionBasisper_credit_hour, per_course, per_term, per_academic_year, flat_rate_program, subscription_term
tuition[].sourceurlRequiredolf:sourceUrlPage on the school's site where the value appears
tuition[].verifiedDatedateRequiredolf:verifiedDateDate the school last confirmed the value
tuition[].audienceenum[]Recommendedolf:eligibleAudienceGroups the price applies to. Empty means everyone.
discounts[]object[]Recommendedolf:TuitionDiscountGrants such as a 10% Veteran Grant, with eligibility and stacking rules
fees[]object[]RecommendedpriceComponentTypeTechnology, materials, application and graduation fees
program.creditsintegerRecommendednumberOfCreditsCredits required to complete
transferCredit.maxintegerRecommendedolf:maxTransferCreditsMost credits a student can transfer in
program.nextStart, program.applicationDeadlinedateRecommendedstartDate, applicationDeadlineNext start date and the deadline to apply for it
program.admissionTestPolicyenumRecommendedolf:admissionTestPolicytest_required, test_optional, test_not_required, waiver_available
program.completion[]object[]Recommendedolf:completionTimeTypical and fastest time to finish by enrollment (full_time, part_time, accelerated) and assumed transfer credits
program.schedule[]object[]Recommendedolf:classScheduleWhen classes meet: daytime, evening, weekend, synchronous_online or asynchronous_online, with days, times and time zone
program.focusAreas[]object[]Optionalolf:focusAreaConcentrations, specializations or tracks. type carries the school's own word.
program.cipobjectOptionalprogramTypeCIP 2020 classification as { code, name }, or null

Errors and limits

Errors return JSON as { "error": { "code", "message" } }. These are the codes the sample API returns today. When API keys launch, three more are planned: 401 invalid_key, 403 plan_required (for the separately priced bulk and AI access) and 429 rate_limited (wait the seconds in Retry-After).

StatusCodeWhat to do
400missing_url, invalid_urlValidation by address needs ?url= with an https feed address, or send the feed as the POST body.
404institution_not_foundThe school has no feed yet. Check GET /v1/institutions for participating schools.
404program_not_found, field_not_foundCheck the program id, or the rate name for ?field=; the error lists the available names.
404not_foundNo such endpoint. Check the path against the table above.
405method_not_allowedUse GET, except for POST /validate. Subscription calls answer 501 until they launch.
413feed_too_largeThe feed, sent as the POST body or fetched by ?url=, is over 2 MB. Split a large institution into feeds listed with olf:includesFeed.
501not_available_yetSubscriptions aren't available yet. Use /changes in the meantime.
502feed_unreachableThe school's server didn't answer or returned an error. Try again later.
500internal_errorSomething went wrong on our side. Try again.

Sandbox school

Planned: a sandbox domain, sandbox.example.edu, will step through Example University's rate history every hour and send you the matching field.changed events, then start over. Until it launches, the sample API serves example.edu, and the notification demo above shows the events it will send.

StepCapturedUndergraduateMaster'sMilitary, master's
1May 2022$310$420$250
2Jun 2023$335$445$250
3Jun 2024$350$465$290
4Dec 2024$350$465$250
5Apr 2026$365$480$250

Rates per credit hour for the fictional sandbox school. The military undergraduate rate was $250 at every step.

Changelog

DateVersionChange
Oct 10, 2026v1 draftFirst draft of the developer documentation