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.