{"id":"88e3f0fb-dd78-475c-a6aa-b5262a7ea30a","entityType":"agent","slug":"clawhub-chrischall-canvas-parent-api","name":"canvas-parent-api","canonicalUrl":"https://www.xpersona.co/agent/clawhub-chrischall-canvas-parent-api","canonicalPath":"/agent/clawhub-chrischall-canvas-parent-api","generatedAt":"2026-10-10T10:43:06.815Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-10T05:47:08.046Z","emptyReason":null},"description":"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed. Skill: canvas-parent-api Owner: chrischall Summary: Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MC","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.6K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:canvas-parent-api","sourceUrl":"https://clawhub.ai/chrischall/canvas-parent-api","homepage":"https://clawhub.ai/chrischall/skills/canvas-parent-api","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/chrischall/canvas-parent-api","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/chrischall/skills/canvas-parent-api","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":64,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, "},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:47:08.046Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:47:08.046Z","emptyReason":null},"stars":null,"forks":null,"downloads":1642,"packageName":null,"latestVersion":"2.1.9","tractionLabel":"1.6K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:47:07.934Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T05:47:08.046Z","lastCrawledAt":"2026-10-10T05:47:07.934Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T05:47:07.934Z","lastVerifiedAt":null,"highlights":[{"version":"2.1.9","createdAt":"2026-10-09T23:23:37.944Z","changelog":"- Removed the documentation file skill-card.md. - No user-facing or code changes; documentation only.","fileCount":4,"zipByteSize":7019},{"version":"2.1.8","createdAt":"2026-10-07T13:39:21.657Z","changelog":"- Removed the sample file skill-card.md from the repository. - No changes made to any functional documentation or code.","fileCount":4,"zipByteSize":7049},{"version":"2.1.7","createdAt":"2026-10-05T02:51:53.158Z","changelog":"- Removed the file: skill-card.md. - No changes to functionality or documentation in SKILL.md. - No impact to users or API usage.","fileCount":4,"zipByteSize":7057},{"version":"2.1.6","createdAt":"2026-10-03T01:40:49.847Z","changelog":"- Removed the file: skill-card.md - No changes to core functionality or documentation in SKILL.md - This update is purely a cleanup, removing unused or redundant markdown documentation","fileCount":4,"zipByteSize":6978},{"version":"2.1.5","createdAt":"2026-09-28T13:58:41.279Z","changelog":"- Removed the sample file skill-card.md. - No changes to skill functionality or documentation content. - This update is a minor cleanup to remove an unused file.","fileCount":4,"zipByteSize":6967},{"version":"2.1.4","createdAt":"2026-09-25T15:51:10.936Z","changelog":"- Removed the file: skill-card.md. - No changes to core functionality. - Documentation remains unchanged except for the file removal.","fileCount":4,"zipByteSize":6920},{"version":"2.1.3","createdAt":"2026-09-23T21:42:03.115Z","changelog":"- Removed the unused skill-card.md file. - Updated references/canvas-endpoints.md (details not shown). - No changes to code functionality or usage.","fileCount":4,"zipByteSize":7098},{"version":"2.1.2","createdAt":"2026-09-23T15:44:14.328Z","changelog":"- Removed the file: skill-card.md - No other changes to the code or documentation.","fileCount":4,"zipByteSize":7102}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:canvas-parent-api","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T10:43:06.813Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-canvas-parent-api/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-10T05:47:08.046Z","emptyReason":null},"readme":"Skill: canvas-parent-api\n\nOwner: chrischall\n\nSummary: Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\n\nTags: latest:2.1.9\n\nVersion history:\n\nv2.1.9 | 2026-10-09T23:23:37.944Z | auto\n\n- Removed the documentation file skill-card.md.\n- No user-facing or code changes; documentation only.\n\nv2.1.8 | 2026-10-07T13:39:21.657Z | auto\n\n- Removed the sample file skill-card.md from the repository.\n- No changes made to any functional documentation or code.\n\nv2.1.7 | 2026-10-05T02:51:53.158Z | auto\n\n- Removed the file: skill-card.md.\n- No changes to functionality or documentation in SKILL.md.\n- No impact to users or API usage.\n\nv2.1.6 | 2026-10-03T01:40:49.847Z | auto\n\n- Removed the file: skill-card.md\n- No changes to core functionality or documentation in SKILL.md\n- This update is purely a cleanup, removing unused or redundant markdown documentation\n\nv2.1.5 | 2026-09-28T13:58:41.279Z | auto\n\n- Removed the sample file skill-card.md.\n- No changes to skill functionality or documentation content.\n- This update is a minor cleanup to remove an unused file.\n\nv2.1.4 | 2026-09-25T15:51:10.936Z | auto\n\n- Removed the file: skill-card.md.\n- No changes to core functionality.\n- Documentation remains unchanged except for the file removal.\n\nv2.1.3 | 2026-09-23T21:42:03.115Z | auto\n\n- Removed the unused skill-card.md file.\n- Updated references/canvas-endpoints.md (details not shown).\n- No changes to code functionality or usage.\n\nv2.1.2 | 2026-09-23T15:44:14.328Z | auto\n\n- Removed the file: skill-card.md\n- No other changes to the code or documentation.\n\nv2.1.1 | 2026-09-21T04:16:41.326Z | auto\n\n- Removed the file skill-card.md.\n- No changes to main functionality or usage—just a file cleanup.\n\nv2.1.0 | 2026-09-20T02:52:18.936Z | auto\n\n- Removed the file: skill-card.md.\n- No changes made to SKILL.md content.\n\nv2.0.0 | 2026-09-17T23:35:44.081Z | auto\n\nVersion 2.0.0\n\n- Removed the skill-card.md file.\n- No changes to functionality or documentation in SKILL.md.\n- This update is a housekeeping release with only file cleanup.\n\nv1.5.6 | 2026-09-15T19:24:20.077Z | auto\n\n- Removed sample file: skill-card.md\n- No code or documentation changes to feature set or usage\n- No user-facing changes—maintenance cleanup only\n\nv1.5.5 | 2026-09-14T14:09:28.531Z | auto\n\n- Removed the sample file skill-card.md.\n- No functional or usage changes.\n\nv1.5.4 | 2026-09-10T17:49:20.374Z | auto\n\n- Removed the file skill-card.md.\n- No other changes to code or documentation.\n\nv1.5.3 | 2026-09-05T00:50:41.894Z | auto\n\n- Removed the file skill-card.md.\n- No changes to other functionality or documentation.\n\nv1.5.2 | 2026-09-04T22:19:37.207Z | auto\n\n- Removed the sample skill-card.md file.\n- No functional or documentation changes to the core API usage or instructions.\n\nv1.5.1 | 2026-09-02T20:38:00.193Z | auto\n\n- Removed the file skill-card.md.\n- No changes to functionality or documentation in SKILL.md.\n\nv1.5.0 | 2026-08-31T00:22:21.649Z | auto\n\n- Removed the sample file: skill-card.md.\n- No functional changes to the core skill; documentation and usage remain unchanged.\n\nv1.4.0 | 2026-08-29T13:54:06.223Z | auto\n\n- Removed the sample file skill-card.md.\n- No user-facing functionality changes; documentation and usage remain unchanged.\n\nv1.3.1 | 2026-08-28T21:06:54.779Z | auto\n\n- Removed the sample skill-card.md file.\n- No functional or documentation changes to the skill itself.\n\nv1.3.0 | 2026-08-28T11:34:04.276Z | auto\n\n- Removed the file skill-card.md.\n- No changes to the skill's code or documentation in SKILL.md.\n\nv1.2.5 | 2026-08-06T00:42:44.512Z | auto\n\n- Removed the file: skill-card.md\n- No code or behavior changes; documentation and usage remain the same.\n\nv1.2.4 | 2026-08-03T05:27:21.391Z | auto\n\n- Removed the file skill-card.md.\n- No user-facing functionality or documentation in SKILL.md was changed.\n\nv1.2.3 | 2026-08-03T04:33:41.445Z | auto\n\n- Removed the file skill-card.md.  \n- No changes to functionality or documentation.  \n- This update is maintenance/cleanup only.\n\nv1.2.2 | 2026-07-30T12:53:47.452Z | auto\n\n- Added detailed usage instructions for querying Canvas LMS API directly with curl and a bearer access token, without requiring the MCP server.\n- Documented one-time setup steps for authentication with either a personal access token or OAuth via mobile QR login.\n- Provided practical examples for API calls, including how to handle authentication headers, XSSI prefixes, and numeric ID handling.\n- Explained how to obtain required user and course IDs dynamically and how to adapt API paths for observer accounts.\n- Included guidance on handling API pagination and access token expiration.\n- Clarified use cases: obtaining data for courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files.\n\nArchive index:\n\nArchive v2.1.9: 4 files, 7019 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (2062b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.9:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.9:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.9\",\n  \"publishedAt\": 1791588217944\n}\n\nFile v2.1.9:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.9:skill-card.md\n\n## Description:\n\nGuides agents in querying a user's or linked student's Canvas LMS courses, grades, assignments, messages, and files through authenticated API calls without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, parents, and other authorized Canvas users can use this skill to retrieve course and student information from their institution's Canvas account in scripts or shell-based agent workflows.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Authenticated queries can expose sensitive Canvas account and student information.\n\nMitigation: Use a scoped token and grant access only to agents authorized to handle the account data.\n\nRisk: Evaluating QR-login helper output or running an unverified npx package can execute untrusted commands.\n\nMitigation: Review helper output instead of evaluating it and pin or verify the package before running it.\n\nRisk: Refresh tokens can leak through shared shells or logs, and file downloads can overwrite unintended destinations.\n\nMitigation: Keep tokens out of shared shells and logs; restrict downloads to a known safe folder and refuse overwrites.\n\n## Reference(s):\n\n- [Canvas API endpoint examples](references/canvas-endpoints.md)\n- [Canvas Parent API release on ClawHub](https://clawhub.ai/chrischall/skills/canvas-parent-api)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands return Canvas API JSON and may download course files when run.]\n\n## Skill Version(s):\n\n2.1.9 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.8: 4 files, 7049 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (2122b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.8:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.8:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.8\",\n  \"publishedAt\": 1791380361657\n}\n\nFile v2.1.8:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.8:skill-card.md\n\n## Description:\n\nGuides agents in querying Canvas LMS for courses, grades, assignments, messages, and files through authenticated shell commands without a running MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, parents, and other authorized Canvas users can use the skill to retrieve their own or a linked observee's learning information with direct Canvas API requests when the MCP server is unavailable or unnecessary.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: An agent with shell access to Canvas tokens can expose credentials or sensitive education records.\n\nMitigation: Limit access to authorized users, keep tokens and client secrets out of shell history and logs, and unset credentials when finished.\n\nRisk: The suggested unpinned npx command executes dynamically generated shell assignments.\n\nMitigation: Avoid the command unless the helper package has been verified and pinned; inspect its output before using it.\n\nRisk: File download commands can fetch an unexpected URL or write to an unintended destination.\n\nMitigation: Validate the download URL and destination before running the command, and avoid overwriting existing files.\n\n## Reference(s):\n\n- [Canvas API endpoint examples](references/canvas-endpoints.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/canvas-parent-api)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell command examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Canvas API responses can contain sensitive education records; access requires a valid bearer token.]\n\n## Skill Version(s):\n\n2.1.8 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.7: 4 files, 7057 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (2095b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.7:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.7:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.7\",\n  \"publishedAt\": 1791168713158\n}\n\nFile v2.1.7:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.7:skill-card.md\n\n## Description:\n\nGuides authorized Canvas LMS users in querying their own or linked students' courses, grades, assignments, and other school data with curl and a bearer token, without a running MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nStudents, parents with linked observees, and developers use this guide to retrieve authorized Canvas LMS information from a shell or script when an MCP server is unavailable or unnecessary.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Canvas access and refresh tokens can be exposed through shell history, logs, or shared output.\n\nMitigation: Keep credentials out of history and logs, share only redacted output, and revoke tokens when finished.\n\nRisk: Running an unpinned QR-login helper through shell eval can expose OAuth credentials or execute unexpected commands.\n\nMitigation: Pin or independently verify the helper before use, and avoid eval when handling its output.\n\nRisk: Sending an Authorization header to an unverified file URL can disclose a Canvas token.\n\nMitigation: Validate the destination URL before using it with authenticated curl commands.\n\n## Reference(s):\n\n- [Canvas API endpoints for curl](references/canvas-endpoints.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/canvas-parent-api)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Guidance]\n\n**Output Format:** [Markdown with shell code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Examples return Canvas API data after authentication; results depend on the user's access permissions.]\n\n## Skill Version(s):\n\n2.1.7 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.6: 4 files, 6978 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (1979b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.6:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.6:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.6\",\n  \"publishedAt\": 1790991649847\n}\n\nFile v2.1.6:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.6:skill-card.md\n\n## Description:\n\nGuides agents in querying Canvas LMS for a user or linked observee through authenticated shell requests without running an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nStudents, parents, and developers use this skill to retrieve their own or a linked student's Canvas courses, grades, assignments, messages, and files through the institution's API without an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Canvas access and refresh credentials could be exposed through shell history, logs, or insecure storage.\n\nMitigation: Keep credentials in a secure secret store, limit access to command output, and unset shell variables when finished.\n\nRisk: The suggested QR-login command executes an unpinned helper through eval.\n\nMitigation: Review and pin the helper before running it, and avoid eval where possible.\n\nRisk: Downloaded Canvas files and API responses may contain sensitive student or message data.\n\nMitigation: Keep downloaded files and command output private and share them only with authorized recipients.\n\n## Reference(s):\n\n- [Canvas API endpoint examples](references/canvas-endpoints.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/canvas-parent-api)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands can retrieve Canvas JSON, including sensitive student records and messages.]\n\n## Skill Version(s):\n\n2.1.6 (source: ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.5: 4 files, 6967 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (2005b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.5:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.5:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.5\",\n  \"publishedAt\": 1790603921279\n}\n\nFile v2.1.5:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.5:skill-card.md\n\n## Description:\n\nGuides users in querying Canvas LMS courses, grades, assignments, and other educational data for themselves or linked students using authenticated shell commands without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, students, and linked observers use this skill to retrieve permitted Canvas course information and student progress through the institution's API without running an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Running an unpinned QR-login helper through eval can execute unreviewed shell output.\n\nMitigation: Use a pinned or locally reviewed helper and avoid eval on untrusted output.\n\nRisk: Shell commands and long-lived refresh tokens can expose Canvas credentials or educational data.\n\nMitigation: Review commands before execution, limit access to trusted environments, and keep refresh tokens out of long-lived shell sessions.\n\nRisk: Downloaded Canvas files could overwrite existing local data.\n\nMitigation: Download only to trusted paths and check for existing files first.\n\n## Reference(s):\n\n- [Canvas API endpoint reference](references/canvas-endpoints.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/canvas-parent-api)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Guidance]\n\n**Output Format:** [Markdown with shell code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Authenticated Canvas API responses may include student records and grades.]\n\n## Skill Version(s):\n\n2.1.5 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.4: 4 files, 6920 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (1786b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.4:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.4:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.4\",\n  \"publishedAt\": 1790351470936\n}\n\nFile v2.1.4:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.4:skill-card.md\n\n## Description:\n\nQuery Canvas LMS for courses, grades, assignments, submissions, calendars, messages, and files using authenticated curl commands instead of an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nStudents, parents with linked observer access, and developers use this skill to retrieve authorized Canvas course information and files from a shell or script without running an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: OAuth setup runs a live package helper and evaluates its output while handling long-lived credentials.\n\nMitigation: Prefer a pinned, trusted helper; inspect its output and avoid evaluating live package output when possible.\n\nRisk: Canvas tokens and retrieved student data could be exposed through shared shells, logs, or unauthorized queries.\n\nMitigation: Keep tokens out of shared shells and logs, unset them after use, and access or download only data you are authorized to view.\n\n## Reference(s):\n\n- [Canvas API endpoints for curl](references/canvas-endpoints.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands require an institution-specific Canvas URL and authorized bearer token.]\n\n## Skill Version(s):\n\n2.1.4 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.3: 4 files, 7098 bytes\n\nFiles: references/canvas-endpoints.md (8882b), skill-card.md (2253b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.3:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.3:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.3\",\n  \"publishedAt\": 1790199723115\n}\n\nFile v2.1.3:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally only fetches `/files/` URLs on the\n`CANVAS_BASE_URL` host, only writes inside `CANVAS_OUTPUT_DIR` (default\n`~/Downloads`), refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.3:skill-card.md\n\n## Description:\n\nQuery Canvas LMS from a shell with curl and a bearer access token for courses, grades, assignments, submissions, calendar items, planner items, announcements, conversations, discussions, and files for yourself or a linked observee.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, engineers, and Canvas users use this skill to query Canvas LMS data directly from shell sessions without running the canvas-parent-mcp server. It is useful for scripted or ad hoc access to courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Canvas tokens, refresh tokens, client secrets, and downloaded files can expose sensitive education records if handled carelessly in shell history, logs, or shared paths.\n\nMitigation: Treat Canvas credentials as long-lived secrets, unset them after use, avoid logging Authorization headers, and validate file URLs and output destinations before downloading files.\n\nRisk: The OAuth QR-login helper is invoked through an eval/npx pattern that can execute unpinned package output in the user's shell.\n\nMitigation: Verify and pin the helper package before use, or manually inspect the generated environment assignments instead of evaluating them directly.\n\n## Reference(s):\n\n- [Canvas API endpoints for curl](references/canvas-endpoints.md)\n\n## Skill Output:\n\n**Output Type(s):** [guidance, markdown, shell commands, configuration]\n\n**Output Format:** [Markdown with inline bash code blocks and curl examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Includes endpoint patterns, environment variable setup, pagination guidance, jq projections, and credential-handling cautions.]\n\n## Skill Version(s):\n\n2.1.3 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.2: 4 files, 7102 bytes\n\nFiles: references/canvas-endpoints.md (8759b), skill-card.md (2388b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.2:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-trip wrong (JS `2^53` overflow).\n- **XSSI prefix** — some endpoints prepend `while(1);` to the JSON body.\n  Strip it before piping to `jq` (`sed 's/^while(1);//'`), or it'll fail to\n  parse.\n\n## Resolve-first rule: get IDs before detail\n\nMost detail/list-by-course endpoints need a `courseId` and (for parents) an\nobservee's user ID — get those first, then substitute them into the paths in\n`references/canvas-endpoints.md`:\n\n```sh\n# Your own profile (sanity-check the token works)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' | jq '{id, name, primary_email}'\n\n# Students linked to your observer account (empty array for a plain student token)\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' | jq '.[] | {id, name}'\n\n# Your (or an observee's) active courses, with grades\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name}'\n```\n\nFor an observee, swap `users/self` for `users/<observeeId>` in any path that\nstarts that way (per-user endpoints); course-scoped endpoints\n(`/api/v1/courses/<courseId>/...`) don't need the observee ID at all — Canvas\nscopes them to whichever user the token belongs to.\n\n## Pagination\n\nCanvas paginates with an RFC 5988 `Link` header, not a body field. Fetch\nheaders separately to follow `rel=\"next\"`:\n\n```sh\nurl=\"$CANVAS_BASE_URL/api/v1/courses/123/students/submissions?student_ids[]=self&per_page=100\"\nwhile [ -n \"$url\" ]; do\n  headers=$(curl -sD - -o page.json -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n    -H \"Accept: application/json+canvas-string-ids, application/json\" \"$url\")\n  sed 's/^while(1);//' page.json | jq -c '.[]' >> all.jsonl\n  url=$(echo \"$headers\" | grep -i '^link:' | grep -oE '<[^>]+>; rel=\"next\"' | grep -oE 'https?://[^>]+')\ndone\n```\n\nBumping `per_page` (Canvas allows up to 100) is usually enough for one-off\nqueries; only bother with the loop for genuinely large collections.\n\n## Auth expiry\n\n- **Personal token**: a 401 means it's expired or revoked — mint a fresh one\n  in the Canvas UI (tokens don't self-refresh).\n- **OAuth access token**: expires in ~1h — re-run the `refresh_token`\n  exchange above; the refresh token itself is long-lived.\n\nAll 18 endpoints (courses, assignments, submissions, grades, calendar,\nplanner, announcements, conversations, discussions, files) are in\n`references/canvas-endpoints.md` with the real query params and a `jq`\nprojection for each.\n\nFile v2.1.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.2\",\n  \"publishedAt\": 1790178254328\n}\n\nFile v2.1.2:references/canvas-endpoints.md\n\n# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&include[]=course&filter[]=submittable&course_ids[]=123&course_ids[]=456\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, course_id}'\n```\n\n*(paginated)*\n\n## 7. Single submission — rubric + grader comments\n\n`userId` defaults to `self`; pass a numeric Canvas user ID for an observee.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments/{assignmentId}/submissions/self?include[]=submission_comments&include[]=rubric_assessment&include[]=assignment\" \\\n  | sed 's/^while(1);//' | jq '{score, grade, submitted_at, submission_comments}'\n```\n\n## 8. Recent graded submissions in a course\n\n`student_ids[]` defaults to `self`; `graded_since` defaults to 14 days ago\n(ISO 8601, compute it yourself for curl — Canvas doesn't).\n\n```sh\nsince=$(date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)   # macOS date; use `date -u -d '14 days ago' ...` on GNU\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/students/submissions?student_ids[]=self&workflow_state[]=graded&graded_since=$since&include[]=assignment&include[]=submission_comments\" \\\n  | sed 's/^while(1);//' | jq '.[] | {assignment: .assignment.name, score, grade}'\n```\n\n*(paginated)*\n\n## 9. Enrollments — per-course grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/enrollments?state[]=active&type[]=StudentEnrollment&include[]=current_points&include[]=grades\" \\\n  | sed 's/^while(1);//' | jq '.[] | {course_id, grades}'\n```\n\n*(paginated)*\n\n## 10. Calendar events / assignments across contexts\n\n`context_codes[]` looks like `course_123`, `user_456` (repeat the param per\ncode). `type` is `event` or `assignment`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/calendar_events?type=assignment&start_date=2026-07-01&end_date=2026-07-31&context_codes[]=course_123\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, start_at}'\n```\n\n*(paginated)*\n\n## 11. Upcoming events — Canvas's curated next-7-days view\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/upcoming_events\" | sed 's/^while(1);//' | jq .\n```\n\n## 12. Planner items — assignments + announcements + notes + events\n\nOmit `{observeeId}` (use `/api/v1/planner/items`) for yourself.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/{observeeId}/planner/items?start_date=2026-07-01&end_date=2026-07-31\" \\\n  | sed 's/^while(1);//' | jq '.[] | {plannable_type, plannable_date: .plannable_date}'\n```\n\n*(paginated; self variant: `$CANVAS_BASE_URL/api/v1/planner/items?...`)*\n\n## 13. Announcements — across one or more courses\n\n`context_codes[]` is **required** (e.g. `course_123`); defaults to\n`active_only=true`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/announcements?context_codes[]=course_123&context_codes[]=course_456&active_only=true\" \\\n  | sed 's/^while(1);//' | jq '.[] | {title, posted_at}'\n```\n\n*(paginated)*\n\n## 14. Conversations (inbox) — list\n\n`scope` (optional): `unread` `starred` `archived` `sent`. `filter[]` is an\narray of context codes.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations?scope=unread&include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, subject, last_message}'\n```\n\n*(paginated)*\n\n## 15. Conversation detail — full thread\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/conversations/{id}?include[]=participant_avatars\" \\\n  | sed 's/^while(1);//' | jq '.messages[] | {author_id, body, created_at}'\n```\n\n## 16. Discussion topics — a course's list\n\n`only_announcements` defaults `false`; `order_by` (optional): `position`\n`recent_activity` `title`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/discussion_topics?only_announcements=false&order_by=recent_activity\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, title, posted_at}'\n```\n\n*(paginated)*\n\n## 17. Course files — metadata list\n\n`search_term` and `content_types[]` are optional filters.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/files?search_term=syllabus\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, display_name, url, \"content-type\"}'\n```\n\n*(paginated)*\n\n## 18. Download a file\n\nUse the absolute `url` field from §17 directly — it's already a\nfully-qualified, Bearer-authable Canvas URL (no `/api/v1` prefix needed):\n\n```sh\ncurl -sL -H \"Authorization: Bearer $CANVAS_TOKEN\" \"$FILE_URL\" -o /path/to/destination.pdf\n```\n\n`-L` follows Canvas's redirect to the actual file storage backend. The MCP's\n`canvas_download_file` tool additionally refuses to overwrite an existing\ndestination unless told to and validates the parent directory exists —\nreplicate that yourself in a script if it matters (`[ -f dest ] && exit 1`,\n`[ -d \"$(dirname dest)\" ] || exit 1`).\n\nFile v2.1.2:skill-card.md\n\n## Description:\n\nQuery Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server; it covers courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, engineers, and Canvas users use this skill to query Canvas LMS data directly with curl and bearer tokens when they need scriptable access without running the MCP server. It is especially relevant for retrieving course, grade, assignment, submission, calendar, announcement, conversation, discussion, and file data for an authorized user or linked observee.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill handles sensitive Canvas bearer tokens and student data that could be exposed through shell history, logs, copied commands, or unauthorized use.\n\nMitigation: Keep tokens out of logs and command history, scope use to Canvas accounts and observee records the user is authorized to access, and revoke or rotate tokens if exposure is suspected.\n\nRisk: The OAuth helper setup uses an unpinned npx command piped into eval, which can execute unexpected shell code if the package or command output is not independently trusted.\n\nMitigation: Avoid the eval+npx setup unless the helper package has been verified and pinned, or use a manually reviewed token setup path instead.\n\n## Reference(s):\n\n- [Canvas API endpoints for curl](references/canvas-endpoints.md)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, Configuration instructions, Code]\n\n**Output Format:** [Markdown with shell command examples and JSON/jq projections]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Produces command patterns and endpoint examples for authorized Canvas LMS access; it does not produce executable files.]\n\n## Skill Version(s):\n\n2.1.2 (source: release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.1.1: 4 files, 6910 bytes\n\nFiles: references/canvas-endpoints.md (8759b), skill-card.md (1944b), SKILL.md (6097b), _meta.json (136b)\n\nFile v2.1.1:SKILL.md\n\n---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks every call needs:\n\n- **`Accept: application/json+canvas-string-ids, application/json`** — without\n  it, large numeric IDs can silently round-tri\n\nArchive v2.1.0: 4 files, 7071 bytes\n\nFiles: references/canvas-endpoints.md (8759b), skill-card.md (2281b), SKILL.md (6097b), _meta.json (136b)","readmeExcerpt":"Skill: canvas-parent-api Owner: chrischall Summary: Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MC","codeSnippets":[],"executableExamples":[{"language":"sh","snippet":"export CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>"},{"language":"sh","snippet":"eval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\""},{"language":"sh","snippet":"curl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\"},{"language":"sh","snippet":"curl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'"},{"language":"sh","snippet":"curl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\"},{"language":"sh","snippet":"curl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq ."}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: canvas-parent-api\ndescription: \"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed.\"\n---\n\n# Canvas LMS via curl (no MCP)\n\nCanvas's REST API (`/api/v1/...`) is a normal per-institution API reachable\ndirectly with `curl` — no browser bridge, no bot wall. Every request needs an\n`Authorization: Bearer <token>` header; there is no anonymous access. The\nhost is **per-institution** (`https://<district>.instructure.com`, or a\nself-hosted domain) — always read it from `$CANVAS_BASE_URL`, never hardcode\none tenant.\n\nThis is the same data the `canvas_*` MCP tools return, reached with plain\n`curl` instead of a running server.\n\n## One-time setup — get a bearer token\n\nPick whichever your institution allows (mirrors `canvas-parent-mcp`'s\n`CANVAS_TOKEN` / OAuth modes — the session-cookie and fetchproxy modes need\nthe MCP or a browser and are out of scope here):\n\n**A. Personal access token (simplest, if allowed)**\n\nCanvas web UI → Account → Settings → **+ New Access Token**. Most\ninstitutions disable this for non-admins, so try B if it's not offered.\n\n```sh\nexport CANVAS_BASE_URL=https://<district>.instructure.com   # per-institution, no trailing slash\nexport CANVAS_TOKEN=<token from the UI>\n```\n\n**B. OAuth via the mobile QR-login flow**\n\nIn the Canvas mobile app: Account → **QR for Login** (or \"Pair with\nObserver/QR Login\"), scan it with any camera to get the URL it encodes\n(`https://sso.canvaslms.com/canvas/login?domain=...&code=...`). Exchange it\nonce for OAuth credentials with the helper this same repo ships (no MCP\nserver needs to be *running* — it's a one-off CLI).\n\nIt prints four `NAME=value` lines to stdout\n(`CANVAS_BASE_URL`/`CANVAS_CLIENT_ID`/`CANVAS_CLIENT_SECRET`/`CANVAS_REFRESH_TOKEN`)\n— **export them into the shell**, since the next `curl` reads them as env\nvars and this step can't be skipped:\n\n```sh\neval \"$(npx canvas-parent-mcp-qr-login \"<qr-url>\" | sed 's/^/export /')\"\n```\n\nThen mint (and later re-mint) a short-lived access token from the refresh\ntoken with `curl` directly — no need to re-scan the QR each time:\n\n```sh\ncurl -s -X POST \"$CANVAS_BASE_URL/login/oauth2/token\" \\\n  -d grant_type=refresh_token \\\n  -d client_id=\"$CANVAS_CLIENT_ID\" \\\n  -d client_secret=\"$CANVAS_CLIENT_SECRET\" \\\n  -d refresh_token=\"$CANVAS_REFRESH_TOKEN\" \\\n  | jq -r '.access_token'\n```\n\nExport the result as `CANVAS_TOKEN` (access tokens expire in ~1h — re-run\nthis when calls start 401ing).\n\n## Core call pattern\n\n```sh\ncurl -s \\\n  -H \"Authorization: Bearer $CANVAS_TOKEN\" \\\n  -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" \\\n  | sed 's/^while(1);//' | jq .\n```\n\nTwo quirks"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"canvas-parent-api\",\n  \"version\": \"2.1.9\",\n  \"publishedAt\": 1791588217944\n}"},{"path":"references/canvas-endpoints.md","content":"# Canvas API endpoints for curl\n\nAll 18 `canvas_*` MCP tools, transcribed from `src/tools/*.ts` in\n`canvas-parent-mcp`. Every call needs:\n\n```sh\n-H \"Authorization: Bearer $CANVAS_TOKEN\"\n-H \"Accept: application/json+canvas-string-ids, application/json\"\n```\n\nagainst `$CANVAS_BASE_URL` (per-institution, e.g.\n`https://<district>.instructure.com`). Pipe every response through\n`sed 's/^while(1);//'` before `jq` (Canvas's XSSI guard). List endpoints are\nmarked **paginated** — see the `Link`-header loop in `SKILL.md` for anything\nyou expect to run long; for a quick look just bump `per_page`.\n\n`{userSegment}` below means `users/self`, or `users/<observeeId>` to read a\nlinked observee's data instead (get IDs from §2).\n\n---\n\n## 1. Profile\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/profile\" | sed 's/^while(1);//' \\\n  | jq '{id, name, primary_email, login_id, locale, time_zone}'\n```\n\n## 2. Observees — students linked to your observer account\n\nEmpty array for a plain student token.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/users/self/observees?include[]=avatar_url\" | sed 's/^while(1);//' \\\n  | jq '.[] | {id, name}'\n```\n\n*(paginated)*\n\n## 3. Courses — active, with grades\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/courses?enrollment_state=active&state[]=available&include[]=total_scores&include[]=current_grading_period_scores&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, total_scores}'\n```\n\n*(paginated)*\n\n## 4. Single course — syllabus, teachers, term\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}?include[]=syllabus_body&include[]=teachers&include[]=term\" \\\n  | sed 's/^while(1);//' | jq '{id, name, syllabus_body, teachers, term}'\n```\n\n## 5. Assignments — a course's assignment list, with your submission inline\n\n`bucket` (optional): `past` `overdue` `undated` `ungraded` `unsubmitted`\n`upcoming` `future`.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/courses/{courseId}/assignments?include[]=submission&order_by=due_at&bucket=upcoming\" \\\n  | sed 's/^while(1);//' | jq '.[] | {id, name, due_at, submission: .submission.workflow_state}'\n```\n\n*(paginated)*\n\n## 6. Missing submissions — past-due, unsubmitted\n\nFor an observee, `course_ids[]` (repeat the param per ID) is required.\n\n```sh\ncurl -s -H \"Authorization: Bearer $CANVAS_TOKEN\" -H \"Accept: application/json+canvas-string-ids, application/json\" \\\n  \"$CANVAS_BASE_URL/api/v1/{userSegment}/missing_submissions?include[]=planner_overrides&"},{"path":"skill-card.md","content":"## Description:\n\nGuides agents in querying a user's or linked student's Canvas LMS courses, grades, assignments, messages, and files through authenticated API calls without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, parents, and other authorized Canvas users can use this skill to retrieve course and student information from their institution's Canvas account in scripts or shell-based agent workflows.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Authenticated queries can expose sensitive Canvas account and student information.\n\nMitigation: Use a scoped token and grant access only to agents authorized to handle the account data.\n\nRisk: Evaluating QR-login helper output or running an unverified npx package can execute untrusted commands.\n\nMitigation: Review helper output instead of evaluating it and pin or verify the package before running it.\n\nRisk: Refresh tokens can leak through shared shells or logs, and file downloads can overwrite unintended destinations.\n\nMitigation: Keep tokens out of shared shells and logs; restrict downloads to a known safe folder and refuse overwrites.\n\n## Reference(s):\n\n- [Canvas API endpoint examples](references/canvas-endpoints.md)\n- [Canvas Parent API release on ClawHub](https://clawhub.ai/chrischall/skills/canvas-parent-api)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands return Canvas API JSON and may download course files when run.]\n\n## Skill Version(s):\n\n2.1.9 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MCP isn't installed. Skill: canvas-parent-api Owner: chrischall Summary: Query Canvas LMS (Instructure) from a shell with curl and a bearer access token instead of running the canvas-parent-mcp server — courses, grades, assignments, submissions, calendar, planner, announcements, conversations, discussions, and files for yourself or a linked observee. Use when you want Canvas data without the MCP, in a script, or on a machine where the MC","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1401,"uniquenessScore":45,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T05:47:08.046Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T05:47:08.046Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T10:43:06.815Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}