# Rate My Transcript Scoring API

Score how broad a set of University of Illinois Urbana-Champaign courses is, for up to 500 transcripts per request. No key is needed.

## What the Score Means

Each course's title and description is embedded as a 3,072-dimensional vector (OpenAI `text-embedding-3-large`, once per semester the course was offered). A transcript's breadth is the volume its courses span in that space: the log-determinant of their Gram matrix. The score normalizes that volume to 0–100 against reference curves for the same number of courses. 100 is the most diverse set a greedy search finds, and 0 is the average of random courses from the lowest-scoring department. [How the score works](https://www.ratemytranscript.com/how) has the details.

## Request

`POST https://www.ratemytranscript.com/api/v1/score` with `Content-Type: application/json` and a body holding `transcripts`: 1 to 500 of them. Each transcript has `courses`, up to 100, and an optional `id`: any string up to 200 characters, echoed back so you can match results to your records.

Each course can be:

- a code, like `"CS 124"` (case doesn't matter);
- a code with a section title, like `"CS 199: Python for Data"`, for courses whose sections cover different topics;
- an object that says when the course was taken, like `{"id": "CS 124", "year": 2025, "term": "Fall"}`. With both `year` and `term` (Winter, Spring, Summer, or Fall), the score uses that semester's course description; otherwise it uses the most recent offering. Add `sectionHint`, a section code like `"UAI"`, to pick a section without its title.

Course codes, with or without a section title, are at most 200 characters. A course listed twice in one transcript counts once.

## Response

`catalog.exportedAt` says when the course catalog behind the scores was built. It's refreshed each semester, and scores can shift slightly when it is, so compare scores computed against the same export. `results` has one entry per transcript, in request order:

- `id`: yours, or `null` if you didn't send one.
- `score`: 0 to 100, the same number the site shows. It's 0 when fewer than 2 courses matched.
- `k`: how many courses matched.
- `matched` and `unmatched`: normalized codes found and not found in the catalog. Transfer credit and courses not offered since 2004 end up in `unmatched`.
- `ambiguous`: codes offered only as titled sections, such as `CS 199`, which need a title or `sectionHint` to score.

## Errors

- **400**: the request is malformed. The body is `{"error": "..."}`, naming the offending entry, such as `transcripts[0].courses[1] must be a course code or {id, year?, term?, sectionHint?}`.
- **503**: the scoring catalog isn't loaded. Retry later.

## Example

```sh
curl -s https://www.ratemytranscript.com/api/v1/score \
  -H 'Content-Type: application/json' \
  -d '{
    "transcripts": [
      {
        "id": "student-1",
        "courses": [
          "CS 124",
          "MATH 241",
          "PHYS 211",
          "MUS 130"
        ]
      },
      {
        "id": "student-2",
        "courses": [
          {
            "id": "CS 124",
            "year": 2025,
            "term": "Fall"
          },
          "CS 128",
          "CS 225",
          "CS 199"
        ]
      }
    ]
  }'
```

returns (scores rounded here; the API returns full precision):

```json
{
  "catalog": {
    "exportedAt": "2026-09-10T20:28:15.221Z"
  },
  "results": [
    {
      "id": "student-1",
      "score": 73,
      "k": 4,
      "matched": [
        "CS 124",
        "MATH 241",
        "PHYS 211",
        "MUS 130"
      ],
      "unmatched": [],
      "ambiguous": []
    },
    {
      "id": "student-2",
      "score": 37,
      "k": 3,
      "matched": [
        "CS 124",
        "CS 128",
        "CS 225"
      ],
      "unmatched": [],
      "ambiguous": [
        "CS 199"
      ]
    }
  ]
}
```

The same request from Python:

```python
import requests

transcripts = [
    {"id": "student-1", "courses": ["CS 124", "MATH 241", "PHYS 211", "MUS 130"]},
    # ...one entry per student, up to 500 per request
]

response = requests.post(
    "https://www.ratemytranscript.com/api/v1/score",
    json={"transcripts": transcripts},
    timeout=120,
)
response.raise_for_status()
for result in response.json()["results"]:
    print(result["id"], round(result["score"], 1), result["unmatched"])
```

## Limits and Privacy

- No key is needed. A full batch of 500 transcripts takes several seconds, so send students together rather than one request each, and split larger sets into batches of 500.
- Nothing in a request is logged or stored. The API only needs course codes and semesters, never names, grades, or student numbers, so use an id that means nothing outside your records.
- Use only `/api/v1/score`. The site's other `/api/*` routes serve its own pages and can change without notice.

## Machine-Readable Versions

- [OpenAPI 3.1 specification](https://www.ratemytranscript.com/openapi.json)
- [This reference as a web page](https://www.ratemytranscript.com/developers)
- [llms.txt](https://www.ratemytranscript.com/llms.txt), an index for agents, also served as [ai.txt](https://www.ratemytranscript.com/ai.txt)
