{
  "openapi": "3.1.0",
  "info": {
    "title": "GradFax Data API",
    "version": "1.0.0",
    "summary": "Read-only U.S. college data from IPEDS and the College Scorecard.",
    "description": "U.S. college data from IPEDS (NCES) and the U.S. Department of Education College Scorecard, plus two brand fields (mascot and primary_color) that have no government source, served as JSON over HTTPS. Two read endpoints: search schools, and fetch one school by IPEDS unitid or GradFax slug.\n\nAuthentication: send your key in the X-API-Key header, for example: curl -H \"X-API-Key: gf_live_YOUR_KEY\" \"https://www.gradfax.com/api/v1/schools?state=CA&limit=5\". Keys are created from a signed-in GradFax account at https://www.gradfax.com/my-gradfax/api-keys; key management is an account action and is not part of this API description.\n\nMonthly request limits per key: free 2000, developer 25000, startup 250000, enterprise unlimited. Free keys on one account also share a 5000 request monthly ceiling. Every request whose key is accepted counts against the month, whatever status the endpoint then returns.\n\nAttribution: every successful response carries an attribution string. Please credit IPEDS (NCES), the College Scorecard and GradFax wherever you display this data.\n\nTerms: API access does not grant commercial, resale, redistribution, or white-label rights. Any such rights require a separate written agreement signed by GradFax that expressly grants them. See https://www.gradfax.com/terms and https://www.gradfax.com/developers.",
    "termsOfService": "https://www.gradfax.com/terms",
    "contact": {
      "name": "GradFax",
      "url": "https://www.gradfax.com/developers",
      "email": "josh@gradfax.com"
    }
  },
  "servers": [
    {
      "url": "https://www.gradfax.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "Schools",
      "description": "Institution-level data."
    }
  ],
  "externalDocs": {
    "description": "GradFax developer documentation",
    "url": "https://www.gradfax.com/developers"
  },
  "paths": {
    "/api/v1/schools": {
      "get": {
        "operationId": "listSchools",
        "tags": [
          "Schools"
        ],
        "summary": "List schools with filters",
        "description": "Returns one page of schools, ordered by the stored enrollment column from largest to smallest (schools with no enrollment figure come last). That column can differ from the enrollment_total value shown on an item, so the visible order may not be strictly descending. Schools without a name are never returned.\n\nFilters combine with AND. The state, control, max_tuition, min_acceptance_rate and max_acceptance_rate filters are applied before paging. The hbcu filter is applied after paging: with hbcu=true a page can come back with fewer than limit rows, and meta.total still counts the schools matched by the other filters, not only HBCUs.\n\nEach item combines two source tables. The school-level fields (location, control, admissions, test scores, pct_women, mascot) are always present, null when unknown. The remaining fields (zip, institution_level, hbcu, tribal, land_grant, enrollment_total, tuition, net price, graduation rate, earnings, debt, Pell and first-generation shares, primary_color) are left out of an item entirely when GradFax has no record for that school in the second table. The single-school endpoint always returns every field.\n\nThe server does not validate query values and never answers 400. A value that cannot be read as a number, or a limit below 1 or an offset below 0, gives undefined results.",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Two-letter state or territory postal abbreviation. The server upper-cases the value, so ca and CA are the same filter. An empty value applies no filter.",
            "schema": {
              "type": "string",
              "examples": [
                "CA"
              ]
            }
          },
          {
            "name": "control",
            "in": "query",
            "required": false,
            "description": "Ownership filter. public returns public schools. private returns private schools, which includes both private nonprofit and for-profit. Any other value is ignored and applies no filter.",
            "schema": {
              "type": "string",
              "enum": [
                "public",
                "private"
              ]
            }
          },
          {
            "name": "max_tuition",
            "in": "query",
            "required": false,
            "description": "Maximum out-of-state tuition, in dollars, inclusive. The value is read as an integer (a decimal part is dropped). The filter uses the stored out-of-state tuition column, which can differ from the tuition_outstate value shown on an item. Schools with no out-of-state tuition on record are excluded when this filter is set.",
            "schema": {
              "type": "integer",
              "examples": [
                20000
              ]
            }
          },
          {
            "name": "min_acceptance_rate",
            "in": "query",
            "required": false,
            "description": "Minimum acceptance rate, inclusive, compared with the acceptance_rate field in the same units that field is returned in (percent, see the acceptance_rate field). Schools with no stored acceptance rate are excluded when this filter is set. A stored 0 is treated as a real number by this filter, so it can match a maximum.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "max_acceptance_rate",
            "in": "query",
            "required": false,
            "description": "Maximum acceptance rate, inclusive, compared with the acceptance_rate field in the same units that field is returned in (percent, see the acceptance_rate field). Schools with no stored acceptance rate are excluded when this filter is set. A stored 0 is treated as a real number by this filter, so it can match a maximum.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "hbcu",
            "in": "query",
            "required": false,
            "description": "Send true to keep only historically Black colleges and universities. Any other value, including false, applies no filter. This filter runs after paging, see the operation description.",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Values above 100 are capped at 100. Use 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of rows to skip, for paging. Use 0 or higher.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of schools.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchoolListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/schools/{id}": {
      "get": {
        "operationId": "getSchool",
        "tags": [
          "Schools"
        ],
        "summary": "Get one school by IPEDS unitid or slug",
        "description": "Returns one school. The id is either an IPEDS unitid (1 to 9 digits) or a GradFax slug such as stanford-university. A path made only of digits is always treated as a unitid; a run of more than 9 digits, and any id longer than 200 characters, is answered with 404.\n\nEvery field listed in the SchoolProfile schema is always present. A value GradFax has no record of is null.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "IPEDS unitid (1 to 9 digits) or GradFax slug.",
            "schema": {
              "type": "string",
              "maxLength": 200,
              "examples": [
                "110635"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The school.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchoolProfileResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No school matches the id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "School not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your GradFax API key. Example header: X-API-Key: gf_live_YOUR_KEY. The REST endpoints read only this header."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The X-API-Key header is missing, or the key is not valid (a wrong key and a disabled key look the same).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "summary": "No key sent",
                "value": {
                  "error": "Missing X-API-Key header"
                }
              },
              "invalid": {
                "summary": "Key not recognised or disabled",
                "value": {
                  "error": "Invalid API key"
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "A monthly limit is used up. Either the key's own monthly limit (2000 for a free key) or, for free keys, the 5000 requests a month shared by all free keys on one account. The message names the limit and the reset date. Limits reset on the 1st of each month, UTC. A key created on the 29th, 30th or 31st may get its first reset a month later than that.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "keyLimit": {
                "summary": "The key's own tier limit",
                "value": {
                  "error": "Monthly limit reached (2000 requests for free tier). Resets Tue Dec 01 2026."
                }
              },
              "accountLimit": {
                "summary": "The shared free-tier account ceiling",
                "value": {
                  "error": "Monthly account limit reached (5000 free requests across all keys). Resets Tue Dec 01 2026."
                }
              }
            }
          }
        }
      },
      "ServerError": {
        "description": "The data could not be read. The message is fixed text and never contains database details.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "query": {
                "summary": "Database read failed",
                "value": {
                  "error": "Query failed"
                }
              },
              "config": {
                "summary": "Server configuration problem",
                "value": {
                  "error": "Server configuration error"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error response has this shape. Statuses used: 401, 404 (single school only), 429, 500.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          }
        }
      },
      "Attribution": {
        "type": "string",
        "description": "Source credit to display with the data.",
        "examples": [
          "Data sourced from IPEDS and College Scorecard via GradFax (gradfax.com)"
        ]
      },
      "SchoolListResponse": {
        "type": "object",
        "required": [
          "data",
          "meta",
          "attribution"
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "Schools on this page, largest enrollment first. After the hbcu filter the array can hold fewer than meta.limit items.",
            "items": {
              "$ref": "#/components/schemas/SchoolListItem"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          }
        }
      },
      "ListMeta": {
        "type": "object",
        "required": [
          "total",
          "limit",
          "offset",
          "tier",
          "requests_remaining"
        ],
        "properties": {
          "total": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Number of schools matching the state, control, max_tuition and acceptance rate filters, across all pages. It does not reflect the hbcu filter."
          },
          "limit": {
            "type": "integer",
            "description": "The page size used, after the cap of 100."
          },
          "offset": {
            "type": "integer",
            "description": "The offset used."
          },
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "developer",
              "startup",
              "enterprise"
            ],
            "description": "Tier of the key that made the request."
          },
          "requests_remaining": {
            "oneOf": [
              {
                "type": "integer",
                "minimum": 0
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ],
            "description": "Requests left this month for this key, worked out from the count recorded before the current request was added. The string unlimited for the enterprise tier."
          }
        }
      },
      "SchoolProfileResponse": {
        "type": "object",
        "required": [
          "data",
          "attribution"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/SchoolProfile"
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          }
        }
      },
      "SchoolListItem": {
        "description": "One school in a list. Fields from the school_data source (see SchoolDataFields) are absent when GradFax has no record for the school there.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SchoolFields"
          },
          {
            "$ref": "#/components/schemas/SchoolDataFields"
          },
          {
            "type": "object",
            "required": [
              "unitid",
              "name",
              "city",
              "state",
              "website",
              "latitude",
              "longitude",
              "locale",
              "control",
              "acceptance_rate",
              "retention_rate",
              "sat_math_25",
              "sat_math_75",
              "sat_reading_25",
              "sat_reading_75",
              "act_composite_25",
              "act_composite_75",
              "pct_women",
              "mascot"
            ],
            "properties": {
              "unitid": {
                "type": [
                  "integer",
                  "string"
                ],
                "description": "IPEDS unitid. In list responses it is passed through from the schools table exactly as stored, so it can be a JSON number or a string of digits. Treat it as an opaque id or convert it to a number. The single-school endpoint always returns an integer."
              },
              "name": {
                "type": "string",
                "description": "Institution name."
              }
            }
          }
        ]
      },
      "SchoolProfile": {
        "description": "One school. All 35 fields are always present; null means GradFax has no record.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SchoolFields"
          },
          {
            "$ref": "#/components/schemas/SchoolDataFields"
          },
          {
            "type": "object",
            "required": [
              "unitid",
              "name",
              "city",
              "state",
              "zip",
              "website",
              "latitude",
              "longitude",
              "locale",
              "control",
              "institution_level",
              "hbcu",
              "tribal",
              "land_grant",
              "acceptance_rate",
              "enrollment_total",
              "tuition_instate",
              "tuition_outstate",
              "avg_net_price_overall",
              "grad_rate_4yr",
              "retention_rate",
              "earnings_10yr",
              "median_debt",
              "sat_math_25",
              "sat_math_75",
              "sat_reading_25",
              "sat_reading_75",
              "act_composite_25",
              "act_composite_75",
              "pct_receiving_pell",
              "pct_women",
              "pct_first_generation",
              "mascot",
              "primary_color",
              "slug"
            ],
            "properties": {
              "unitid": {
                "type": "integer",
                "description": "IPEDS unitid."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Institution name."
              },
              "slug": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "GradFax slug for the school, usable as the id path parameter, for example stanford-university."
              }
            }
          }
        ]
      },
      "SchoolFields": {
        "type": "object",
        "description": "Fields read from the GradFax schools table. IPEDS unless noted.",
        "properties": {
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "City."
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "State or territory postal abbreviation."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "Official web address as stored, usually a host name without an http(s):// prefix."
          },
          "latitude": {
            "type": [
              "number",
              "null"
            ],
            "description": "Campus latitude."
          },
          "longitude": {
            "type": [
              "number",
              "null"
            ],
            "description": "Campus longitude."
          },
          "locale": {
            "type": [
              "string",
              "integer",
              "null"
            ],
            "description": "IPEDS locale (urban, suburban, town or rural setting). Returned as stored: some schools carry a text label, others a numeric IPEDS locale code."
          },
          "control": {
            "type": [
              "integer",
              "null"
            ],
            "enum": [
              1,
              2,
              3,
              null
            ],
            "description": "Ownership as an IPEDS-style code: 1 public, 2 private, 3 for-profit. null when the ownership is not recorded."
          },
          "acceptance_rate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Admitted divided by applied, as a percent from 0 to 100 (for example 3.9 means 3.9 percent). Returned as stored: a stored 0 can mean the value is missing rather than a measured zero."
          },
          "retention_rate": {
            "type": [
              "number",
              "null"
            ],
            "description": "First-to-second year retention, as a percent from 0 to 100. Returned as stored: a stored 0 can mean the value is missing rather than a measured zero."
          },
          "sat_math_25": {
            "type": [
              "number",
              "null"
            ],
            "description": "SAT math, 25th percentile score."
          },
          "sat_math_75": {
            "type": [
              "number",
              "null"
            ],
            "description": "SAT math, 75th percentile score."
          },
          "sat_reading_25": {
            "type": [
              "number",
              "null"
            ],
            "description": "SAT reading, 25th percentile score."
          },
          "sat_reading_75": {
            "type": [
              "number",
              "null"
            ],
            "description": "SAT reading, 75th percentile score."
          },
          "act_composite_25": {
            "type": [
              "number",
              "null"
            ],
            "description": "ACT composite, 25th percentile score."
          },
          "act_composite_75": {
            "type": [
              "number",
              "null"
            ],
            "description": "ACT composite, 75th percentile score."
          },
          "pct_women": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of women among enrolled students, as a percent."
          },
          "mascot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Team nickname. Brand metadata, not taken from IPEDS or the College Scorecard."
          }
        }
      },
      "SchoolDataFields": {
        "type": "object",
        "description": "Fields read from the GradFax school_data table. IPEDS unless noted.",
        "properties": {
          "zip": {
            "type": [
              "string",
              "null"
            ],
            "description": "ZIP code."
          },
          "institution_level": {
            "type": [
              "integer",
              "string",
              "null"
            ],
            "description": "Institution level, returned as stored (a numeric code or a text label depending on the row). Not normalized by the API."
          },
          "hbcu": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 when IPEDS flags the school as a historically Black college or university, 0 when it does not."
          },
          "tribal": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 when flagged as a tribal college, 0 when not."
          },
          "land_grant": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 when flagged as a land-grant institution, 0 when not."
          },
          "enrollment_total": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Total enrollment, undergraduate and graduate."
          },
          "tuition_instate": {
            "type": [
              "number",
              "null"
            ],
            "description": "In-state tuition (sticker price), dollars."
          },
          "tuition_outstate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Out-of-state tuition (sticker price), dollars."
          },
          "avg_net_price_overall": {
            "type": [
              "number",
              "null"
            ],
            "description": "Average net price (sticker price minus aid), dollars."
          },
          "grad_rate_4yr": {
            "type": [
              "number",
              "null"
            ],
            "description": "Federal graduation rate within 150% of normal time (six years at a four-year school, three at a two-year school), as a percent from 0 to 100. The field name says 4yr for historical reasons. Returned as stored: a stored 0 can mean the value is missing rather than a measured zero."
          },
          "earnings_10yr": {
            "type": [
              "number",
              "null"
            ],
            "description": "Median earnings ten years after enrollment, dollars. Source: College Scorecard."
          },
          "median_debt": {
            "type": [
              "number",
              "null"
            ],
            "description": "Median federal loan debt, dollars. Source: College Scorecard."
          },
          "pct_receiving_pell": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of undergraduates receiving Pell grants, as a percent."
          },
          "pct_first_generation": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of first-generation students, as a percent. Source: College Scorecard."
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ],
            "description": "School brand color as a hex code. Brand metadata, not taken from IPEDS or the College Scorecard."
          }
        }
      }
    }
  }
}
