[{"data":1,"prerenderedAt":3569},["ShallowReactive",2],{"api-nav":3,"api-guide:\u002Fapi\u002Fclients\u002Fnode":351,"api-spec:guide:\u002Fapi\u002Fclients\u002Fnode":3525},[4,28,57,95,115,301,317,331],{"id":5,"title":6,"links":7,"groups":27},"start","Start here",[8,11,15,18,21,24],{"title":9,"to":10},"Overview","\u002Fapi",{"title":12,"to":13,"status":14},"Quickstart","\u002Fapi\u002Fquickstart","available",{"title":16,"to":17,"status":14},"Environments","\u002Fapi\u002Fenvironments",{"title":19,"to":20,"status":14},"Authentication","\u002Fapi\u002Fauthentication",{"title":22,"to":23,"status":14},"Organizations","\u002Fapi\u002Forganizations",{"title":25,"to":26,"status":14},"Permissions","\u002Fapi\u002Fpermissions",[],{"id":29,"title":30,"links":31,"groups":56},"concepts","Concepts",[32,35,38,41,44,47,50,53],{"title":33,"to":34,"status":14},"Requests and responses","\u002Fapi\u002Frequests-and-responses",{"title":36,"to":37,"status":14},"Identifiers","\u002Fapi\u002Fidentifiers",{"title":39,"to":40,"status":14},"Pagination","\u002Fapi\u002Fpagination",{"title":42,"to":43},"Errors","\u002Fapi\u002Ferrors",{"title":45,"to":46,"status":14},"Rate limits","\u002Fapi\u002Frate-limits",{"title":48,"to":49,"status":14},"Retries","\u002Fapi\u002Fretries-and-idempotency",{"title":51,"to":52,"status":14},"Security","\u002Fapi\u002Fsecurity",{"title":54,"to":55,"status":14},"Versioning","\u002Fapi\u002Fversioning",[],{"id":58,"title":59,"links":60,"groups":94},"resources","Guides",[61,64,67,70,73,76,79,82,85,88,91],{"title":62,"to":63,"status":14},"Students","\u002Fapi\u002Fguides\u002Fstudents",{"title":65,"to":66,"status":14},"Bulk registration","\u002Fapi\u002Fguides\u002Fbulk-registration",{"title":68,"to":69,"status":14},"Passwords","\u002Fapi\u002Fguides\u002Fpasswords",{"title":71,"to":72,"status":14},"Supervisors","\u002Fapi\u002Fguides\u002Fsupervisors",{"title":74,"to":75,"status":14},"Reference data","\u002Fapi\u002Fguides\u002Freference-data",{"title":77,"to":78,"status":14},"API account","\u002Fapi\u002Fguides\u002Fapi-account",{"title":80,"to":81,"status":14},"Sign-in links","\u002Fapi\u002Fguides\u002Fsign-in-links",{"title":83,"to":84,"status":14},"Exams","\u002Fapi\u002Fguides\u002Fexams",{"title":86,"to":87,"status":14},"Applications","\u002Fapi\u002Fguides\u002Fapplications",{"title":89,"to":90,"status":14},"Group challenges","\u002Fapi\u002Fguides\u002Fgroup-challenges",{"title":92,"to":93,"status":14},"Certificates and reports","\u002Fapi\u002Fguides\u002Fcertificates-and-reports",[],{"id":96,"title":97,"links":98,"groups":114},"tutorials","Tutorials",[99,102,105,108,111],{"title":100,"to":101,"status":14},"Register and apply","\u002Fapi\u002Ftutorials\u002Fregister-and-apply",{"title":103,"to":104,"status":14},"Send a student to the panel","\u002Fapi\u002Ftutorials\u002Fsend-student-to-panel",{"title":106,"to":107,"status":14},"Change an application","\u002Fapi\u002Ftutorials\u002Fchange-an-application",{"title":109,"to":110,"status":14},"Collect results","\u002Fapi\u002Ftutorials\u002Fcollect-results",{"title":112,"to":113,"status":14},"Token handling","\u002Fapi\u002Ftutorials\u002Ftoken-handling",[],{"id":116,"title":117,"links":118,"groups":125},"reference","Reference",[119,122],{"title":120,"to":121},"All endpoints","\u002Fapi\u002Freference",{"title":123,"to":124},"Sandbox console","\u002Fapi\u002Fconsole",[126,135,145,166,206,212,230,255,271],{"tag":127,"slug":128,"links":129},"Health","health",[130],{"title":131,"to":132,"method":133,"deprecated":134},"Check that the API is up","\u002Fapi\u002Freference\u002Fget-health","GET",false,{"tag":77,"slug":136,"links":137},"api-account",[138,142],{"title":139,"to":140,"method":141,"deprecated":134},"Revoke the token you send, before it expires","\u002Fapi\u002Freference\u002Frevoke-token","POST",{"title":143,"to":144,"method":133,"deprecated":134},"Fetch the API account your token belongs to","\u002Fapi\u002Freference\u002Fget-current-api-account",{"tag":74,"slug":146,"links":147},"reference-data",[148,151,154,157,160,163],{"title":149,"to":150,"method":133,"deprecated":134},"List the countries a student can be registered in","\u002Fapi\u002Freference\u002Flist-countries",{"title":152,"to":153,"method":133,"deprecated":134},"Fetch one country by its id","\u002Fapi\u002Freference\u002Fget-country",{"title":155,"to":156,"method":133,"deprecated":134},"List the grades a student can be registered with","\u002Fapi\u002Freference\u002Flist-grades",{"title":158,"to":159,"method":133,"deprecated":134},"Fetch one grade by its id","\u002Fapi\u002Freference\u002Fget-grade",{"title":161,"to":162,"method":133,"deprecated":134},"List the organizations and their ids","\u002Fapi\u002Freference\u002Flist-organizations",{"title":164,"to":165,"method":133,"deprecated":134},"Fetch one organization by its id","\u002Fapi\u002Freference\u002Fget-organization",{"tag":62,"slug":167,"links":168},"students",[169,172,175,178,181,184,187,191,194,197,200,203],{"title":170,"to":171,"method":133,"deprecated":134},"List your students","\u002Fapi\u002Freference\u002Flist-students",{"title":173,"to":174,"method":141,"deprecated":134},"Register a student","\u002Fapi\u002Freference\u002Fregister-student",{"title":176,"to":177,"method":141,"deprecated":134},"Check a registration without registering the student","\u002Fapi\u002Freference\u002Fcheck-student-registration",{"title":179,"to":180,"method":141,"deprecated":134},"Register many students at once","\u002Fapi\u002Freference\u002Fcreate-student-import",{"title":182,"to":183,"method":133,"deprecated":134},"Follow a batch of students you sent","\u002Fapi\u002Freference\u002Fget-student-import",{"title":185,"to":186,"method":133,"deprecated":134},"Fetch one of your students","\u002Fapi\u002Freference\u002Fget-student",{"title":188,"to":189,"method":190,"deprecated":134},"Update one of your students","\u002Fapi\u002Freference\u002Fupdate-student","PUT",{"title":192,"to":193,"method":190,"deprecated":134},"Set the sign-in password of one of your students","\u002Fapi\u002Freference\u002Fset-student-password",{"title":195,"to":196,"method":133,"deprecated":134},"List your students who can use this organization","\u002Fapi\u002Freference\u002Flist-org-students",{"title":198,"to":199,"method":133,"deprecated":134},"Fetch one of your students, if they can use this organization","\u002Fapi\u002Freference\u002Fget-org-student",{"title":201,"to":202,"method":190,"deprecated":134},"Update one of your students and give them access to this organization","\u002Fapi\u002Freference\u002Fupdate-org-student",{"title":204,"to":205,"method":190,"deprecated":134},"Link one of your students to a supervisor on this organization","\u002Fapi\u002Freference\u002Flink-student-supervisor",{"tag":80,"slug":207,"links":208},"sign-in-links",[209],{"title":210,"to":211,"method":141,"deprecated":134},"Create a single-use sign-in link for one of your students","\u002Fapi\u002Freference\u002Fcreate-signin-link",{"tag":83,"slug":213,"links":214},"exams",[215,218,221,224,227],{"title":216,"to":217,"method":133,"deprecated":134},"List the exams open for applications","\u002Fapi\u002Freference\u002Flist-exams",{"title":219,"to":220,"method":133,"deprecated":134},"List an organization’s exam categories","\u002Fapi\u002Freference\u002Flist-exam-categories",{"title":222,"to":223,"method":133,"deprecated":134},"Fetch one exam category","\u002Fapi\u002Freference\u002Fget-exam-category",{"title":225,"to":226,"method":133,"deprecated":134},"List the exams one of your students can apply to","\u002Fapi\u002Freference\u002Flist-available-exams",{"title":228,"to":229,"method":133,"deprecated":134},"Fetch one exam that is open for applications","\u002Fapi\u002Freference\u002Fget-exam",{"tag":86,"slug":231,"links":232},"applications",[233,236,239,242,245,248,251],{"title":234,"to":235,"method":133,"deprecated":134},"List your students’ applications in this organization","\u002Fapi\u002Freference\u002Flist-applications",{"title":237,"to":238,"method":141,"deprecated":134},"Enter one of your students for an exam","\u002Fapi\u002Freference\u002Fcreate-application",{"title":240,"to":241,"method":133,"deprecated":134},"List your students’ applications for one exam","\u002Fapi\u002Freference\u002Flist-exam-applications",{"title":243,"to":244,"method":133,"deprecated":134},"List one of your students’ applications in this organization","\u002Fapi\u002Freference\u002Flist-student-applications",{"title":246,"to":247,"method":133,"deprecated":134},"Fetch one of your students’ applications","\u002Fapi\u002Freference\u002Fget-application",{"title":249,"to":250,"method":190,"deprecated":134},"Move one of your students’ applications to another exam","\u002Fapi\u002Freference\u002Fmove-application",{"title":252,"to":253,"method":254,"deprecated":134},"Withdraw one of your students from an exam","\u002Fapi\u002Freference\u002Fdelete-application","DELETE",{"tag":256,"slug":257,"links":258},"Documents","documents",[259,262,265,268],{"title":260,"to":261,"method":133,"deprecated":134},"Download a certificate file","\u002Fapi\u002Freference\u002Fdownload-certificate",{"title":263,"to":264,"method":133,"deprecated":134},"List one of your students’ released certificates","\u002Fapi\u002Freference\u002Flist-student-certificates",{"title":266,"to":267,"method":133,"deprecated":134},"Download a result report file","\u002Fapi\u002Freference\u002Fdownload-report",{"title":269,"to":270,"method":133,"deprecated":134},"List one of your students’ released result reports","\u002Fapi\u002Freference\u002Flist-student-reports",{"tag":89,"slug":272,"links":273},"group-challenges",[274,277,280,283,286,289,292,295,298],{"title":275,"to":276,"method":133,"deprecated":134},"List the group challenges an organization runs","\u002Fapi\u002Freference\u002Flist-group-challenges",{"title":278,"to":279,"method":133,"deprecated":134},"Fetch one group challenge","\u002Fapi\u002Freference\u002Fget-group-challenge",{"title":281,"to":282,"method":133,"deprecated":134},"List the groups your students are in for a group challenge","\u002Fapi\u002Freference\u002Flist-group-challenge-groups",{"title":284,"to":285,"method":133,"deprecated":134},"Fetch one group, with its steps and files","\u002Fapi\u002Freference\u002Fget-group-challenge-group",{"title":287,"to":288,"method":133,"deprecated":134},"List what has happened in one group","\u002Fapi\u002Freference\u002Flist-group-challenge-activity",{"title":290,"to":291,"method":141,"deprecated":134},"Send a group’s finished work for one of your students","\u002Fapi\u002Freference\u002Fsubmit-group-challenge-work",{"title":293,"to":294,"method":141,"deprecated":134},"Submit one step of a group for one of your students","\u002Fapi\u002Freference\u002Fsubmit-group-challenge-step",{"title":296,"to":297,"method":133,"deprecated":134},"List your students’ eligibility and groups for a group challenge","\u002Fapi\u002Freference\u002Flist-group-challenge-students",{"title":299,"to":300,"method":133,"deprecated":134},"Fetch one of your students’ eligibility and group for a group challenge","\u002Fapi\u002Freference\u002Fget-group-challenge-student",{"id":302,"title":303,"links":304,"groups":316},"clients","Clients",[305,307,310,313],{"title":9,"to":306,"status":14},"\u002Fapi\u002Fclients",{"title":308,"to":309,"status":14},"Node.js","\u002Fapi\u002Fclients\u002Fnode",{"title":311,"to":312,"status":14},"PHP","\u002Fapi\u002Fclients\u002Fphp",{"title":314,"to":315,"status":14},"Build your own","\u002Fapi\u002Fclients\u002Fbuild-your-own",[],{"id":318,"title":319,"links":320,"groups":330},"agents","AI agents",[321,324,327],{"title":322,"to":323},"AI connections","\u002Fapi\u002Fmcp",{"title":325,"to":326},"What it can do","\u002Fapi\u002Fmcp\u002Ftools",{"title":328,"to":329},"Agent skills","\u002Fapi\u002Fskills",[],{"id":332,"title":333,"links":334,"groups":350},"help","Help",[335,338,341,344,347],{"title":336,"to":337,"status":14},"Glossary","\u002Fapi\u002Fglossary",{"title":339,"to":340,"status":14},"FAQ","\u002Fapi\u002Ffaq",{"title":342,"to":343,"status":14},"Troubleshooting","\u002Fapi\u002Ftroubleshooting",{"title":345,"to":346,"status":14},"Support","\u002Fapi\u002Fsupport",{"title":348,"to":349},"Changelog","\u002Fapi\u002Fchangelog",[],{"id":352,"title":353,"body":354,"description":3507,"extension":3508,"meta":3509,"navTitle":308,"navigation":549,"operations":3510,"order":3521,"path":309,"section":302,"seo":3522,"status":14,"stem":3523,"__hash__":3524},"apiGuides\u002Fapi\u002Fclients\u002Fnode.md","Node.js client",{"type":355,"value":356,"toc":3488},"minimark",[357,365,368,373,413,417,431,456,460,463,518,608,611,684,687,700,726,730,742,779,794,982,985,1034,1039,1043,1050,1124,1130,1179,1189,1209,1213,1219,1224,1487,1503,1510,1753,1764,1921,1926,1930,1933,1981,1984,2073,2092,2095,2265,2278,2282,2299,2332,2337,2341,2393,2450,2456,2459,2468,2590,2608,2645,2651,2663,2772,2775,2796,2870,2876,2942,2947,2951,2961,3056,3064,3068,3095,3104,3352,3372,3376,3379,3456,3460,3484],[358,359,360,364],"p",{},[361,362,363],"code",{},"@main-team\u002Fapi-client"," is the official Node.js and TypeScript client for the Main Team API. This\npage takes you from installation to a complete integration: you register a student, send them\ninto the panel, enter them for an exam, and collect their certificates. Each step explains which\nAPI rule it relies on, so you know what to expect when a call fails.",[358,366,367],{},"The package README is the full method reference. This page covers the parts you need to build a\nworking integration and the API behavior behind each of them.",[369,370,372],"h2",{"id":371},"requirements","Requirements",[374,375,376,388,398],"ul",{},[377,378,379,383,384,387],"li",{},[380,381,382],"strong",{},"Node.js 20 or newer."," The client uses the built-in ",[361,385,386],{},"fetch",".",[377,389,390,393,394,397],{},[380,391,392],{},"No runtime dependencies."," ESM, CommonJS and TypeScript types ship in one package, so there's\nno ",[361,395,396],{},"@types"," package to install and no bundler required.",[377,399,400,403,404,407,408,387],{},[380,401,402],{},"A server."," The client holds your ",[361,405,406],{},"apiSecret",", so it runs on your backend only. See\n",[409,410,412],"a",{"href":411},"\u002Fapi\u002Fclients#server-side-only","Server-side only",[369,414,416],{"id":415},"install","Install",[358,418,419,420,423,424,426,427,387],{},"The package is private. Access comes with your API credentials: the access instructions you\nreceive with your ",[361,421,422],{},"apiKey"," and ",[361,425,406],{}," tell you how to configure npm for it. If you have\ncredentials but no package access, write to ",[409,428,430],{"href":429},"mailto:info@main-team.org","info@main-team.org",[432,433,438],"pre",{"className":434,"code":435,"language":436,"meta":437,"style":437},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast","npm install @main-team\u002Fapi-client\n","bash","",[361,439,440],{"__ignoreMap":437},[441,442,445,449,453],"span",{"class":443,"line":444},"line",1,[441,446,448],{"class":447},"soyes","npm",[441,450,452],{"class":451},"sT6z2"," install",[441,454,455],{"class":451}," @main-team\u002Fapi-client\n",[369,457,459],{"id":458},"set-up-the-client","Set up the client",[358,461,462],{},"You need two values, both from your API credentials:",[464,465,466,482],"table",{},[467,468,469],"thead",{},[470,471,472,476,479],"tr",{},[473,474,475],"th",{},"Value",[473,477,478],{},"What it is",[473,480,481],{},"Where it goes",[483,484,485,502],"tbody",{},[470,486,487,492,499],{},[488,489,490],"td",{},[361,491,422],{},[488,493,494,495,498],{},"Your public account identifier, ",[361,496,497],{},"key_"," followed by 24 characters.",[488,500,501],{},"Anywhere. It's not a secret.",[470,503,504,508,515],{},[488,505,506],{},[361,507,406],{},[488,509,510,511,514],{},"Your signing key, ",[361,512,513],{},"secret_"," followed by random characters. Shown to you once, and it can't be recovered.",[488,516,517],{},"An environment variable or a secret store, on the server only.",[432,519,523],{"className":520,"code":521,"language":522,"meta":437,"style":437},"language-ts shiki shiki-themes github-light-high-contrast github-dark-high-contrast","import { MtoClient } from '@main-team\u002Fapi-client';\n\nconst client = new MtoClient({\n  apiKey: process.env.MTO_API_KEY!,\n  apiSecret: process.env.MTO_API_SECRET!,\n});\n","ts",[361,524,525,544,551,574,589,602],{"__ignoreMap":437},[441,526,527,531,535,538,541],{"class":443,"line":444},[441,528,530],{"class":529},"sHUrx","import",[441,532,534],{"class":533},"suds8"," { MtoClient } ",[441,536,537],{"class":529},"from",[441,539,540],{"class":451}," '@main-team\u002Fapi-client'",[441,542,543],{"class":533},";\n",[441,545,547],{"class":443,"line":546},2,[441,548,550],{"emptyLinePlaceholder":549},true,"\n",[441,552,554,557,561,564,567,571],{"class":443,"line":553},3,[441,555,556],{"class":529},"const",[441,558,560],{"class":559},"s-5SL"," client",[441,562,563],{"class":529}," =",[441,565,566],{"class":529}," new",[441,568,570],{"class":569},"sKwhi"," MtoClient",[441,572,573],{"class":533},"({\n",[441,575,577,580,583,586],{"class":443,"line":576},4,[441,578,579],{"class":533},"  apiKey: process.env.",[441,581,582],{"class":559},"MTO_API_KEY",[441,584,585],{"class":529},"!",[441,587,588],{"class":533},",\n",[441,590,592,595,598,600],{"class":443,"line":591},5,[441,593,594],{"class":533},"  apiSecret: process.env.",[441,596,597],{"class":559},"MTO_API_SECRET",[441,599,585],{"class":529},[441,601,588],{"class":533},[441,603,605],{"class":443,"line":604},6,[441,606,607],{"class":533},"});\n",[358,609,610],{},"The same in CommonJS:",[432,612,616],{"className":613,"code":614,"language":615,"meta":437,"style":437},"language-js shiki shiki-themes github-light-high-contrast github-dark-high-contrast","const { MtoClient } = require('@main-team\u002Fapi-client');\n\nconst client = new MtoClient({\n  apiKey: process.env.MTO_API_KEY,\n  apiSecret: process.env.MTO_API_SECRET,\n});\n","js",[361,617,618,646,650,664,672,680],{"__ignoreMap":437},[441,619,620,622,625,628,631,634,637,640,643],{"class":443,"line":444},[441,621,556],{"class":529},[441,623,624],{"class":533}," { ",[441,626,627],{"class":559},"MtoClient",[441,629,630],{"class":533}," } ",[441,632,633],{"class":529},"=",[441,635,636],{"class":569}," require",[441,638,639],{"class":533},"(",[441,641,642],{"class":451},"'@main-team\u002Fapi-client'",[441,644,645],{"class":533},");\n",[441,647,648],{"class":443,"line":546},[441,649,550],{"emptyLinePlaceholder":549},[441,651,652,654,656,658,660,662],{"class":443,"line":553},[441,653,556],{"class":529},[441,655,560],{"class":559},[441,657,563],{"class":529},[441,659,566],{"class":529},[441,661,570],{"class":569},[441,663,573],{"class":533},[441,665,666,668,670],{"class":443,"line":576},[441,667,579],{"class":533},[441,669,582],{"class":559},[441,671,588],{"class":533},[441,673,674,676,678],{"class":443,"line":591},[441,675,594],{"class":533},[441,677,597],{"class":559},[441,679,588],{"class":533},[441,681,682],{"class":443,"line":604},[441,683,607],{"class":533},[358,685,686],{},"Create one client per process and share it. The client signs short-lived tokens with your secret\nfor you, so there's no token endpoint to call and no token for you to manage.",[688,689,691],"callout",{"type":690},"security",[358,692,693,696,697,699],{},[380,694,695],{},"The secret never leaves your process."," Only the signed token travels, and a token expires\nwithin an hour at most. Never commit the secret to source control, bundle it into front-end code\nor write it to a log. If it leaks, ask your operator to deactivate the account. There is no secret\nrotation, so read ",[409,698,51],{"href":52}," before you need it.",[358,701,702,709,710,713,714,718,719,722,723,387],{},[380,703,704,705,708],{},"There is no ",[361,706,707],{},"baseUrl"," option."," The client talks to ",[361,711,712],{},"https:\u002F\u002Fapi.main-team.org\u002Fv1"," and refuses\nto be pointed anywhere else, because any other host would receive a bearer token it could replay\nas your account. See ",[409,715,717],{"href":716},"\u002Fapi\u002Fclients#why-the-base-url-is-locked","Why the base URL is locked",".\nThat includes the sandbox: to call ",[361,720,721],{},"https:\u002F\u002Fapisnd.main-team.org\u002Fv1",", send plain HTTPS requests, or\nuse the client from ",[409,724,725],{"href":315},"Build your own client",[369,727,729],{"id":728},"check-that-your-credentials-work","Check that your credentials work",[358,731,732,735,736,741],{},[361,733,734],{},"whoami()"," calls ",[409,737,738],{"href":144},[361,739,740],{},"GET \u002Fv1\u002Fapi-account\u002Fvalidate-me",",\nwhich answers with the account your token belongs to:",[432,743,745],{"className":520,"code":744,"language":522,"meta":437,"style":437},"const account = await client.account.whoami();\nconsole.log(account.companyName, account.roles);\n",[361,746,747,768],{"__ignoreMap":437},[441,748,749,751,754,756,759,762,765],{"class":443,"line":444},[441,750,556],{"class":529},[441,752,753],{"class":559}," account",[441,755,563],{"class":529},[441,757,758],{"class":529}," await",[441,760,761],{"class":533}," client.account.",[441,763,764],{"class":569},"whoami",[441,766,767],{"class":533},"();\n",[441,769,770,773,776],{"class":443,"line":546},[441,771,772],{"class":533},"console.",[441,774,775],{"class":569},"log",[441,777,778],{"class":533},"(account.companyName, account.roles);\n",[358,780,781,782,785,786,789,790,793],{},"On the wire, this is the one JSON route without the response envelope. It returns the account\nobject itself, without ",[361,783,784],{},"success",", ",[361,787,788],{},"message"," or ",[361,791,792],{},"data"," around it.",[432,795,799],{"className":796,"code":797,"language":798,"meta":437,"style":437},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast","{\n  \"_id\": \"66f1a2b3c4d5e6f708192a3b\",\n  \"apiKey\": \"key_Q2hvb3NlQW5vdGhlcktleUhl\",\n  \"companyName\": \"Example Learning Ltd\",\n  \"scopes\": [],\n  \"roles\": [\n    { \"effect\": \"allow\", \"action\": \"*\u002Fread\", \"target\": \"*\" },\n    { \"effect\": \"allow\", \"action\": \"api\u002F*\", \"target\": \"mto\" },\n    { \"effect\": \"allow\", \"action\": \"student\u002F*\", \"target\": \"mto\" }\n  ],\n  \"isActive\": true\n}\n","json",[361,800,801,806,820,832,844,852,860,897,928,959,965,976],{"__ignoreMap":437},[441,802,803],{"class":443,"line":444},[441,804,805],{"class":533},"{\n",[441,807,808,812,815,818],{"class":443,"line":546},[441,809,811],{"class":810},"sne4z","  \"_id\"",[441,813,814],{"class":533},": ",[441,816,817],{"class":451},"\"66f1a2b3c4d5e6f708192a3b\"",[441,819,588],{"class":533},[441,821,822,825,827,830],{"class":443,"line":553},[441,823,824],{"class":810},"  \"apiKey\"",[441,826,814],{"class":533},[441,828,829],{"class":451},"\"key_Q2hvb3NlQW5vdGhlcktleUhl\"",[441,831,588],{"class":533},[441,833,834,837,839,842],{"class":443,"line":576},[441,835,836],{"class":810},"  \"companyName\"",[441,838,814],{"class":533},[441,840,841],{"class":451},"\"Example Learning Ltd\"",[441,843,588],{"class":533},[441,845,846,849],{"class":443,"line":591},[441,847,848],{"class":810},"  \"scopes\"",[441,850,851],{"class":533},": [],\n",[441,853,854,857],{"class":443,"line":604},[441,855,856],{"class":810},"  \"roles\"",[441,858,859],{"class":533},": [\n",[441,861,863,866,869,871,874,876,879,881,884,886,889,891,894],{"class":443,"line":862},7,[441,864,865],{"class":533},"    { ",[441,867,868],{"class":810},"\"effect\"",[441,870,814],{"class":533},[441,872,873],{"class":451},"\"allow\"",[441,875,785],{"class":533},[441,877,878],{"class":810},"\"action\"",[441,880,814],{"class":533},[441,882,883],{"class":451},"\"*\u002Fread\"",[441,885,785],{"class":533},[441,887,888],{"class":810},"\"target\"",[441,890,814],{"class":533},[441,892,893],{"class":451},"\"*\"",[441,895,896],{"class":533}," },\n",[441,898,900,902,904,906,908,910,912,914,917,919,921,923,926],{"class":443,"line":899},8,[441,901,865],{"class":533},[441,903,868],{"class":810},[441,905,814],{"class":533},[441,907,873],{"class":451},[441,909,785],{"class":533},[441,911,878],{"class":810},[441,913,814],{"class":533},[441,915,916],{"class":451},"\"api\u002F*\"",[441,918,785],{"class":533},[441,920,888],{"class":810},[441,922,814],{"class":533},[441,924,925],{"class":451},"\"mto\"",[441,927,896],{"class":533},[441,929,931,933,935,937,939,941,943,945,948,950,952,954,956],{"class":443,"line":930},9,[441,932,865],{"class":533},[441,934,868],{"class":810},[441,936,814],{"class":533},[441,938,873],{"class":451},[441,940,785],{"class":533},[441,942,878],{"class":810},[441,944,814],{"class":533},[441,946,947],{"class":451},"\"student\u002F*\"",[441,949,785],{"class":533},[441,951,888],{"class":810},[441,953,814],{"class":533},[441,955,925],{"class":451},[441,957,958],{"class":533}," }\n",[441,960,962],{"class":443,"line":961},10,[441,963,964],{"class":533},"  ],\n",[441,966,968,971,973],{"class":443,"line":967},11,[441,969,970],{"class":810},"  \"isActive\"",[441,972,814],{"class":533},[441,974,975],{"class":559},"true\n",[441,977,979],{"class":443,"line":978},12,[441,980,981],{"class":533},"}\n",[358,983,984],{},"If this fails, the error tells you where to look:",[464,986,987,1000],{},[467,988,989],{},[470,990,991,994,997],{},[473,992,993],{},"Result",[473,995,996],{},"Meaning",[473,998,999],{},"What to do",[483,1001,1002,1017],{},[470,1003,1004,1009,1012],{},[488,1005,1006],{},[361,1007,1008],{},"401 unauthorized",[488,1010,1011],{},"The token was refused. Every token failure gives this same answer.",[488,1013,1014,1015,387],{},"Check the key, the secret and your server clock. See ",[409,1016,19],{"href":20},[470,1018,1019,1024,1031],{},[488,1020,1021],{},[361,1022,1023],{},"403 forbidden",[488,1025,1026,1027,1030],{},"The token is fine, but your roles don't include ",[361,1028,1029],{},"api\u002F*"," on mto, which this route needs.",[488,1032,1033],{},"Ask your operator for the permission. Other routes may still work.",[358,1035,1036,1038],{},[361,1037,406],{}," is never returned by any route.",[369,1040,1042],{"id":1041},"choosing-an-organization","Choosing an organization",[358,1044,1045,1046,1049],{},"Most operations belong to one organization: mto (the core record), stem, hilingua, neo, gmath or\ncoding. The API's paths take the organization's ",[361,1047,1048],{},"_id",", never its slug. The client resolves slugs\nfor you:",[432,1051,1053],{"className":520,"code":1052,"language":522,"meta":437,"style":437},"await client.loadOrganizations(); \u002F\u002F one request, then cached\n\nconst stem = client.organization('stem');\nconst { data: exams } = await stem.exams.list();\n",[361,1054,1055,1073,1077,1098],{"__ignoreMap":437},[441,1056,1057,1060,1063,1066,1069],{"class":443,"line":444},[441,1058,1059],{"class":529},"await",[441,1061,1062],{"class":533}," client.",[441,1064,1065],{"class":569},"loadOrganizations",[441,1067,1068],{"class":533},"(); ",[441,1070,1072],{"class":1071},"sLBg1","\u002F\u002F one request, then cached\n",[441,1074,1075],{"class":443,"line":546},[441,1076,550],{"emptyLinePlaceholder":549},[441,1078,1079,1081,1084,1086,1088,1091,1093,1096],{"class":443,"line":553},[441,1080,556],{"class":529},[441,1082,1083],{"class":559}," stem",[441,1085,563],{"class":529},[441,1087,1062],{"class":533},[441,1089,1090],{"class":569},"organization",[441,1092,639],{"class":533},[441,1094,1095],{"class":451},"'stem'",[441,1097,645],{"class":533},[441,1099,1100,1102,1104,1106,1108,1110,1112,1114,1116,1119,1122],{"class":443,"line":576},[441,1101,556],{"class":529},[441,1103,624],{"class":533},[441,1105,792],{"class":447},[441,1107,814],{"class":533},[441,1109,213],{"class":559},[441,1111,630],{"class":533},[441,1113,633],{"class":529},[441,1115,758],{"class":529},[441,1117,1118],{"class":533}," stem.exams.",[441,1120,1121],{"class":569},"list",[441,1123,767],{"class":533},[358,1125,1126,1129],{},[361,1127,1128],{},"loadOrganizations()"," also returns every organization, keyed by slug and ready to use:",[432,1131,1133],{"className":520,"code":1132,"language":522,"meta":437,"style":437},"const orgs = await client.loadOrganizations();\n\nawait orgs.coding.exams.list();\nconsole.log(orgs.coding.organizationId); \u002F\u002F the _id, if you need it\n",[361,1134,1135,1152,1156,1167],{"__ignoreMap":437},[441,1136,1137,1139,1142,1144,1146,1148,1150],{"class":443,"line":444},[441,1138,556],{"class":529},[441,1140,1141],{"class":559}," orgs",[441,1143,563],{"class":529},[441,1145,758],{"class":529},[441,1147,1062],{"class":533},[441,1149,1065],{"class":569},[441,1151,767],{"class":533},[441,1153,1154],{"class":443,"line":546},[441,1155,550],{"emptyLinePlaceholder":549},[441,1157,1158,1160,1163,1165],{"class":443,"line":553},[441,1159,1059],{"class":529},[441,1161,1162],{"class":533}," orgs.coding.exams.",[441,1164,1121],{"class":569},[441,1166,767],{"class":533},[441,1168,1169,1171,1173,1176],{"class":443,"line":576},[441,1170,772],{"class":533},[441,1172,775],{"class":569},[441,1174,1175],{"class":533},"(orgs.coding.organizationId); ",[441,1177,1178],{"class":1071},"\u002F\u002F the _id, if you need it\n",[358,1180,1181,1182,1185,1186,1188],{},"A slug your account can't reach throws an error that names the slugs it can, so a typo doesn't\ncome back as ",[361,1183,1184],{},"undefined"," three calls later. Passing a raw ",[361,1187,1048],{}," always works and skips the lookup.",[688,1190,1192],{"type":1191},"note",[358,1193,1194,1195,1198,1199,789,1201,1204,1205,423,1207,387],{},"Operations without an organization in the path, such as students on the core record, countries,\ngrades and organizations, act on ",[380,1196,1197],{},"mto",". Your roles need target ",[361,1200,1197],{},[361,1202,1203],{},"*"," for those. See\n",[409,1206,22],{"href":23},[409,1208,25],{"href":26},[369,1210,1212],{"id":1211},"a-complete-flow","A complete flow",[358,1214,1215,1216,1218],{},"This is the path most integrations follow: register a student, send them into an organization\nonce, then enter them for an exam. The\n",[409,1217,100],{"href":101}," tutorial walks through the same flow over\nplain HTTP.",[1220,1221,1223],"h3",{"id":1222},"step-1-register-the-student","Step 1: register the student",[432,1225,1227],{"className":520,"code":1226,"language":522,"meta":437,"style":437},"import { autoPaginate, MtoConflictError } from '@main-team\u002Fapi-client';\n\n\u002F\u002F `country` takes an id only. Find it once and cache it: reference data rarely changes.\nlet albania;\nfor await (const country of autoPaginate((p) => client.countries.list(p))) {\n  if (country.iso2 === 'AL') { albania = country; break; }\n}\n\nconst student = await client.students.register({\n  firstName: 'Jane',\n  lastName: 'Doe',\n  email: 'jane.doe@example.com',\n  birth: '14\u002F05\u002F2010',          \u002F\u002F DD\u002FMM\u002FYYYY\n  sex: 'f',                     \u002F\u002F 'm', 'f' or 'n'\n  country: albania._id,\n  grade: '8',                   \u002F\u002F a grade _id, or its name '1' to '12'\n  city: 'Berlin',               \u002F\u002F an id, or a name within the country\n  school: 'Berlin International School', \u002F\u002F an id, or a name within country and city\n});\n\nconsole.log(student._id, student.username); \u002F\u002F keep student._id: it's the id every route uses\n",[361,1228,1229,1242,1246,1251,1259,1299,1327,1331,1335,1354,1364,1374,1384,1399,1414,1420,1435,1450,1464,1469,1474],{"__ignoreMap":437},[441,1230,1231,1233,1236,1238,1240],{"class":443,"line":444},[441,1232,530],{"class":529},[441,1234,1235],{"class":533}," { autoPaginate, MtoConflictError } ",[441,1237,537],{"class":529},[441,1239,540],{"class":451},[441,1241,543],{"class":533},[441,1243,1244],{"class":443,"line":546},[441,1245,550],{"emptyLinePlaceholder":549},[441,1247,1248],{"class":443,"line":553},[441,1249,1250],{"class":1071},"\u002F\u002F `country` takes an id only. Find it once and cache it: reference data rarely changes.\n",[441,1252,1253,1256],{"class":443,"line":576},[441,1254,1255],{"class":529},"let",[441,1257,1258],{"class":533}," albania;\n",[441,1260,1261,1264,1266,1269,1271,1274,1277,1280,1283,1285,1288,1291,1294,1296],{"class":443,"line":591},[441,1262,1263],{"class":529},"for",[441,1265,758],{"class":529},[441,1267,1268],{"class":533}," (",[441,1270,556],{"class":529},[441,1272,1273],{"class":559}," country",[441,1275,1276],{"class":529}," of",[441,1278,1279],{"class":569}," autoPaginate",[441,1281,1282],{"class":533},"((",[441,1284,358],{"class":447},[441,1286,1287],{"class":533},") ",[441,1289,1290],{"class":529},"=>",[441,1292,1293],{"class":533}," client.countries.",[441,1295,1121],{"class":569},[441,1297,1298],{"class":533},"(p))) {\n",[441,1300,1301,1304,1307,1310,1313,1316,1318,1321,1324],{"class":443,"line":604},[441,1302,1303],{"class":529},"  if",[441,1305,1306],{"class":533}," (country.iso2 ",[441,1308,1309],{"class":529},"===",[441,1311,1312],{"class":451}," 'AL'",[441,1314,1315],{"class":533},") { albania ",[441,1317,633],{"class":529},[441,1319,1320],{"class":533}," country; ",[441,1322,1323],{"class":529},"break",[441,1325,1326],{"class":533},"; }\n",[441,1328,1329],{"class":443,"line":862},[441,1330,981],{"class":533},[441,1332,1333],{"class":443,"line":899},[441,1334,550],{"emptyLinePlaceholder":549},[441,1336,1337,1339,1342,1344,1346,1349,1352],{"class":443,"line":930},[441,1338,556],{"class":529},[441,1340,1341],{"class":559}," student",[441,1343,563],{"class":529},[441,1345,758],{"class":529},[441,1347,1348],{"class":533}," client.students.",[441,1350,1351],{"class":569},"register",[441,1353,573],{"class":533},[441,1355,1356,1359,1362],{"class":443,"line":961},[441,1357,1358],{"class":533},"  firstName: ",[441,1360,1361],{"class":451},"'Jane'",[441,1363,588],{"class":533},[441,1365,1366,1369,1372],{"class":443,"line":967},[441,1367,1368],{"class":533},"  lastName: ",[441,1370,1371],{"class":451},"'Doe'",[441,1373,588],{"class":533},[441,1375,1376,1379,1382],{"class":443,"line":978},[441,1377,1378],{"class":533},"  email: ",[441,1380,1381],{"class":451},"'jane.doe@example.com'",[441,1383,588],{"class":533},[441,1385,1387,1390,1393,1396],{"class":443,"line":1386},13,[441,1388,1389],{"class":533},"  birth: ",[441,1391,1392],{"class":451},"'14\u002F05\u002F2010'",[441,1394,1395],{"class":533},",          ",[441,1397,1398],{"class":1071},"\u002F\u002F DD\u002FMM\u002FYYYY\n",[441,1400,1402,1405,1408,1411],{"class":443,"line":1401},14,[441,1403,1404],{"class":533},"  sex: ",[441,1406,1407],{"class":451},"'f'",[441,1409,1410],{"class":533},",                     ",[441,1412,1413],{"class":1071},"\u002F\u002F 'm', 'f' or 'n'\n",[441,1415,1417],{"class":443,"line":1416},15,[441,1418,1419],{"class":533},"  country: albania._id,\n",[441,1421,1423,1426,1429,1432],{"class":443,"line":1422},16,[441,1424,1425],{"class":533},"  grade: ",[441,1427,1428],{"class":451},"'8'",[441,1430,1431],{"class":533},",                   ",[441,1433,1434],{"class":1071},"\u002F\u002F a grade _id, or its name '1' to '12'\n",[441,1436,1438,1441,1444,1447],{"class":443,"line":1437},17,[441,1439,1440],{"class":533},"  city: ",[441,1442,1443],{"class":451},"'Berlin'",[441,1445,1446],{"class":533},",               ",[441,1448,1449],{"class":1071},"\u002F\u002F an id, or a name within the country\n",[441,1451,1453,1456,1459,1461],{"class":443,"line":1452},18,[441,1454,1455],{"class":533},"  school: ",[441,1457,1458],{"class":451},"'Berlin International School'",[441,1460,785],{"class":533},[441,1462,1463],{"class":1071},"\u002F\u002F an id, or a name within country and city\n",[441,1465,1467],{"class":443,"line":1466},19,[441,1468,607],{"class":533},[441,1470,1472],{"class":443,"line":1471},20,[441,1473,550],{"emptyLinePlaceholder":549},[441,1475,1477,1479,1481,1484],{"class":443,"line":1476},21,[441,1478,772],{"class":533},[441,1480,775],{"class":569},[441,1482,1483],{"class":533},"(student._id, student.username); ",[441,1485,1486],{"class":1071},"\u002F\u002F keep student._id: it's the id every route uses\n",[358,1488,1489,785,1492,423,1495,1498,1499,1502],{},[361,1490,1491],{},"grade",[361,1493,1494],{},"city",[361,1496,1497],{},"school"," accept either an id or a name. A name is looked up on the server and\nrefused with ",[361,1500,1501],{},"400 bad_request"," if it matches nothing. The API never creates a city, a school or a\ngrade for you. Some countries can't be selected; they're left out of the country list and refused\nat registration as if they didn't exist.",[358,1504,1505,1506,1509],{},"The API answers ",[361,1507,1508],{},"201"," and returns the new student:",[432,1511,1513],{"className":796,"code":1512,"language":798,"meta":437,"style":437},"{\n  \"success\": true,\n  \"message\": \"User registered successfully.\",\n  \"data\": {\n    \"_id\": \"66f2b7c1e4a9d20012ab34cd\",\n    \"username\": \"XXK10427\",\n    \"firstName\": \"Jane\",\n    \"lastName\": \"Doe\",\n    \"fullName\": \"Jane Doe\",\n    \"email\": \"jane.doe@example.com\",\n    \"emailConfirmed\": false,\n    \"birth\": \"14\u002F05\u002F2010\",\n    \"sex\": \"f\",\n    \"country\": \"630e0182c53dc79a6836e67e\",\n    \"city\": \"63a4c2d1e0f9a80012345678\",\n    \"school\": \"64b1d3e2f1a0b90012345679\",\n    \"grade\": \"630e01826836e67ec53dc7ae\",\n    \"activatedPlatformsThisSeason\": [\"common\"],\n    \"createdAt\": \"2026-09-15T09:12:44.512Z\",\n    \"updatedAt\": \"2026-09-15T09:12:44.512Z\"\n  }\n}\n",[361,1514,1515,1519,1531,1543,1551,1563,1575,1587,1599,1611,1623,1635,1647,1659,1671,1683,1695,1707,1721,1733,1743,1748],{"__ignoreMap":437},[441,1516,1517],{"class":443,"line":444},[441,1518,805],{"class":533},[441,1520,1521,1524,1526,1529],{"class":443,"line":546},[441,1522,1523],{"class":810},"  \"success\"",[441,1525,814],{"class":533},[441,1527,1528],{"class":559},"true",[441,1530,588],{"class":533},[441,1532,1533,1536,1538,1541],{"class":443,"line":553},[441,1534,1535],{"class":810},"  \"message\"",[441,1537,814],{"class":533},[441,1539,1540],{"class":451},"\"User registered successfully.\"",[441,1542,588],{"class":533},[441,1544,1545,1548],{"class":443,"line":576},[441,1546,1547],{"class":810},"  \"data\"",[441,1549,1550],{"class":533},": {\n",[441,1552,1553,1556,1558,1561],{"class":443,"line":591},[441,1554,1555],{"class":810},"    \"_id\"",[441,1557,814],{"class":533},[441,1559,1560],{"class":451},"\"66f2b7c1e4a9d20012ab34cd\"",[441,1562,588],{"class":533},[441,1564,1565,1568,1570,1573],{"class":443,"line":604},[441,1566,1567],{"class":810},"    \"username\"",[441,1569,814],{"class":533},[441,1571,1572],{"class":451},"\"XXK10427\"",[441,1574,588],{"class":533},[441,1576,1577,1580,1582,1585],{"class":443,"line":862},[441,1578,1579],{"class":810},"    \"firstName\"",[441,1581,814],{"class":533},[441,1583,1584],{"class":451},"\"Jane\"",[441,1586,588],{"class":533},[441,1588,1589,1592,1594,1597],{"class":443,"line":899},[441,1590,1591],{"class":810},"    \"lastName\"",[441,1593,814],{"class":533},[441,1595,1596],{"class":451},"\"Doe\"",[441,1598,588],{"class":533},[441,1600,1601,1604,1606,1609],{"class":443,"line":930},[441,1602,1603],{"class":810},"    \"fullName\"",[441,1605,814],{"class":533},[441,1607,1608],{"class":451},"\"Jane Doe\"",[441,1610,588],{"class":533},[441,1612,1613,1616,1618,1621],{"class":443,"line":961},[441,1614,1615],{"class":810},"    \"email\"",[441,1617,814],{"class":533},[441,1619,1620],{"class":451},"\"jane.doe@example.com\"",[441,1622,588],{"class":533},[441,1624,1625,1628,1630,1633],{"class":443,"line":967},[441,1626,1627],{"class":810},"    \"emailConfirmed\"",[441,1629,814],{"class":533},[441,1631,1632],{"class":559},"false",[441,1634,588],{"class":533},[441,1636,1637,1640,1642,1645],{"class":443,"line":978},[441,1638,1639],{"class":810},"    \"birth\"",[441,1641,814],{"class":533},[441,1643,1644],{"class":451},"\"14\u002F05\u002F2010\"",[441,1646,588],{"class":533},[441,1648,1649,1652,1654,1657],{"class":443,"line":1386},[441,1650,1651],{"class":810},"    \"sex\"",[441,1653,814],{"class":533},[441,1655,1656],{"class":451},"\"f\"",[441,1658,588],{"class":533},[441,1660,1661,1664,1666,1669],{"class":443,"line":1401},[441,1662,1663],{"class":810},"    \"country\"",[441,1665,814],{"class":533},[441,1667,1668],{"class":451},"\"630e0182c53dc79a6836e67e\"",[441,1670,588],{"class":533},[441,1672,1673,1676,1678,1681],{"class":443,"line":1416},[441,1674,1675],{"class":810},"    \"city\"",[441,1677,814],{"class":533},[441,1679,1680],{"class":451},"\"63a4c2d1e0f9a80012345678\"",[441,1682,588],{"class":533},[441,1684,1685,1688,1690,1693],{"class":443,"line":1422},[441,1686,1687],{"class":810},"    \"school\"",[441,1689,814],{"class":533},[441,1691,1692],{"class":451},"\"64b1d3e2f1a0b90012345679\"",[441,1694,588],{"class":533},[441,1696,1697,1700,1702,1705],{"class":443,"line":1437},[441,1698,1699],{"class":810},"    \"grade\"",[441,1701,814],{"class":533},[441,1703,1704],{"class":451},"\"630e01826836e67ec53dc7ae\"",[441,1706,588],{"class":533},[441,1708,1709,1712,1715,1718],{"class":443,"line":1452},[441,1710,1711],{"class":810},"    \"activatedPlatformsThisSeason\"",[441,1713,1714],{"class":533},": [",[441,1716,1717],{"class":451},"\"common\"",[441,1719,1720],{"class":533},"],\n",[441,1722,1723,1726,1728,1731],{"class":443,"line":1466},[441,1724,1725],{"class":810},"    \"createdAt\"",[441,1727,814],{"class":533},[441,1729,1730],{"class":451},"\"2026-09-15T09:12:44.512Z\"",[441,1732,588],{"class":533},[441,1734,1735,1738,1740],{"class":443,"line":1471},[441,1736,1737],{"class":810},"    \"updatedAt\"",[441,1739,814],{"class":533},[441,1741,1742],{"class":451},"\"2026-09-15T09:12:44.512Z\"\n",[441,1744,1745],{"class":443,"line":1476},[441,1746,1747],{"class":533},"  }\n",[441,1749,1751],{"class":443,"line":1750},22,[441,1752,981],{"class":533},[358,1754,1755,1756,1759,1760,1763],{},"Registration is ",[380,1757,1758],{},"not"," idempotent. If you retry after a timeout and the first attempt actually\nsucceeded, the retry answers ",[361,1761,1762],{},"409 conflict",". Handle that case explicitly:",[432,1765,1767],{"className":520,"code":1766,"language":522,"meta":437,"style":437},"try {\n  await client.students.register(profile);\n} catch (error) {\n  if (error instanceof MtoConflictError) {\n    \u002F\u002F \"A student with that email is already registered to this account (66f2b7c1e4a9d20012ab34cd).\"\n    const existingId = \u002F\\(([0-9a-f]{24})\\)\u002F.exec(error.message)?.[1];\n    if (!existingId) throw error; \u002F\u002F another account holds this address: it can't be used\n    \u002F\u002F fetch or update existingId instead of registering again\n  } else {\n    throw error;\n  }\n}\n",[361,1768,1769,1777,1789,1800,1816,1821,1869,1890,1895,1905,1913,1917],{"__ignoreMap":437},[441,1770,1771,1774],{"class":443,"line":444},[441,1772,1773],{"class":529},"try",[441,1775,1776],{"class":533}," {\n",[441,1778,1779,1782,1784,1786],{"class":443,"line":546},[441,1780,1781],{"class":529},"  await",[441,1783,1348],{"class":533},[441,1785,1351],{"class":569},[441,1787,1788],{"class":533},"(profile);\n",[441,1790,1791,1794,1797],{"class":443,"line":553},[441,1792,1793],{"class":533},"} ",[441,1795,1796],{"class":529},"catch",[441,1798,1799],{"class":533}," (error) {\n",[441,1801,1802,1804,1807,1810,1813],{"class":443,"line":576},[441,1803,1303],{"class":529},[441,1805,1806],{"class":533}," (error ",[441,1808,1809],{"class":529},"instanceof",[441,1811,1812],{"class":447}," MtoConflictError",[441,1814,1815],{"class":533},") {\n",[441,1817,1818],{"class":443,"line":591},[441,1819,1820],{"class":1071},"    \u002F\u002F \"A student with that email is already registered to this account (66f2b7c1e4a9d20012ab34cd).\"\n",[441,1822,1823,1826,1829,1831,1834,1838,1840,1843,1846,1849,1852,1855,1857,1860,1863,1866],{"class":443,"line":604},[441,1824,1825],{"class":529},"    const",[441,1827,1828],{"class":559}," existingId",[441,1830,563],{"class":529},[441,1832,1833],{"class":451}," \u002F",[441,1835,1837],{"class":1836},"sJaa3","\\(",[441,1839,639],{"class":451},[441,1841,1842],{"class":559},"[0-9a-f]",[441,1844,1845],{"class":529},"{24}",[441,1847,1848],{"class":451},")",[441,1850,1851],{"class":1836},"\\)",[441,1853,1854],{"class":451},"\u002F",[441,1856,387],{"class":533},[441,1858,1859],{"class":569},"exec",[441,1861,1862],{"class":533},"(error.message)?.[",[441,1864,1865],{"class":559},"1",[441,1867,1868],{"class":533},"];\n",[441,1870,1871,1874,1876,1878,1881,1884,1887],{"class":443,"line":862},[441,1872,1873],{"class":529},"    if",[441,1875,1268],{"class":533},[441,1877,585],{"class":529},[441,1879,1880],{"class":533},"existingId) ",[441,1882,1883],{"class":529},"throw",[441,1885,1886],{"class":533}," error; ",[441,1888,1889],{"class":1071},"\u002F\u002F another account holds this address: it can't be used\n",[441,1891,1892],{"class":443,"line":899},[441,1893,1894],{"class":1071},"    \u002F\u002F fetch or update existingId instead of registering again\n",[441,1896,1897,1900,1903],{"class":443,"line":930},[441,1898,1899],{"class":533},"  } ",[441,1901,1902],{"class":529},"else",[441,1904,1776],{"class":533},[441,1906,1907,1910],{"class":443,"line":961},[441,1908,1909],{"class":529},"    throw",[441,1911,1912],{"class":533}," error;\n",[441,1914,1915],{"class":443,"line":967},[441,1916,1747],{"class":533},[441,1918,1919],{"class":443,"line":978},[441,1920,981],{"class":533},[358,1922,1923,1924,387],{},"The id is in the message because the message is where the API reports it. Store the id your side\nat registration so you rarely need this. See ",[409,1925,62],{"href":63},[1220,1927,1929],{"id":1928},"step-2-send-the-student-into-the-organization","Step 2: send the student into the organization",[358,1931,1932],{},"An organization can only refer to a student after the student has signed in there once. That first\nsign-in creates the organization's copy of the student. Mint a sign-in link and redirect the\nstudent's browser to it:",[432,1934,1936],{"className":520,"code":1935,"language":522,"meta":437,"style":437},"const stem = client.organization('stem');\nconst link = await stem.auth.signinAsStudent({ studentId: student._id });\n\u002F\u002F link.url: redirect the browser here now. link.expiresIn: 120\n",[361,1937,1938,1956,1976],{"__ignoreMap":437},[441,1939,1940,1942,1944,1946,1948,1950,1952,1954],{"class":443,"line":444},[441,1941,556],{"class":529},[441,1943,1083],{"class":559},[441,1945,563],{"class":529},[441,1947,1062],{"class":533},[441,1949,1090],{"class":569},[441,1951,639],{"class":533},[441,1953,1095],{"class":451},[441,1955,645],{"class":533},[441,1957,1958,1960,1963,1965,1967,1970,1973],{"class":443,"line":546},[441,1959,556],{"class":529},[441,1961,1962],{"class":559}," link",[441,1964,563],{"class":529},[441,1966,758],{"class":529},[441,1968,1969],{"class":533}," stem.auth.",[441,1971,1972],{"class":569},"signinAsStudent",[441,1974,1975],{"class":533},"({ studentId: student._id });\n",[441,1977,1978],{"class":443,"line":553},[441,1979,1980],{"class":1071},"\u002F\u002F link.url: redirect the browser here now. link.expiresIn: 120\n",[358,1982,1983],{},"The API answers:",[432,1985,1987],{"className":796,"code":1986,"language":798,"meta":437,"style":437},"{\n  \"success\": true,\n  \"message\": \"Sign-in link generated successfully.\",\n  \"data\": {\n    \"url\": \"https:\u002F\u002Fauth.main-team.org\u002F…\",\n    \"organization\": \"stem\",\n    \"studentId\": \"66f2b7c1e4a9d20012ab34cd\",\n    \"expiresIn\": 120\n  }\n}\n",[361,1988,1989,1993,2003,2014,2020,2032,2044,2055,2065,2069],{"__ignoreMap":437},[441,1990,1991],{"class":443,"line":444},[441,1992,805],{"class":533},[441,1994,1995,1997,1999,2001],{"class":443,"line":546},[441,1996,1523],{"class":810},[441,1998,814],{"class":533},[441,2000,1528],{"class":559},[441,2002,588],{"class":533},[441,2004,2005,2007,2009,2012],{"class":443,"line":553},[441,2006,1535],{"class":810},[441,2008,814],{"class":533},[441,2010,2011],{"class":451},"\"Sign-in link generated successfully.\"",[441,2013,588],{"class":533},[441,2015,2016,2018],{"class":443,"line":576},[441,2017,1547],{"class":810},[441,2019,1550],{"class":533},[441,2021,2022,2025,2027,2030],{"class":443,"line":591},[441,2023,2024],{"class":810},"    \"url\"",[441,2026,814],{"class":533},[441,2028,2029],{"class":451},"\"https:\u002F\u002Fauth.main-team.org\u002F…\"",[441,2031,588],{"class":533},[441,2033,2034,2037,2039,2042],{"class":443,"line":604},[441,2035,2036],{"class":810},"    \"organization\"",[441,2038,814],{"class":533},[441,2040,2041],{"class":451},"\"stem\"",[441,2043,588],{"class":533},[441,2045,2046,2049,2051,2053],{"class":443,"line":862},[441,2047,2048],{"class":810},"    \"studentId\"",[441,2050,814],{"class":533},[441,2052,1560],{"class":451},[441,2054,588],{"class":533},[441,2056,2057,2060,2062],{"class":443,"line":899},[441,2058,2059],{"class":810},"    \"expiresIn\"",[441,2061,814],{"class":533},[441,2063,2064],{"class":559},"120\n",[441,2066,2067],{"class":443,"line":930},[441,2068,1747],{"class":533},[441,2070,2071],{"class":443,"line":961},[441,2072,981],{"class":533},[688,2074,2076],{"type":2075},"danger",[358,2077,2078,2081,2082,2085,2086,2089,2090,387],{},[380,2079,2080],{},"A sign-in link is a credential."," It signs whoever opens it in as the student, it works ",[380,2083,2084],{},"once",",\nand it expires ",[380,2087,2088],{},"120 seconds"," after it was issued. Mint it when the student clicks, redirect\nstraight away, and never store, log, email or paste it. Chat apps and email scanners open links to\npreview them, which uses the link up. See ",[409,2091,80],{"href":81},[358,2093,2094],{},"In a web app, that looks like this:",[432,2096,2098],{"className":520,"code":2097,"language":522,"meta":437,"style":437},"import express from 'express';\n\nconst app = express();\n\napp.get('\u002Fgo\u002Folympiad', async (req, res, next) => {\n  try {\n    \u002F\u002F Look the student up from YOUR session, never from a query parameter.\n    const studentId = req.session.mainTeamStudentId;\n    const link = await client.organization('stem').auth.signinAsStudent({ studentId });\n    res.redirect(302, link.url);\n  } catch (error) {\n    next(error);\n  }\n});\n",[361,2099,2100,2114,2118,2132,2136,2175,2182,2187,2199,2225,2241,2249,2257,2261],{"__ignoreMap":437},[441,2101,2102,2104,2107,2109,2112],{"class":443,"line":444},[441,2103,530],{"class":529},[441,2105,2106],{"class":533}," express ",[441,2108,537],{"class":529},[441,2110,2111],{"class":451}," 'express'",[441,2113,543],{"class":533},[441,2115,2116],{"class":443,"line":546},[441,2117,550],{"emptyLinePlaceholder":549},[441,2119,2120,2122,2125,2127,2130],{"class":443,"line":553},[441,2121,556],{"class":529},[441,2123,2124],{"class":559}," app",[441,2126,563],{"class":529},[441,2128,2129],{"class":569}," express",[441,2131,767],{"class":533},[441,2133,2134],{"class":443,"line":576},[441,2135,550],{"emptyLinePlaceholder":549},[441,2137,2138,2141,2144,2146,2149,2151,2154,2156,2159,2161,2164,2166,2169,2171,2173],{"class":443,"line":591},[441,2139,2140],{"class":533},"app.",[441,2142,2143],{"class":569},"get",[441,2145,639],{"class":533},[441,2147,2148],{"class":451},"'\u002Fgo\u002Folympiad'",[441,2150,785],{"class":533},[441,2152,2153],{"class":529},"async",[441,2155,1268],{"class":533},[441,2157,2158],{"class":447},"req",[441,2160,785],{"class":533},[441,2162,2163],{"class":447},"res",[441,2165,785],{"class":533},[441,2167,2168],{"class":447},"next",[441,2170,1287],{"class":533},[441,2172,1290],{"class":529},[441,2174,1776],{"class":533},[441,2176,2177,2180],{"class":443,"line":604},[441,2178,2179],{"class":529},"  try",[441,2181,1776],{"class":533},[441,2183,2184],{"class":443,"line":862},[441,2185,2186],{"class":1071},"    \u002F\u002F Look the student up from YOUR session, never from a query parameter.\n",[441,2188,2189,2191,2194,2196],{"class":443,"line":899},[441,2190,1825],{"class":529},[441,2192,2193],{"class":559}," studentId",[441,2195,563],{"class":529},[441,2197,2198],{"class":533}," req.session.mainTeamStudentId;\n",[441,2200,2201,2203,2205,2207,2209,2211,2213,2215,2217,2220,2222],{"class":443,"line":930},[441,2202,1825],{"class":529},[441,2204,1962],{"class":559},[441,2206,563],{"class":529},[441,2208,758],{"class":529},[441,2210,1062],{"class":533},[441,2212,1090],{"class":569},[441,2214,639],{"class":533},[441,2216,1095],{"class":451},[441,2218,2219],{"class":533},").auth.",[441,2221,1972],{"class":569},[441,2223,2224],{"class":533},"({ studentId });\n",[441,2226,2227,2230,2233,2235,2238],{"class":443,"line":961},[441,2228,2229],{"class":533},"    res.",[441,2231,2232],{"class":569},"redirect",[441,2234,639],{"class":533},[441,2236,2237],{"class":559},"302",[441,2239,2240],{"class":533},", link.url);\n",[441,2242,2243,2245,2247],{"class":443,"line":967},[441,2244,1899],{"class":533},[441,2246,1796],{"class":529},[441,2248,1799],{"class":533},[441,2250,2251,2254],{"class":443,"line":978},[441,2252,2253],{"class":569},"    next",[441,2255,2256],{"class":533},"(error);\n",[441,2258,2259],{"class":443,"line":1386},[441,2260,1747],{"class":533},[441,2262,2263],{"class":443,"line":1401},[441,2264,607],{"class":533},[358,2266,2267,2268,2270,2271,2274,2275,2277],{},"The sign-in link is refused with ",[361,2269,1023],{}," if the student has no access to that organization,\nand with ",[361,2272,2273],{},"404 not_found"," if the student isn't one of yours. The\n",[409,2276,103],{"href":104}," tutorial covers both cases.",[1220,2279,2281],{"id":2280},"step-3-choose-an-exam-for-the-student","Step 3: choose an exam for the student",[358,2283,2284,2285,2288,2289,2294,2295,2298],{},"Pick from the exams ",[380,2286,2287],{},"this student"," can take, not from the organization's whole list. The picker,\n",[409,2290,2291],{"href":226},[361,2292,2293],{},"GET \u002Fv1\u002F{organizationId}\u002Fexam\u002Favailable\u002F{studentId}",",\napplies the student's grade and country and leaves out what they already hold. Its tree runs\ncategory, then session (date), then language, and every leaf carries a ",[361,2296,2297],{},"matchedExam",". The package\nREADME names the method that calls it. The organization's open exams are also available as a plain\nlist:",[432,2300,2302],{"className":520,"code":2301,"language":522,"meta":437,"style":437},"const { data: openExams } = await stem.exams.list(); \u002F\u002F open exams only, for every student\n",[361,2303,2304],{"__ignoreMap":437},[441,2305,2306,2308,2310,2312,2314,2317,2319,2321,2323,2325,2327,2329],{"class":443,"line":444},[441,2307,556],{"class":529},[441,2309,624],{"class":533},[441,2311,792],{"class":447},[441,2313,814],{"class":533},[441,2315,2316],{"class":559},"openExams",[441,2318,630],{"class":533},[441,2320,633],{"class":529},[441,2322,758],{"class":529},[441,2324,1118],{"class":533},[441,2326,1121],{"class":569},[441,2328,1068],{"class":533},[441,2330,2331],{"class":1071},"\u002F\u002F open exams only, for every student\n",[358,2333,2334,2335,387],{},"An exam missing from that list is closed. It can't be read by id or applied to. See\n",[409,2336,83],{"href":84},[1220,2338,2340],{"id":2339},"step-4-enter-the-student","Step 4: enter the student",[432,2342,2344],{"className":520,"code":2343,"language":522,"meta":437,"style":437},"const { data: application } = await stem.applications.create({\n  examId: leaf.matchedExam._id, \u002F\u002F from the picker\n  studentId: student._id,       \u002F\u002F the core id from registration\n});\n",[361,2345,2346,2373,2381,2389],{"__ignoreMap":437},[441,2347,2348,2350,2352,2354,2356,2359,2361,2363,2365,2368,2371],{"class":443,"line":444},[441,2349,556],{"class":529},[441,2351,624],{"class":533},[441,2353,792],{"class":447},[441,2355,814],{"class":533},[441,2357,2358],{"class":559},"application",[441,2360,630],{"class":533},[441,2362,633],{"class":529},[441,2364,758],{"class":529},[441,2366,2367],{"class":533}," stem.applications.",[441,2369,2370],{"class":569},"create",[441,2372,573],{"class":533},[441,2374,2375,2378],{"class":443,"line":546},[441,2376,2377],{"class":533},"  examId: leaf.matchedExam._id, ",[441,2379,2380],{"class":1071},"\u002F\u002F from the picker\n",[441,2382,2383,2386],{"class":443,"line":553},[441,2384,2385],{"class":533},"  studentId: student._id,       ",[441,2387,2388],{"class":1071},"\u002F\u002F the core id from registration\n",[441,2390,2391],{"class":443,"line":576},[441,2392,607],{"class":533},[464,2394,2395,2404],{},[467,2396,2397],{},[470,2398,2399,2402],{},[473,2400,2401],{},"Answer",[473,2403,996],{},[483,2405,2406,2419,2432,2441],{},[470,2407,2408,2416],{},[488,2409,2410,2412,2413],{},[361,2411,1508],{}," ",[361,2414,2415],{},"\"Application created successfully.\"",[488,2417,2418],{},"A new application.",[470,2420,2421,2429],{},[488,2422,2423,2412,2426],{},[361,2424,2425],{},"200",[361,2427,2428],{},"\"Application already exists.\"",[488,2430,2431],{},"The student already holds this exam. You get the existing application, so a retry is safe.",[470,2433,2434,2438],{},[488,2435,2436],{},[361,2437,1762],{},[488,2439,2440],{},"The exam isn't one the picker would offer (closed, wrong grade or country, no language, or a clash with another exam in the same category on the same sitting), or the student has never signed in to this organization. The message says which.",[470,2442,2443,2447],{},[488,2444,2445],{},[361,2446,2273],{},[488,2448,2449],{},"The exam or the student doesn't exist, or the student isn't yours.",[358,2451,2452,2453,2455],{},"See ",[409,2454,86],{"href":87}," for moving and deleting applications, and how\npayments behave.",[369,2457,42],{"id":2458},"errors",[358,2460,2461,2462,2464,2465,387],{},"Every failure is thrown as an exception that carries the same ",[361,2463,361],{}," and HTTP status the API\nreturns. The codes are listed in the ",[409,2466,2467],{"href":43},"error reference",[432,2469,2471],{"className":520,"code":2470,"language":522,"meta":437,"style":437},"import { MtoConflictError, MtoError } from '@main-team\u002Fapi-client';\n\ntry {\n  await stem.applications.create({ examId, studentId });\n} catch (error) {\n  if (error instanceof MtoConflictError) {\n    console.error(error.message, error.documentationUrl);\n  } else if (error instanceof MtoError) {\n    console.error(error.code, error.status);\n  } else {\n    throw error; \u002F\u002F a network failure or a bug: not an API answer\n  }\n}\n",[361,2472,2473,2486,2490,2496,2507,2515,2527,2538,2556,2565,2573,2582,2586],{"__ignoreMap":437},[441,2474,2475,2477,2480,2482,2484],{"class":443,"line":444},[441,2476,530],{"class":529},[441,2478,2479],{"class":533}," { MtoConflictError, MtoError } ",[441,2481,537],{"class":529},[441,2483,540],{"class":451},[441,2485,543],{"class":533},[441,2487,2488],{"class":443,"line":546},[441,2489,550],{"emptyLinePlaceholder":549},[441,2491,2492,2494],{"class":443,"line":553},[441,2493,1773],{"class":529},[441,2495,1776],{"class":533},[441,2497,2498,2500,2502,2504],{"class":443,"line":576},[441,2499,1781],{"class":529},[441,2501,2367],{"class":533},[441,2503,2370],{"class":569},[441,2505,2506],{"class":533},"({ examId, studentId });\n",[441,2508,2509,2511,2513],{"class":443,"line":591},[441,2510,1793],{"class":533},[441,2512,1796],{"class":529},[441,2514,1799],{"class":533},[441,2516,2517,2519,2521,2523,2525],{"class":443,"line":604},[441,2518,1303],{"class":529},[441,2520,1806],{"class":533},[441,2522,1809],{"class":529},[441,2524,1812],{"class":447},[441,2526,1815],{"class":533},[441,2528,2529,2532,2535],{"class":443,"line":862},[441,2530,2531],{"class":533},"    console.",[441,2533,2534],{"class":569},"error",[441,2536,2537],{"class":533},"(error.message, error.documentationUrl);\n",[441,2539,2540,2542,2544,2547,2549,2551,2554],{"class":443,"line":899},[441,2541,1899],{"class":533},[441,2543,1902],{"class":529},[441,2545,2546],{"class":529}," if",[441,2548,1806],{"class":533},[441,2550,1809],{"class":529},[441,2552,2553],{"class":447}," MtoError",[441,2555,1815],{"class":533},[441,2557,2558,2560,2562],{"class":443,"line":930},[441,2559,2531],{"class":533},[441,2561,2534],{"class":569},[441,2563,2564],{"class":533},"(error.code, error.status);\n",[441,2566,2567,2569,2571],{"class":443,"line":961},[441,2568,1899],{"class":533},[441,2570,1902],{"class":529},[441,2572,1776],{"class":533},[441,2574,2575,2577,2579],{"class":443,"line":967},[441,2576,1909],{"class":529},[441,2578,1886],{"class":533},[441,2580,2581],{"class":1071},"\u002F\u002F a network failure or a bug: not an API answer\n",[441,2583,2584],{"class":443,"line":978},[441,2585,1747],{"class":533},[441,2587,2588],{"class":443,"line":1386},[441,2589,981],{"class":533},[358,2591,2592,2593,2596,2597,2600,2601,423,2604,2607],{},"A call that can't possibly succeed is rejected ",[380,2594,2595],{},"before"," the request is sent, as\n",[361,2598,2599],{},"MtoValidationError",". It carries ",[361,2602,2603],{},"code: 'bad_request'",[361,2605,2606],{},"status: 400",", the values the API would\nhave returned, so one handler covers both:",[432,2609,2611],{"className":520,"code":2610,"language":522,"meta":437,"style":437},"await client.students.register({ firstName: 'Jane', country: 'XX', grade: '8' });\n\u002F\u002F MtoValidationError: country must be a 24-character hex id, received 'XX'.\n",[361,2612,2613,2640],{"__ignoreMap":437},[441,2614,2615,2617,2619,2621,2624,2626,2629,2632,2635,2637],{"class":443,"line":444},[441,2616,1059],{"class":529},[441,2618,1348],{"class":533},[441,2620,1351],{"class":569},[441,2622,2623],{"class":533},"({ firstName: ",[441,2625,1361],{"class":451},[441,2627,2628],{"class":533},", country: ",[441,2630,2631],{"class":451},"'XX'",[441,2633,2634],{"class":533},", grade: ",[441,2636,1428],{"class":451},[441,2638,2639],{"class":533}," });\n",[441,2641,2642],{"class":443,"line":546},[441,2643,2644],{"class":1071},"\u002F\u002F MtoValidationError: country must be a 24-character hex id, received 'XX'.\n",[358,2646,2647,2648,2650],{},"The client never adds a rule the API doesn't have. Anything the API accepts passes through\nuntouched, including properties the client doesn't model. The API then refuses properties it\ndoesn't declare with ",[361,2649,1501],{},", naming the property.",[358,2652,2653,2654,423,2656,2659,2660,2662],{},"Branch on ",[361,2655,361],{},[361,2657,2658],{},"status",". Treat ",[361,2661,788],{}," as text for people: it can change between\nreleases.",[464,2664,2665,2678],{},[467,2666,2667],{},[470,2668,2669,2672,2675],{},[473,2670,2671],{},"Status and code",[473,2673,2674],{},"Retry?",[473,2676,2677],{},"Typical fix",[483,2679,2680,2692,2705,2718,2729,2740,2756],{},[470,2681,2682,2686,2689],{},[488,2683,2684],{},[361,2685,1501],{},[488,2687,2688],{},"No",[488,2690,2691],{},"Fix the request. The message names the field.",[470,2693,2694,2698,2700],{},[488,2695,2696],{},[361,2697,1008],{},[488,2699,2688],{},[488,2701,2702,2703,387],{},"Check the key, the secret and your clock. See ",[409,2704,19],{"href":20},[470,2706,2707,2711,2713],{},[488,2708,2709],{},[361,2710,1023],{},[488,2712,2688],{},[488,2714,2715,2716,387],{},"Ask your operator for the permission. See ",[409,2717,25],{"href":26},[470,2719,2720,2724,2726],{},[488,2721,2722],{},[361,2723,2273],{},[488,2725,2688],{},[488,2727,2728],{},"Check the id, and that the record is yours.",[470,2730,2731,2735,2737],{},[488,2732,2733],{},[361,2734,1762],{},[488,2736,2688],{},[488,2738,2739],{},"Read the message: it names the rule.",[470,2741,2742,2747,2753],{},[488,2743,2744],{},[361,2745,2746],{},"429 too_many_requests",[488,2748,2749,2750],{},"Yes, after ",[361,2751,2752],{},"Retry-After",[488,2754,2755],{},"Slow down. See below.",[470,2757,2758,2763,2766],{},[488,2759,2760],{},[361,2761,2762],{},"500 internal_error",[488,2764,2765],{},"Yes, with backoff",[488,2767,2768,2769,387],{},"Retry later. If it persists, report the ",[361,2770,2771],{},"request_id",[369,2773,39],{"id":2774},"pagination",[358,2776,2777,2778,2781,2782,2784,2785,2781,2788,2791,2792,2795],{},"List methods return ",[361,2779,2780],{},"{ data, pagination }",". ",[361,2783,2774],{}," is\n",[361,2786,2787],{},"{ page, limit, total, totalPages }",[361,2789,2790],{},"page"," starts at 1, and ",[361,2793,2794],{},"limit"," defaults to 20 with a maximum\nof 100. A larger value is lowered to 100 rather than refused.",[432,2797,2799],{"className":520,"code":2798,"language":522,"meta":437,"style":437},"const { data, pagination } = await client.students.list({ page: 2, limit: 50 });\nconsole.log(`${data.length} of ${pagination.total}`);\n",[361,2800,2801,2837],{"__ignoreMap":437},[441,2802,2803,2805,2807,2809,2811,2813,2815,2817,2819,2821,2823,2826,2829,2832,2835],{"class":443,"line":444},[441,2804,556],{"class":529},[441,2806,624],{"class":533},[441,2808,792],{"class":559},[441,2810,785],{"class":533},[441,2812,2774],{"class":559},[441,2814,630],{"class":533},[441,2816,633],{"class":529},[441,2818,758],{"class":529},[441,2820,1348],{"class":533},[441,2822,1121],{"class":569},[441,2824,2825],{"class":533},"({ page: ",[441,2827,2828],{"class":559},"2",[441,2830,2831],{"class":533},", limit: ",[441,2833,2834],{"class":559},"50",[441,2836,2639],{"class":533},[441,2838,2839,2841,2843,2845,2848,2850,2852,2855,2858,2860,2862,2865,2868],{"class":443,"line":546},[441,2840,772],{"class":533},[441,2842,775],{"class":569},[441,2844,639],{"class":533},[441,2846,2847],{"class":451},"`${",[441,2849,792],{"class":533},[441,2851,387],{"class":451},[441,2853,2854],{"class":559},"length",[441,2856,2857],{"class":451},"} of ${",[441,2859,2774],{"class":533},[441,2861,387],{"class":451},[441,2863,2864],{"class":533},"total",[441,2866,2867],{"class":451},"}`",[441,2869,645],{"class":533},[358,2871,2872,2875],{},[361,2873,2874],{},"autoPaginate"," walks every page:",[432,2877,2879],{"className":520,"code":2878,"language":522,"meta":437,"style":437},"import { autoPaginate } from '@main-team\u002Fapi-client';\n\nfor await (const student of autoPaginate((p) => client.students.list(p))) {\n  console.log(student.username);\n}\n",[361,2880,2881,2894,2898,2928,2938],{"__ignoreMap":437},[441,2882,2883,2885,2888,2890,2892],{"class":443,"line":444},[441,2884,530],{"class":529},[441,2886,2887],{"class":533}," { autoPaginate } ",[441,2889,537],{"class":529},[441,2891,540],{"class":451},[441,2893,543],{"class":533},[441,2895,2896],{"class":443,"line":546},[441,2897,550],{"emptyLinePlaceholder":549},[441,2899,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926],{"class":443,"line":553},[441,2901,1263],{"class":529},[441,2903,758],{"class":529},[441,2905,1268],{"class":533},[441,2907,556],{"class":529},[441,2909,1341],{"class":559},[441,2911,1276],{"class":529},[441,2913,1279],{"class":569},[441,2915,1282],{"class":533},[441,2917,358],{"class":447},[441,2919,1287],{"class":533},[441,2921,1290],{"class":529},[441,2923,1348],{"class":533},[441,2925,1121],{"class":569},[441,2927,1298],{"class":533},[441,2929,2930,2933,2935],{"class":443,"line":576},[441,2931,2932],{"class":533},"  console.",[441,2934,775],{"class":569},[441,2936,2937],{"class":533},"(student.username);\n",[441,2939,2940],{"class":443,"line":591},[441,2941,981],{"class":533},[358,2943,2944,2945,387],{},"Each page is one request, and it counts against that route's rate limit. Walking 5,000 students at\n100 per page is 50 requests, half of one minute's budget for that route. See\n",[409,2946,39],{"href":40},[369,2948,2950],{"id":2949},"downloads","Downloads",[358,2952,2953,2954,2956,2957,2960],{},"Certificate and report downloads return the PDF itself, not JSON. Only released documents can be\nlisted or downloaded. Pass either the document's ",[361,2955,1048],{}," or its ",[361,2958,2959],{},"shortId",":",[432,2962,2964],{"className":520,"code":2963,"language":522,"meta":437,"style":437},"import { writeFile } from 'node:fs\u002Fpromises';\nimport { basename } from 'node:path';\n\nconst file = await stem.certificates.download(certificateId); \u002F\u002F _id or shortId\nawait writeFile(basename(file.fileName ?? 'certificate.pdf'), await file.bytes());\n",[361,2965,2966,2980,2994,2998,3021],{"__ignoreMap":437},[441,2967,2968,2970,2973,2975,2978],{"class":443,"line":444},[441,2969,530],{"class":529},[441,2971,2972],{"class":533}," { writeFile } ",[441,2974,537],{"class":529},[441,2976,2977],{"class":451}," 'node:fs\u002Fpromises'",[441,2979,543],{"class":533},[441,2981,2982,2984,2987,2989,2992],{"class":443,"line":546},[441,2983,530],{"class":529},[441,2985,2986],{"class":533}," { basename } ",[441,2988,537],{"class":529},[441,2990,2991],{"class":451}," 'node:path'",[441,2993,543],{"class":533},[441,2995,2996],{"class":443,"line":553},[441,2997,550],{"emptyLinePlaceholder":549},[441,2999,3000,3002,3005,3007,3009,3012,3015,3018],{"class":443,"line":576},[441,3001,556],{"class":529},[441,3003,3004],{"class":559}," file",[441,3006,563],{"class":529},[441,3008,758],{"class":529},[441,3010,3011],{"class":533}," stem.certificates.",[441,3013,3014],{"class":569},"download",[441,3016,3017],{"class":533},"(certificateId); ",[441,3019,3020],{"class":1071},"\u002F\u002F _id or shortId\n",[441,3022,3023,3025,3028,3030,3033,3036,3039,3042,3045,3047,3050,3053],{"class":443,"line":591},[441,3024,1059],{"class":529},[441,3026,3027],{"class":569}," writeFile",[441,3029,639],{"class":533},[441,3031,3032],{"class":569},"basename",[441,3034,3035],{"class":533},"(file.fileName ",[441,3037,3038],{"class":529},"??",[441,3040,3041],{"class":451}," 'certificate.pdf'",[441,3043,3044],{"class":533},"), ",[441,3046,1059],{"class":529},[441,3048,3049],{"class":533}," file.",[441,3051,3052],{"class":569},"bytes",[441,3054,3055],{"class":533},"());\n",[358,3057,3058,3059,3061,3062,387],{},"A document that doesn't exist, isn't released yet, isn't yours or has no file all answer the same\n",[361,3060,2273],{},". The file name comes from the server and can contain non-ASCII characters; take\nits last path segment, as above, before you write it to disk. See\n",[409,3063,92],{"href":93},[369,3065,3067],{"id":3066},"rate-limits-and-retries","Rate limits and retries",[358,3069,3070,3071,3074,3075,3078,3079,785,3082,423,3085,3088,3089,3091,3092,3094],{},"The limit is ",[380,3072,3073],{},"100 requests per 60 seconds, per account, per operation",", each operation counted\non its own (",[409,3076,45],{"href":3077},"\u002Fapi\u002Frate-limits#what-per-operation-means","). Every counted response\ncarries ",[361,3080,3081],{},"X-RateLimit-Limit",[361,3083,3084],{},"X-RateLimit-Remaining",[361,3086,3087],{},"X-RateLimit-Reset"," (seconds until the\nwindow resets). A ",[361,3090,2746],{}," carries ",[361,3093,2752],{}," in seconds.",[358,3096,3097,3098,3100,3101,3103],{},"Wait at least ",[361,3099,2752],{}," seconds before calling that operation again. Requests sent during the\nblock are refused too, though they don't extend it. A block never lasts longer than one 60-second\nwindow. So if the error you catch doesn't expose ",[361,3102,2752],{},", waiting 60 seconds always clears\nit:",[432,3105,3107],{"className":520,"code":3106,"language":522,"meta":437,"style":437},"import { setTimeout as sleep } from 'node:timers\u002Fpromises';\nimport { MtoError } from '@main-team\u002Fapi-client';\n\nasync function withRateLimitRetry(call, attempts = 3) {\n  for (let attempt = 1; ; attempt++) {\n    try {\n      return await call();\n    } catch (error) {\n      const limited = error instanceof MtoError && error.status === 429;\n      if (!limited || attempt === attempts) throw error;\n      await sleep(60_000); \u002F\u002F one full window always clears a block\n    }\n  }\n}\n\nconst page = await withRateLimitRetry(() => client.students.list({ page: 1, limit: 100 }));\n",[361,3108,3109,3129,3142,3146,3173,3198,3205,3217,3226,3256,3282,3301,3306,3310,3314,3318],{"__ignoreMap":437},[441,3110,3111,3113,3116,3119,3122,3124,3127],{"class":443,"line":444},[441,3112,530],{"class":529},[441,3114,3115],{"class":533}," { setTimeout ",[441,3117,3118],{"class":529},"as",[441,3120,3121],{"class":533}," sleep } ",[441,3123,537],{"class":529},[441,3125,3126],{"class":451}," 'node:timers\u002Fpromises'",[441,3128,543],{"class":533},[441,3130,3131,3133,3136,3138,3140],{"class":443,"line":546},[441,3132,530],{"class":529},[441,3134,3135],{"class":533}," { MtoError } ",[441,3137,537],{"class":529},[441,3139,540],{"class":451},[441,3141,543],{"class":533},[441,3143,3144],{"class":443,"line":553},[441,3145,550],{"emptyLinePlaceholder":549},[441,3147,3148,3150,3153,3156,3158,3161,3163,3166,3168,3171],{"class":443,"line":576},[441,3149,2153],{"class":529},[441,3151,3152],{"class":529}," function",[441,3154,3155],{"class":569}," withRateLimitRetry",[441,3157,639],{"class":533},[441,3159,3160],{"class":447},"call",[441,3162,785],{"class":533},[441,3164,3165],{"class":447},"attempts",[441,3167,563],{"class":529},[441,3169,3170],{"class":559}," 3",[441,3172,1815],{"class":533},[441,3174,3175,3178,3180,3182,3185,3187,3190,3193,3196],{"class":443,"line":591},[441,3176,3177],{"class":529},"  for",[441,3179,1268],{"class":533},[441,3181,1255],{"class":529},[441,3183,3184],{"class":533}," attempt ",[441,3186,633],{"class":529},[441,3188,3189],{"class":559}," 1",[441,3191,3192],{"class":533},"; ; attempt",[441,3194,3195],{"class":529},"++",[441,3197,1815],{"class":533},[441,3199,3200,3203],{"class":443,"line":604},[441,3201,3202],{"class":529},"    try",[441,3204,1776],{"class":533},[441,3206,3207,3210,3212,3215],{"class":443,"line":862},[441,3208,3209],{"class":529},"      return",[441,3211,758],{"class":529},[441,3213,3214],{"class":569}," call",[441,3216,767],{"class":533},[441,3218,3219,3222,3224],{"class":443,"line":899},[441,3220,3221],{"class":533},"    } ",[441,3223,1796],{"class":529},[441,3225,1799],{"class":533},[441,3227,3228,3231,3234,3236,3239,3241,3243,3246,3249,3251,3254],{"class":443,"line":930},[441,3229,3230],{"class":529},"      const",[441,3232,3233],{"class":559}," limited",[441,3235,563],{"class":529},[441,3237,3238],{"class":533}," error ",[441,3240,1809],{"class":529},[441,3242,2553],{"class":447},[441,3244,3245],{"class":529}," &&",[441,3247,3248],{"class":533}," error.status ",[441,3250,1309],{"class":529},[441,3252,3253],{"class":559}," 429",[441,3255,543],{"class":533},[441,3257,3258,3261,3263,3265,3268,3271,3273,3275,3278,3280],{"class":443,"line":961},[441,3259,3260],{"class":529},"      if",[441,3262,1268],{"class":533},[441,3264,585],{"class":529},[441,3266,3267],{"class":533},"limited ",[441,3269,3270],{"class":529},"||",[441,3272,3184],{"class":533},[441,3274,1309],{"class":529},[441,3276,3277],{"class":533}," attempts) ",[441,3279,1883],{"class":529},[441,3281,1912],{"class":533},[441,3283,3284,3287,3290,3292,3295,3298],{"class":443,"line":967},[441,3285,3286],{"class":529},"      await",[441,3288,3289],{"class":569}," sleep",[441,3291,639],{"class":533},[441,3293,3294],{"class":559},"60_000",[441,3296,3297],{"class":533},"); ",[441,3299,3300],{"class":1071},"\u002F\u002F one full window always clears a block\n",[441,3302,3303],{"class":443,"line":978},[441,3304,3305],{"class":533},"    }\n",[441,3307,3308],{"class":443,"line":1386},[441,3309,1747],{"class":533},[441,3311,3312],{"class":443,"line":1401},[441,3313,981],{"class":533},[441,3315,3316],{"class":443,"line":1416},[441,3317,550],{"emptyLinePlaceholder":549},[441,3319,3320,3322,3325,3327,3329,3331,3334,3336,3338,3340,3342,3344,3346,3349],{"class":443,"line":1422},[441,3321,556],{"class":529},[441,3323,3324],{"class":559}," page",[441,3326,563],{"class":529},[441,3328,758],{"class":529},[441,3330,3155],{"class":569},[441,3332,3333],{"class":533},"(() ",[441,3335,1290],{"class":529},[441,3337,1348],{"class":533},[441,3339,1121],{"class":569},[441,3341,2825],{"class":533},[441,3343,1865],{"class":559},[441,3345,2831],{"class":533},[441,3347,3348],{"class":559},"100",[441,3350,3351],{"class":533}," }));\n",[358,3353,3354,3355,3358,3359,3362,3363,3366,3367,423,3370,387],{},"Only retry what is safe to repeat. Reads and ",[361,3356,3357],{},"POST \u002Fapplication"," are safe. ",[361,3360,3361],{},"POST \u002Fstudent"," isn't:\nsee step 1 above. Don't retry any other ",[361,3364,3365],{},"4xx",", and never retry a sign-in link. See\n",[409,3368,3369],{"href":49},"Retries and idempotency",[409,3371,45],{"href":46},[369,3373,3375],{"id":3374},"responses-and-types","Responses and types",[358,3377,3378],{},"The package ships TypeScript types for the documented fields. Responses may gain fields over time\nwithout a new major version, so write your code to ignore fields it doesn't know. Two response\ndetails catch people out:",[374,3380,3381,3430],{},[377,3382,3383,3386,3387,785,3390,785,3392,588,3394,785,3396,423,3399,3402,3403,3406,3407,3410,3411,3414,3415,785,3417,785,3420,785,3423,3426,3427,3429],{},[380,3384,3385],{},"Reads resolve their references."," A student read carries ",[361,3388,3389],{},"country",[361,3391,1494],{},[361,3393,1497],{},[361,3395,1491],{},[361,3397,3398],{},"supervisor",[361,3400,3401],{},"partner"," as objects when they're set. An application read carries its\n",[361,3404,3405],{},"exam",", its ",[361,3408,3409],{},"payment"," and a short ",[361,3412,3413],{},"user"," object (",[361,3416,1048],{},[361,3418,3419],{},"mainId",[361,3421,3422],{},"firstName",[361,3424,3425],{},"lastName","). You\ndon't need a second call per row. The exception is the application listing by exam, where ",[361,3428,3405],{},"\nstays an id because every row shares the exam you asked for. Registration and updates answer\nwith the reference fields as ids.",[377,3431,3432,3439,3440,3442,3443,3446,3447,3450,3451,3453,3454,387],{},[380,3433,3434,3435,423,3437,387],{},"The student on an application carries ",[361,3436,1048],{},[361,3438,3419],{}," The ",[361,3441,3413],{}," on an application is\nthe organization's copy of the student, so ",[361,3444,3445],{},"user._id"," is the organization's id for them.\n",[361,3448,3449],{},"user.mainId"," is the id you registered them with. Match on ",[361,3452,3419],{},". See\n",[409,3455,36],{"href":37},[369,3457,3459],{"id":3458},"next-steps","Next steps",[374,3461,3462,3467,3472,3477],{},[377,3463,3464,3466],{},[409,3465,100],{"href":101},": the full flow, end to end.",[377,3468,3469,3471],{},[409,3470,109],{"href":110},": a nightly job for certificates and reports.",[377,3473,3474,3476],{},[409,3475,112],{"href":113},": if you sign tokens yourself alongside the\nclient.",[377,3478,3479,423,3482,387],{},[409,3480,3481],{"href":312},"PHP client",[409,3483,314],{"href":315},[3485,3486,3487],"style",{},"html pre.shiki code .soyes, html code.shiki .soyes{--shiki-default:#702C00;--shiki-dark:#FFB757}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .sKwhi, html code.shiki .sKwhi{--shiki-default:#622CBC;--shiki-dark:#DBB7FF}html pre.shiki code .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}html pre.shiki code .sJaa3, html code.shiki .sJaa3{--shiki-default:#024C1A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}",{"title":437,"searchDepth":546,"depth":553,"links":3489},[3490,3491,3492,3493,3494,3495,3501,3502,3503,3504,3505,3506],{"id":371,"depth":546,"text":372},{"id":415,"depth":546,"text":416},{"id":458,"depth":546,"text":459},{"id":728,"depth":546,"text":729},{"id":1041,"depth":546,"text":1042},{"id":1211,"depth":546,"text":1212,"children":3496},[3497,3498,3499,3500],{"id":1222,"depth":553,"text":1223},{"id":1928,"depth":553,"text":1929},{"id":2280,"depth":553,"text":2281},{"id":2339,"depth":553,"text":2340},{"id":2458,"depth":546,"text":42},{"id":2774,"depth":546,"text":39},{"id":2949,"depth":546,"text":2950},{"id":3066,"depth":546,"text":3067},{"id":3374,"depth":546,"text":3375},{"id":3458,"depth":546,"text":3459},"Install and use @main-team\u002Fapi-client for Node.js and TypeScript. Covers setup, organizations, a complete flow, errors, pagination, downloads and rate limits.","md",{},[3511,3512,3513,3514,3515,3516,3517,3518,3519,3520],"getCurrentApiAccount","listOrganizations","listCountries","registerStudent","createSigninLink","listExams","listAvailableExams","createApplication","listStudents","downloadCertificate","20",{"title":353,"description":3507},"api\u002Fclients\u002Fnode","Wlw2vuSFC9B1e4m3V7_le6gxUySsaIfo0iLf54EjhZE",[3526,3531,3535,3539,3543,3547,3552,3556,3561,3564],{"operationId":3511,"slug":3527,"method":133,"path":3528,"tag":77,"summary":143,"deprecated":134,"public":134,"permission":3529,"scope":3530,"order":553},"get-current-api-account","\u002Fv1\u002Fapi-account\u002Fvalidate-me","api\u002F*:$org:$ID","flat",{"operationId":3512,"slug":3532,"method":133,"path":3533,"tag":74,"summary":161,"deprecated":134,"public":134,"permission":3534,"scope":3530,"order":899},"list-organizations","\u002Fv1\u002Forganization","organization\u002Fread:$org:$ID",{"operationId":3513,"slug":3536,"method":133,"path":3537,"tag":74,"summary":149,"deprecated":134,"public":134,"permission":3538,"scope":3530,"order":576},"list-countries","\u002Fv1\u002Fcountry","country\u002Fread:$org:$ID",{"operationId":3514,"slug":3540,"method":141,"path":3541,"tag":62,"summary":173,"deprecated":134,"public":134,"permission":3542,"scope":3530,"order":967},"register-student","\u002Fv1\u002Fstudent","student\u002Fcreate:$org:$ID",{"operationId":3515,"slug":3544,"method":141,"path":3545,"tag":80,"summary":210,"deprecated":134,"public":134,"permission":3546,"scope":1090,"order":1750},"create-signin-link","\u002Fv1\u002F{organizationId}\u002Fauth\u002Fsignin","auth\u002Fsignin:$org:$ID",{"operationId":3516,"slug":3548,"method":133,"path":3549,"tag":83,"summary":216,"deprecated":134,"public":134,"permission":3550,"scope":1090,"order":3551},"list-exams","\u002Fv1\u002F{organizationId}\u002Fexam","exam\u002Fread:$org:$ID",23,{"operationId":3517,"slug":3553,"method":133,"path":3554,"tag":83,"summary":225,"deprecated":134,"public":134,"permission":3550,"scope":1090,"order":3555},"list-available-exams","\u002Fv1\u002F{organizationId}\u002Fexam\u002Favailable\u002F{studentId}",26,{"operationId":3518,"slug":3557,"method":141,"path":3558,"tag":86,"summary":237,"deprecated":134,"public":134,"permission":3559,"scope":1090,"order":3560},"create-application","\u002Fv1\u002F{organizationId}\u002Fapplication","application\u002Fcreate:$org:$ID",29,{"operationId":3519,"slug":3562,"method":133,"path":3541,"tag":62,"summary":170,"deprecated":134,"public":134,"permission":3563,"scope":3530,"order":961},"list-students","student\u002Fread:$org:$ID",{"operationId":3520,"slug":3565,"method":133,"path":3566,"tag":256,"summary":260,"deprecated":134,"public":134,"permission":3567,"scope":1090,"order":3568},"download-certificate","\u002Fv1\u002F{organizationId}\u002Fcertificate\u002Fdownload\u002F{certificateId}","certificate\u002Fread:$org:$ID",35,1791554615378]