Install
openclaw skills install @chrischall/alphaportal-mcpAccess AlphaPortal (AlphaRoute) school-bus data — students, stops, live bus GPS location, arrival notifications — from a shell with curl instead of running the alphaportal-mcp server. Capture the signed-in web app's refresh token ONCE (a paste-in-console one-liner, or the fpx browser bridge), then mint access tokens and curl the REST API directly. Use when you want AlphaPortal data without the MCP, in a script, or on a machine where the MCP isn't installed.
openclaw skills install @chrischall/alphaportal-mcpAlphaPortal's app (e.g. cmsnc.alphaportal.app — the cmsnc subdomain is the
school district) has no bot wall on its API (api.alpharoute.app): once you
hold a token, plain curl does every call — no browser extension at runtime.
Login itself is reCAPTCHA-gated and can't be scripted, so the credential is the
refresh token the web app stores in your signed-in browser. It's an 8-day,
reusable JWT; from it you mint a 30-minute access token with one unauthenticated
POST (no reCAPTCHA, no MFA).
So the flow is: capture the refresh token once → mint an access token → curl.
Option A — paste-in-console (no fpx needed). In a signed-in AlphaPortal tab, open DevTools → Console and run:
JSON.parse(localStorage.user).User.RefreshToken
Copy the value into your shell:
export ALPHAPORTAL_RT='<paste the refresh token>'
Option B — fpx browser bridge (needs the Transporter extension + a signed-in
*.alphaportal.app tab):
npm install -g @fetchproxy/cli # provides `fpx`
fpx profile add alphaportal --domain alphaportal.app
fpx profile declare alphaportal --local-storage user # declare scope BEFORE first pairing
fpx local-storage user -p alphaportal # first call prints a pair code → approve in Transporter
export ALPHAPORTAL_RT=$(fpx local-storage user -p alphaportal | jq -r '.user | fromjson | .User.RefreshToken')
export ALPHAPORTAL_TOKEN=$(
curl -s -X POST https://api.alpharoute.app/AlphaCore/v1/public/refresh-token \
-H 'Content-Type: application/json' \
-d "{\"refreshToken\":\"$ALPHAPORTAL_RT\"}" | jq -r '.data.token'
)
If ALPHAPORTAL_TOKEN is null, the refresh token is expired/invalid —
re-capture it (step 1).
Every read is a GET with the bearer token; the envelope is {success,data,message},
so pipe .data to jq.
alpha() { curl -s "https://api.alpharoute.app/$1" -H "Authorization: Bearer $ALPHAPORTAL_TOKEN"; }
# your students (grab each studentId here first)
alpha AlphaPortal/v1/user-students/list | jq '.data.students[] | {studentId, name, gradeName}'
# live bus GPS for a student's PM run (shift 0=AM, 1=PM)
alpha AlphaPortal/v1/user-students/vehicle-location/218652901/1 | jq '.data.location'
# arrival/departure notifications
alpha AlphaPortal/v1/notifications/list | jq '.data.notifications[] | {title, body, creationDate}'
See references/endpoints.md for the full endpoint list, path variables, and
ready-to-run jq recipes.
401 — the access token expired (re-run step 2) or the refresh token died
(re-capture, step 1).400 with developerMessage — a required param is missing (e.g. a path
studentId, or studentId/radius on a write).success:false in a 200 body — the API rejected the request; read
.developerMessage / .message.Write endpoints (radius-edit, setnotification) exist and change real
transportation records; the alphaportal-mcp server gates them behind a
confirm dry-run. This shell skill has no dry-run — curl just does it — so
treat every write as real and permanent. Request bodies are in
references/endpoints.md. The transportation-request submission flow is
deliberately undocumented here (its body was not fully verified).