# Arizona State University (ASU) Class Schedule Scraper (`misty_ravel/arizona-state-university-class-scraper`) Actor

Scrapes every class section at Arizona State University (ASU): meeting days and times, instructor, building and room, instruction mode, and live enrollment vs capacity. All ASU campuses plus ASU Online, current and past terms.

- **URL**: https://apify.com/misty\_ravel/arizona-state-university-class-scraper.md
- **Developed by:** [Brian Webster](https://apify.com/misty_ravel) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Arizona State University (ASU) Class Schedule Scraper

Scrapes **every class section at Arizona State University** — meeting days and times, instructor, building and room, instruction mode, and **live enrollment against capacity** — from the public class-search service behind [catalog.apps.asu.edu](https://catalog.apps.asu.edu/catalog/classes).

Arizona State University publishes no bulk download and no documented API for its class schedule. The data exists only inside a single-page app at catalog.apps.asu.edu, one subject at a time.

Covers **all Arizona State University campuses** — Tempe, West Valley, Polytechnic and Downtown Phoenix — plus **ASU Online**, across every subject, for the current term and past ones.

### What you get

One row per **section**, not per course — so a lecture and its six recitations are six rows, each with its own time, room, instructor and seat count.

| Field | Example |
| --- | --- |
| `courseKey`, `title` | `CSE 100`, `Principles of Programming with C++` |
| `section`, `classNumber` | `1001`, `70123` |
| `days`, `daysList` | `M W F`, `["M","W","F"]` |
| `startTime`, `endTime` | `12:20 PM`, `1:10 PM` |
| `buildingName`, `room` | `PSH`, `PSH152` |
| `instructors` | `Yoshihiro Kobayashi` |
| `enrolled`, `capacity`, `seatsOpen` | `167`, `170`, `3` |
| `waitlisted`, `waitlistCapacity` | `0`, `20` |
| `instructionMode`, `campus` | `OL`, `TEMPE` |
| `units`, `unitsMax`, `component` | `3`, `3`, `Lecture` |
| `startDate`, `endDate`, `enrollDeadline` | `2026-08-20`, `2026-12-14`, `2026-08-21` |
| `generalStudiesGold`, `generalStudiesMaroon` | `SCIT`, `SQ` |
| `gradingBasis`, `consent` | `Student Option`, \`\` |
| `validFrom`, `observedAt` | `2007-08-01`, `2026-08-24T…` |
| `syllabusUrl`, `sourceUrl` | links back to ASU |

Set **Include raw API record** to attach the full ~145-field upstream object under `raw` if you need something this misses.

#### General studies: Gold and Maroon are two different curricula

ASU runs two general-studies programmes side by side, and which one governs a
student depends on their **catalog year** — Gold from 2024 onward, Maroon
before it. They are not two spellings of one value and their code sets do not
overlap: ENG 130 is `HUAD` under Gold and `L` under Maroon; CSE 180 is `QTRS`
and `CS`. Across ASU's full catalogue, 1,192 courses carry both designations and
**every one of them differs**.

So filter on `generalStudiesGold` or `generalStudiesMaroon`, not on the legacy
joined `generalStudies` string, which cannot tell you which curriculum a
designation belongs to.

These come from ASU's course catalogue rather than the class record — a section
carries 145 fields and not one of them is a designation — so the actor fetches
them per subject and joins them on. If that lookup fails the sections are still
returned; the designation is enrichment, not payload.

#### Dates: when it was true, and when we looked

`validFrom` is the **term's** start date; `observedAt` is when the row was
scraped. Keeping them apart matters as soon as you pull more than one term: a
run covering twenty years stamps every row with today's scrape time, so
filtering on that would treat a 2007 section as 2026 information. `validFrom` is
derived from the term code, which is exact — never estimated.

### Input

Everything is optional. Run it with no input and you get the whole current term.

| Field | Default | Notes |
| --- | --- | --- |
| `term` | current term | ASU term code, e.g. `2267` = Fall 2026. Past terms work too. |
| `terms` | — | Several at once, e.g. `["2267","2261","2257"]`. ASU publishes **62 terms back to Fall 2007**, so this is how you pull a series and see what changed. |
| `subjects` | all | e.g. `["CSE","MAT"]`. Empty means every subject. |
| `campus` | all + online | Tempe, West Valley, Polytechnic, Downtown Phoenix, ASU Online, or campus-only |
| `maxItems` | 0 (no limit) | Cap for a cheap trial run |
| `requestDelayMs` | 600 | Politeness delay between requests |
| `includeRaw` | false | Attach the upstream record |

An invalid `term` fails immediately with a list of valid codes, rather than quietly returning nothing.

```json
{
  "term": "2267",
  "subjects": ["CSE", "MAT"],
  "campus": "TEMPE",
  "maxItems": 500
}
```

### What people use this Arizona State University data for

- **Seat-availability monitoring** — schedule a run and diff `seatsOpen` to catch a section opening up.
- **Schedule planning tools** — days, times and rooms are what a conflict-checker needs.
- **Enrollment demand research** — `enrolled/capacity` across Arizona State University terms shows which subjects are growing.
- **Space utilisation** — building and room on every meeting.
- **Instructor course loads** — sections per instructor per term.

### Notes on access and etiquette

The actor sends exactly what a logged-out browser sends. There is no account, no credential, and no login.

Arizona State University's class search is public, and the API behind it accepts anonymous requests in a slightly surprising way that's worth stating plainly: every endpoint returns `401` when a request carries **no** `Authorization` header, but the single-page app — which never obtains a token for anonymous visitors — builds the header unconditionally as `"Bearer " + sessionStorage.getItem(...)`. With nothing stored that is `null`, so every anonymous visitor's browser sends the literal string `Bearer null`, and the gateway accepts it. It checks that the header is present, not that it is valid. This actor sends the same string.

`/robots.txt` on the API host returns the SPA shell, so there are no published crawl rules to honour. The actor therefore self-throttles (600 ms between requests by default) and identifies itself in the `User-Agent`. Please leave both alone unless you have a reason.

Only public catalog data is collected. No student data of any kind is exposed by this endpoint.

### Reliability

`term`, `subjects` and `classes` are separate calls, and the actor fails fast on a bad term rather than returning an empty dataset. Individual subjects that error are logged as warnings and skipped so one bad subject cannot lose a run. `429` and `5xx` get bounded exponential backoff. Sections are de-duplicated on `term:classNumber`, because the API's scroll cursor can repeat records across pages.

A run summary is written to the key-value store under `RUN_SUMMARY` with the term, subject counts, failures and total sections.

If ASU changes the access pattern the actor throws a specific error naming the SPA bundle to re-read, rather than silently producing nothing.

#### Why it walks subject by subject

It looks wasteful. ASU's course endpoint accepts a term with no subject filter
and answers in a single request, which would replace hundreds of calls with one.

Measured on term 2267, that shortcut returns **9,363 records across 215 of 343
subjects**, where the per-subject walk returns **14,833 across all 343**. It is a
server-side result cap, and it does not error — it returns HTTP 200 with a third
of the catalogue missing. Failing by quietly returning less is the failure mode
that gets believed, so the actor pays the requests.

Please do not "optimise" this away without re-measuring both totals.

### Scale

A full-term run covers every subject and takes a while at the default delay. Use `subjects` or `maxItems` to trial it first.

### Local development

```bash
npm install
npm run smoke     # exercises the live API, no Apify platform needed
apify run         # requires: npm install -g apify-cli
```

`src/asu.js` has no Apify dependency, so the client and the normaliser can be reused or tested outside the platform.

***

Not affiliated with or endorsed by Arizona State University. All data is public information published by Arizona State University at catalog.apps.asu.edu.

# Actor input Schema

## `term` (type: `string`):

Arizona State University term code, e.g. 2267 for Fall 2026. Leave empty to use the current ASU term. Past terms work too. The run fails fast with a list of valid codes if this one is not offered. To scrape several terms in one run, use Term codes (several) below.

## `terms` (type: `array`):

Scrape more than one term in a single run, e.g. 2267, 2261, 2257. Arizona State University publishes 62 terms going back to Fall 2007, so this is how you pull a series and compare how offerings changed rather than only seeing what is scheduled now. Combined with the single Term code field if you set both. Each term costs a full pass over the subject list, so a long series takes proportionally longer.

## `subjects` (type: `array`):

Arizona State University subject codes to scrape, e.g. CSE, MAT, PHY. Leave empty to scrape every subject offered at ASU in the term.

## `campus` (type: `string`):

Which Arizona State University campuses to include — Tempe, West Valley, Polytechnic, Downtown Phoenix, or ASU Online.

## `maxItems` (type: `integer`):

Stop after this many sections. 0 means no limit. Useful for a cheap trial run.

## `requestDelayMs` (type: `integer`):

Politeness delay. ASU publishes no crawl rules for this host, so the default is deliberately conservative. Lower it at your own discretion.

## `includeRaw` (type: `boolean`):

Attach the full upstream record (~145 fields) to each row under `raw`. Off by default — the flattened fields cover almost every use.

## `userAgent` (type: `string`):

Sent with every request to Arizona State University. Please keep it identifying rather than impersonating a browser.

## Actor input object example

```json
{
  "term": "2267",
  "terms": [
    "2267",
    "2261"
  ],
  "subjects": [
    "CSE"
  ],
  "campus": "A",
  "maxItems": 0,
  "requestDelayMs": 600,
  "includeRaw": false,
  "userAgent": "apify-asu-class-scraper/1.0 (+https://apify.com/)"
}
```

# Actor output Schema

## `sections` (type: `string`):

Every section scraped, one item each. A course with a lecture and six recitations produces seven items.

## `sectionsCsv` (type: `string`):

The same rows as CSV, for spreadsheets and BI tools.

## `runSummary` (type: `string`):

Terms covered, subjects requested, subjects that failed, and total sections. Read this before trusting a count: a run where subjects failed has holes, and the summary is where that is visible.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "terms": [
        "2267",
        "2261"
    ],
    "subjects": [
        "CSE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("misty_ravel/arizona-state-university-class-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "terms": [
        "2267",
        "2261",
    ],
    "subjects": ["CSE"],
}

# Run the Actor and wait for it to finish
run = client.actor("misty_ravel/arizona-state-university-class-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "terms": [
    "2267",
    "2261"
  ],
  "subjects": [
    "CSE"
  ]
}' |
apify call misty_ravel/arizona-state-university-class-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,misty_ravel/arizona-state-university-class-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/PrZUhgMfmXAd0yvvO/builds/8B9ripUPb7ksNSRCW/openapi.json
