Skip to content
datastudyguides

Developers

REST API and MCP reference

Read exam outlines and published content, validate and submit feedback or a study module. No key, no account.

Authentication
Reading, feedback and study modules need none. Your own account endpoints and submitting a course need a session cookie (same-origin browser requests) or a personal key, either as Authorization: Bearer <key> or in the MCP address itself, /mcp/k/<key>.
CORS
Open: Access-Control-Allow-Origin: * on every response, including the MCP endpoint.
Review
A person reviews every submission. Nothing is published automatically.
Format
JSON over HTTPS for REST, JSON-RPC 2.0 over HTTPS for MCP.

REST endpoints

https://datastudyguides.com

Method Path What it does
GET /api/v1/exams.json Every exam on the site, with status and counts.
GET /api/v1/exams/{exam}.json One exam's outline: domains, skill groups, objectives, and what each objective already has.
GET /api/v1/exams/{exam}/content.json Published study cards and questions. Only exists for an exam that already has published questions.
GET /api/v1/schema/module.json JSON Schema of a study module, generated from the same zod schema the API validates with.
GET /contribute/guide.md The contributor guide as plain markdown.
POST /api/v1/feedback Send feedback about a question, a card, the site, or a community course.
POST /api/v1/modules?dry_run=1 Validate a study module. Stores nothing, always answers 200.
POST /api/v1/modules Submit a study module for review.
GET /api/v1/submissions/{id} Status of something you submitted.
GET /api/v1/me Who you are, if signed in, and which sign-in providers are enabled. Never an error when signed out.
PATCH /api/v1/me Change your display name. Session cookie only.
GET /api/v1/me/keys List your personal API keys (never the key itself, only metadata).
POST /api/v1/me/keys Create a personal API key. Session cookie only; shows the key once.
DELETE /api/v1/me/keys/{id} Revoke a personal API key.
GET /api/v1/courses List community courses. `?exam=`, `?sort=top|new`, or `?mine=1` with your key for your own, any status.
POST /api/v1/courses?dry_run=1 Validate a course. Stores nothing, no account needed.
POST /api/v1/courses Submit a course. Needs a personal key or session cookie.
GET /api/v1/courses/{id} Read a course: the listing fields plus `yourVote`, `mine`, and the rendered `bank`.
PUT /api/v1/courses/{id} Replace a course you own. The automatic check runs again.
PATCH /api/v1/courses/{id} Change part of a course you own: `set`, `cards`, `removeCards`, `questions`, `removeQuestions`. The rest stays as it is.
DELETE /api/v1/courses/{id} Remove a course you own.
POST /api/v1/courses/{id}/vote Thumbs up (`value: 1`), thumbs down (`-1`), or take your vote back (`0`). No account needed.

Read an exam's outline

curl -s https://datastudyguides.com/api/v1/exams/dp-900.json

Answers with { exam, counts, domains: [{ id, title, weight, groups: [{ id, title, objectives: [{ id, title, card, questions }] }] }] }. card is the slug of the published study card for that objective, or null. questions is how many are published for it.

Send feedback

curl -s -X POST https://datastudyguides.com/api/v1/feedback \
  -H "Content-Type: application/json" \
  -d '{
    "type": "question",
    "exam": "dp-900",
    "question": "2.1-07",
    "message": "Option B is also correct, because ...",
    "source": "https://learn.microsoft.com/en-us/azure/...",
    "contact": "[email protected]"
  }'

201 answers { id: "fb_...", status: "received", message }. type is one of question, card, site, exam-request or other. message is required, 10 to 4,000 characters. exam and question are required for type question; exam and card for type card. source and contact are always optional.

Validate, then submit a study module

curl -s -X POST "https://datastudyguides.com/api/v1/modules?dry_run=1" \
  -H "Content-Type: application/json" \
  -d @module.json

With dry_run=1 the answer is always 200, whether the module is valid or not: { ok, errors: string[], warnings: string[], summary?, dry_run: true }. Keep fixing and resending until ok is true, then send the same body to /api/v1/modules without the query string. That answers 201 with { id: "mod_...", status: "pending_review", summary, warnings, message } on success, or 422 with { error: "invalid", ok: false, errors, warnings } when it is rejected outright.

Check a submission's status

curl -s https://datastudyguides.com/api/v1/submissions/mod_xxxxxxxxxxxxxxxx

Answers { id, kind, status, received, note? }, or 404 when the id is unknown or malformed. The status field right after you submit ("received" for feedback, "pending_review" for a module) is a friendly label, not the stored value: this endpoint always reports the real one from the table below.

Vote on a community course

curl -s -X POST https://datastudyguides.com/api/v1/courses/c1a2b3c4d5/vote \
  -H "Content-Type: application/json" \
  -d '{ "value": 1 }'

value is 1 for thumbs up, -1 for thumbs down, or 0 to take your vote back. No account is needed: signed out, a vote is tied to a salted hash of your address; signed in, to your account. Answers { id, yourVote, votes: { up, down } }.

Submit a community course

curl -s -X POST "https://datastudyguides.com/api/v1/courses?dry_run=1" \
  -H "Content-Type: application/json" \
  -d @course.json

curl -s -X POST https://datastudyguides.com/api/v1/courses \
  -H "Authorization: Bearer <your key>" \
  -H "Content-Type: application/json" \
  -d @course.json

dry_run=1 needs no account and always answers 200 with { ok, errors, warnings, summary?, dry_run: true }. Without it, submitting needs a personal key (or the session cookie from being signed in on the site) and answers 201 with { id: "c...", status, url, summary, warnings, message }. status is "published" when the automatic check passes (it only asks whether this is real study material, not spam or copied exam questions). Otherwise it is "pending": visible to you only, with the reason in note, until a maintainer decides. Create a key at /account/ first.

Error statuses

What a non-2xx answer means

Status error Meaning
400 invalid_json The request body is not valid JSON.
413 too_large The body is over the byte limit for that endpoint (see Limits).
422 invalid The body fails schema or content validation. The response carries an errors array of strings; a module response also carries warnings and, on success, a summary.
429 rate_limited A rate limit was hit. The response carries a message saying which one.

Submission status values

What /api/v1/submissions/{id} can say

status Meaning
new Just arrived. Nothing has reviewed it yet.
accepted A maintainer accepted it.
rejected A maintainer declined it.
published It is live on the site, or folded into something that is.
spam Treated as abuse, not content.

There is no public endpoint to change a status. A maintainer sets it directly, after review.

Limits

As implemented, not as a promise

Feedback messages 8 per hour per client
Study modules 4 per hour per client
Submissions of any kind 40 per day per client
Submissions site-wide 1,500 per day
Feedback request body 12,000 bytes
Module request body 400,000 bytes
Cards per module 25
Questions per module 150

The per-client limits key on a salted hash of your IP address, so they apply per network address, not per person. Need more room for a legitimate batch? Send feedback of type other and say what you are trying to do.

Community courses

Cards per course 60
Questions per course 300
Course size once rendered 600,000 bytes
Course submissions 6 per day per account
Courses per account 40 total
Active API keys 5 per account

Course limits are per account, not per client, since submitting one needs to be signed in. See src/lib/course.ts and server/courses.ts in the repository for the exact numbers as implemented.

Study module schema

What a card and a question must look like

The exact schema, generated from the same zod definitions the API validates with, is at /api/v1/schema/module.json. This is the short version.

A card

objective an objective id from the outline, shape "1.1.1"
slug lowercase with hyphens, 1 to 60 characters, becomes the URL
short 3 to 48 characters
summary 40 to 240 characters
keyPoints 2 to 6 items, 10 to 220 characters each
trap optional, 20 to 320 characters
links 1 to 4 items, each a title (3+ characters) and a learn.microsoft.com URL
verified date, YYYY-MM-DD
body 200 to 6,000 characters (also checked to be 50 to 320 words)

A question

id "<skill group>-<nn>", for example 2.1-07. Renumbered on import.
objective the slug of a card, in the module or already published
type single, multi, statements, dropdown, order or match
difficulty 1 recall, 2 apply, 3 tell close neighbors apart
stem 15+ characters
explanation 40+ characters
source a title and a learn.microsoft.com URL
verified date, YYYY-MM-DD

By type

single 3 to 5 options, exactly one correct
multi 4 to 6 options, 2 or more correct and at least one wrong
statements 2 to 4 statements, each true or false
dropdown a code or text template with {{n}} blanks, 1 to 4 blanks, 2 to 6 options each
order 3 to 6 items in the right order, plus up to 3 distractors
match 3 to 5 left/right pairs, plus up to 2 distractor right-hand values

The validator also enforces the content rules: no em or en dash anywhere, no duplicate options or items, no "all of the above" or "none of the above" style option, and a question may never say "Microsoft Learn" or "the page". A stem using NOT or EXCEPT is a warning, not an error. It cannot check that an answer is actually right: that is what the blind check and editor pass are for.

MCP

https://datastudyguides.com/mcp

Streamable HTTP, stateless: every POST is one JSON-RPC 2.0 request answered with one JSON response. There are no sessions and no server-sent events, so GET and DELETE both answer 405. A request body can batch up to 20 messages as a JSON array, up to 450,000 bytes total.

initialize negotiates a protocol version from 2025-06-18, 2025-03-26 or 2024-11-05; an unrecognized version falls back to 2025-06-18. The server identifies itself as datastudyguides.

claude mcp add --transport http datastudyguides https://datastudyguides.com/mcp
Tool Arguments What it does
list_exams (none) Lists exams, with status, outline date, and card and question counts.
get_exam_outline exam One exam's outline, with the published card and question count per objective.
get_study_card exam, card One published study card as markdown.
get_questions exam, objective?, group?, limit?, offset? Published practice questions as JSON, with the answer key and source. limit is 1 to 50, default 10.
get_contribution_guide (none) The contributor guide as text.
validate_study_module module Checks a module against the schema and content rules. Stores nothing.
submit_study_module writes module Submits a module for review.
submit_feedback writes type, message, exam?, question?, card?, source?, contact?, agent? Reports a problem or suggests an improvement.
get_submission_status id Status of a submission you sent earlier.
list_courses exam?, sort?, mine? Lists community courses, with votes. `mine` needs a personal key.
get_course id A community course as its author wrote it: cards and questions with answers.
validate_course course Checks a course against the schema and content rules. Stores nothing, no key needed.
submit_course writes course, id? Publishes your own study guide for any exam under your account name. Needs a personal key.
patch_course writes id, set?, cards?, removeCards?, questions?, removeQuestions? Changes part of a course you own without resending all of it. Needs a personal key.

submit_study_module, submit_feedback, submit_course and patch_course store data; every other tool above only reads. A study module is read by a person before it becomes official content. A course is visible to its author only until an automatic check passes. submit_course and patch_course need a personal key, carried in the MCP address (/mcp/k/<key>) or as an Authorization: Bearer <key> header; every other tool above works with no authentication at all.

Signed-in tools also expose a small set of maintainer-only tools, used to review feedback, study modules and community courses, and to change official content. Those need the site's own maintainer token, are not meant for outside use, and are not documented here.