Developer API — Preview

Start building with Magisterial

Everything you need to integrate college sports data into your product — from your first API call to production.

Base URL https://api.magisterial.ai/v1

curl https://api.magisterial.ai/v1/players/search \
  -H "Authorization: Bearer mag_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "sport": "soccer",
    "gender": "men",
    "division": "D1"
  }'

Quick start

Get an API key from the developer console, then make your first request. Keys are created and managed from your account — sign in and the console opens straight to key management.

1Create an API key

Open the console, create a key, and copy it — the full token is shown only once.

2Make your first request
curl https://api.magisterial.ai/v1/sports \
  -H "Authorization: Bearer mag_live_xxxxxxxxxxxxxxxxxxxx"
3Explore the API reference

Every endpoint, parameter, and response schema — with runnable requests and generated code samples — lives in the interactive API reference.

Introduction

The API is organized around predictable, resource-oriented URLs. It accepts JSON request bodies, returns JSON responses, and uses standard HTTP verbs, status codes, and authentication. Every response you receive is scoped to exactly the sports and divisions your plan includes — the same data you see inside the Magisterial app.

Authentication

Authenticate with an API key sent as a bearer token in the Authorization header. Keys are created and rotated from your account settings. Live keys are prefixed mag_live_ and test keys mag_test_. Test keys call the free read endpoints for real and are blocked from usage-billed endpoints, so they are safe to experiment with; live keys carry your plan’s full access. Treat keys like passwords, and never embed a live key in client-side code.

curl https://api.magisterial.ai/v1/players/search \
  -H "Authorization: Bearer mag_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "sport": "soccer", "gender": "men", "division": "D1" }'

Requests without a valid key return 401 Unauthorized.

Scoping & access

Access is governed per sport and per division by your subscription tier. Every request must declare a scope — a sport, gender, and division — and results never span beyond what your plan entitles you to. A request for a division you don’t hold returns 403 Forbidden with the required tier, rather than silently returning nothing.

Multi-division scope. NCAA divisions can be combined into one query — pass "D1,D2,D3" to search across all three at once. NAIA and NJCAA are separate associations and cannot be combined with NCAA divisions.

Rate limits

Every account may make up to 120 requests per minute across all /v1 endpoints, shared by all of its keys. Each response reports where you stand:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
X-RateLimit-Reset: 1751990460

X-RateLimit-Reset is the Unix time (seconds) when the window resets. Exceeding the limit returns 429 Too Many Requests with a Retry-After header (seconds to wait) and the standard error body:

{
  "error": {
    "type": "rate_limited",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded (120 per 1 minute). Wait for the window to reset and retry."
  }
}

Pagination & errors

List endpoints are cursor-paginated. Pass limit (max 100) and follow next_cursor from each response until it is null.

{
  "data": [ /* ... */ ],
  "next_cursor": "eyJvZmZzZXQiOjEwMH0",
  "has_more": true
}

Errors use standard status codes and a consistent body:

{
  "error": {
    "type": "forbidden",
    "code": "tier_required",
    "message": "This division requires a Pro plan or higher for soccer.",
    "required_tier": "pro"
  }
}

Usage-billed endpoints (portal, query, alerts, exports) add billing errors: 403 api_billing_not_enabled until billing is enabled in the console, 403 api_billing_past_due after a failed invoice, and 402 budget_exceeded once month-to-date spend reaches your budget cap (the response includes budget_usd and spent_usd). Requests made with a test key return 403 test_key_paid_endpoint on those endpoints.

Versioning & deprecation

The API is versioned by URL prefix; /v1 is the current and only version. Additive changes — new endpoints, new response fields, new optional parameters, new enum values — ship without notice, so write clients that tolerate unknown fields. Breaking changes get at least 90 days’ notice on the changelog and by email, and affected responses carry Deprecation and Sunset headers you can alert on.

Read the full policy and release history on the changelog.

API reference & spec

The complete endpoint-by-endpoint contract lives in the interactive API reference — every endpoint, parameter, response schema, and error shape, with runnable requests and generated code samples in cURL, Python, JavaScript, and more. It is rendered from the API’s published OpenAPI 3.1 document, so it can never drift from what the API actually does.

The spec itself is public — no authentication required:

curl https://api.magisterial.ai/v1/openapi.json

Import it into Postman, generate a typed client with your SDK generator of choice, or hand it to an LLM agent as the contract for this API. For agents there is also a single-file markdown rendering of the whole contract — auth, conventions, every endpoint and object shape — sized to fit in a model’s context:

curl https://api.magisterial.ai/v1/llms.txt

Official SDKs

Official client libraries for Python and TypeScript/JavaScript. Both are fully typed — every model is generated from the published OpenAPI spec, so the SDKs cannot drift from the live API — and both handle the plumbing for you: automatic cursor pagination, retries, and a one-call submit-and-poll helper for natural-language queries.

Python
pip install magisterial
GitHub · PyPI · Python 3.10+, sync & async
TypeScript / JavaScript
npm install magisterial
GitHub · npm · Node 18+, zero dependencies

Full SDK guide — quickstart, pagination, errors, and method reference →

Players

Players are the heart of the dataset: profiles across seven sports and every division — NCAA D1 through D3, NAIA, and NJCAA — each carrying identity, position, class year, hometown, season-by-season statistics, and accolades. Where an athlete has claimed their profile, it is marked verified.

Search is built for evaluation workflows: filter by scope, team, conference, position, or class year, and rank by any stat the sport carries. It is the foundation for scouting tools, roster intelligence, and player-facing products alike.

Endpoints and schemas in the API reference →

Persons & careers

A player record belongs to one program; athletes don’t. Persons are the durable identity behind the player records — when an athlete transfers, redshirts, or resurfaces two divisions away, their stints are tied together into a single career you can follow from first season to last, across schools and across divisions.

Transfer history is part of that record: every school change, where it came from and where it landed. That is what makes longitudinal work possible — recruiting diligence on where a prospect has been, how production traveled with them, and what a roster’s movement says about the program.

Endpoints and schemas in the API reference →

Teams & coaches

Teams are the program-level view: every program in scope, with its conference, division, current roster, and season-by-season records. Where players answer “who should I look at,” teams answer “what am I up against” — opponent scouting, conference dashboards, and program tracking all start here.

Coaching staffs complete the picture: who runs a program, what their role is, and — through career records that span schools — where they coached before. Programs are ultimately run by people, and coaching changes ripple into recruiting, transfers, and style of play.

Endpoints and schemas in the API reference →

Games

Games are the ground truth beneath the season aggregates: schedules and results, box scores, and — where our coverage includes it — full play-by-play. Season stat lines tell you what happened on average; games tell you what happened on Tuesday night against the conference leader.

That granularity is what makes deeper work possible: verifying a stat line against the games behind it, computing your own metrics from raw events, or building anything that lives at the level of a single match — recaps, win probability, momentum, matchup analysis.

Endpoints and schemas in the API reference →

Transfer portal

The transfer portal is where college rosters are made and unmade, and speed matters: by the time a name circulates, the conversation has often already started. The API serves the live NCAA feed — who entered, from which program, with what status — continuously refreshed and filterable by your sport and division scope.

It is designed for incremental workflows: poll for what is new since your last check rather than re-reading the world. For accounts with a Pro or Max plan in the sport, entries also carry contact information — the same carve-out as the in-app portal. Usage-billed per request.

Endpoints and schemas in the API reference →

Webhook alerts

Alerts turn the portal from something you check into something that reaches out to you. Declare what you are watching for once — a sport, a division, a position, any filter the catalog supports — and the alert is evaluated against the live feed as it refreshes, collecting the entries that match.

Matches can be polled from the API or delivered to your webhook endpoint, so the moment a left-footed center-back enters the portal, your Slack channel, CRM, or recruiting board already knows. Standing alerts are the automation layer of the portal — billed per active alert per month, not per check. See webhook delivery below for event shapes, signatures, and retries.

Endpoints and schemas in the API reference →

Webhook delivery

Webhooks push events to your server the moment they happen — no polling. Register endpoint URLs in the console, subscribe each one to the event types you care about, and verify every delivery with its signing secret. Delivery logs, test pings, and manual redelivery live in the console too.

EventFires when
alert.triggeredA watch alert found new portal matches (one event per alert per evaluation cycle; matches are batched).
query.completedAn async /v1/query run reached done, error, or cancelled.
pingYou pressed “Send test” in the console.

Want a raw portal firehose? Create an alert with broad filters (a whole division, no position filter) and subscribe an endpoint to alert.triggered — every new portal entry in that scope arrives as a match event.

Delivery payload

Every delivery is an HTTP POST with a JSON body. Respond with any 2xx within 10 seconds; anything else is retried with backoff (30s, 2m, 10m, 30m, 2h, 6h, 12h, 24h — 8 attempts over roughly two days). The Magisterial-Webhook-Id header carries the event id — treat it as your idempotency key, since retries and manual redeliveries reuse it. An endpoint that exhausts retries 10 times in a row is auto-disabled until you re-enable it in the console.

{
  "id": "0d4f0c9e-1b2a-4c3d-8e5f-6a7b8c9d0e1f",
  "type": "alert.triggered",
  "created_at": "2026-07-21T18:00:12+00:00",
  "data": {
    "alert_id": "a3e8c2d1-77b4-4f0e-8d21-0b9c8e7f6a5d",
    "alert_name": "D1 women's soccer goalkeepers",
    "match_count": 1,
    "matches": [
      {
        "match_id": 99012,
        "matched_at": "2026-07-21T18:00:11+00:00",
        "player": {
          "name": "Nakamura, Yui",
          "school": "Santa Clara University",
          "division": "D1",
          "sport_path": "womens-soccer"
        }
      }
    ]
  }
}

Verifying signatures

Each delivery carries a Magisterial-Signature header of the form t=<unix-ts>,v1=<hex>, where v1 is the HMAC-SHA256 of {t}.{raw_body} keyed with the endpoint’s whsec_ secret. Verify against the raw bytes (before any JSON parsing), compare with a constant-time function, and reject timestamps older than a few minutes to block replays.

import hashlib, hmac, time

def verify(secret: str, signature_header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp, received = parts["t"], parts["v1"]
    if abs(time.time() - int(timestamp)) > 300:  # 5-minute tolerance
        return False
    expected = hmac.new(
        secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, received)

# In your handler (FastAPI shown; use the RAW request body):
#   sig = request.headers["Magisterial-Signature"]
#   assert verify(WEBHOOK_SECRET, sig, await request.body())
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, signatureHeader, rawBody) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("=", 2)),
  );
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(`${parts.t}.`)
    .update(rawBody) // Buffer of the raw request body
    .digest();
  const received = Buffer.from(parts.v1, "hex");
  return expected.length === received.length && timingSafeEqual(expected, received);
}

Natural-language query

Sometimes the question doesn’t map cleanly onto filters: “Who led the NESCAC in assists this season?” “Which D2 programs lost their starting goalkeeper to the portal?” The query endpoint takes the question in plain English and puts an agent to work on it — researching across players, teams, games, and the portal within your scope, and coming back with an answer.

Runs are asynchronous: submit, get a run id, poll until it completes (typically under a minute). It is the fastest way to answer exploratory questions without writing search logic, and the building block for conversational features in your own product. Usage-billed by what the run consumes.

Endpoints and schemas in the API reference →

Bulk exports

When you need the dataset rather than a page of it — loading a warehouse, training a model, running offline analysis — walking a paginated endpoint cursor by cursor is the wrong tool. Exports are the bulk lane: one asynchronous job writes an entire dataset in your scope — players, teams, games, or coaches — to a compressed CSV or JSONL file.

Start the job, poll it, and collect a signed download URL when it finishes. Billed by result size on success only — a failed export never costs anything.

Endpoints and schemas in the API reference →

This is a preview of the v1 surface. Endpoints and shapes may change before general availability — breaking changes follow the deprecation policy. See plans for included divisions and rate limits.