[{"data":1,"prerenderedAt":2524},["ShallowReactive",2],{"api-nav":3,"api-guide:\u002Fapi\u002Fguides\u002Fsign-in-links":351,"api-spec:guide:\u002Fapi\u002Fguides\u002Fsign-in-links":2519},[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":80,"body":353,"description":2510,"extension":2511,"meta":2512,"navTitle":80,"navigation":1332,"operations":2513,"order":2515,"path":81,"section":58,"seo":2516,"status":14,"stem":2517,"__hash__":2518},"apiGuides\u002Fapi\u002Fguides\u002Fsign-in-links.md",{"type":354,"value":355,"toc":2490},"minimark",[356,360,374,379,382,467,483,502,506,511,527,532,590,600,673,782,789,793,850,853,856,865,871,905,1019,1031,1051,1055,1062,1236,1243,1246,1265,1293,1297,1300,1304,1741,1744,2116,2121,2125,2148,2155,2162,2166,2169,2183,2189,2203,2218,2221,2228,2231,2303,2310,2313,2316,2328,2331,2356,2364,2376,2382,2387,2391,2437,2441,2486],[357,358,359],"p",{},"A sign-in link signs one of your students in to an organization's student panel. You don't need their password. You request the link from your server and then send the student's browser to it. It works once, and only for 120 seconds.",[357,361,362,363,367,368,373],{},"Links are the recommended way to get students into the panel. You don't have to create, store or hand out passwords, and you can decide when each student enters. The link also does a second job: ",[364,365,366],"strong",{},"the first time a student follows a link to an organization, that organization creates its own copy of the student",". Applications and supervisor links need that copy (see ",[369,370,372],"a",{"href":371},"#why-the-first-sign-in-matters","Why the first sign-in matters",").",[375,376,378],"h2",{"id":377},"before-you-start","Before you start",[357,380,381],{},"A link is issued only when all of the following are true:",[383,384,385,398],"table",{},[386,387,388],"thead",{},[389,390,391,395],"tr",{},[392,393,394],"th",{},"Requirement",[392,396,397],{},"How to meet it",[399,400,401,441,455],"tbody",{},[389,402,403,412],{},[404,405,406,407,411],"td",{},"Your account holds the ",[408,409,410],"code",{},"auth\u002Fsignin"," permission on the organization in the path",[404,413,414,415,418,419,421,422,425,426,429,430,433,434,437,438,440],{},"Ask your operator for an ",[408,416,417],{},"allow"," role with action ",[408,420,410],{}," (or ",[408,423,424],{},"auth\u002F*",") and target ",[408,427,428],{},"*"," or that organization's slug. ",[408,431,432],{},"student\u002F*"," roles do ",[364,435,436],{},"not"," grant it. See ",[369,439,25],{"href":26},".",[389,442,443,446],{},[404,444,445],{},"The student is yours",[404,447,448,449,452,453,440],{},"The ",[408,450,451],{},"studentId"," must be a student your API account registered. Another account's student answers exactly like a missing one. See ",[369,454,22],{"href":23},[389,456,457,460],{},[404,458,459],{},"The student has access to that organization",[404,461,462,463,440],{},"Students you register get access to every organization unless you restrict it. See ",[369,464,466],{"href":465},"#granting-access-to-an-organization","Granting access to an organization",[357,468,469,470,472,473,478,479,440],{},"The student does ",[364,471,436],{}," need a confirmed email address to use a link. A student you register through ",[369,474,475],{"href":174},[408,476,477],{},"POST \u002Fv1\u002Fstudent"," starts out unconfirmed, and you can send them a link immediately. Once they land, the panel asks them to confirm the address before they can use it. See ",[369,480,482],{"href":481},"#email-confirmation","Email confirmation",[484,485,487],"callout",{"type":486},"security",[357,488,489,490,494,495,497,498,501],{},"Signing in ",[491,492,493],"em",{},"as"," a student is a stronger power than reading or updating one, which is why it has its own permission. The same ",[408,496,410],{}," grant (on ",[408,499,500],{},"mto",") is needed to set a student's password. Give it only to the server that actually sends students into the panel.",[375,503,505],{"id":504},"request-a-link","Request a link",[357,507,508],{},[408,509,510],{},"POST \u002Fv1\u002F\u003CorganizationId>\u002Fauth\u002Fsignin",[357,512,513,516,517,520,521,526],{},[408,514,515],{},"\u003CorganizationId>"," is the organization's ",[408,518,519],{},"_id"," from ",[369,522,523],{"href":162},[408,524,525],{},"GET \u002Fv1\u002Forganization",". It is never the slug. The link signs the student in to that organization.",[528,529,531],"h3",{"id":530},"request-body","Request body",[383,533,534,550],{},[386,535,536],{},[389,537,538,541,544,547],{},[392,539,540],{},"Field",[392,542,543],{},"Type",[392,545,546],{},"Required",[392,548,549],{},"Rules",[399,551,552,570],{},[389,553,554,558,561,564],{},[404,555,556],{},[408,557,451],{},[404,559,560],{},"string",[404,562,563],{},"yes",[404,565,566,567,569],{},"The student's core id: the ",[408,568,519],{}," that registration returned. A 24-character hexadecimal id.",[389,571,572,577,579,582],{},[404,573,574],{},[408,575,576],{},"redirect",[404,578,560],{},[404,580,581],{},"no",[404,583,584,585,589],{},"Where the student lands after signing in. It must be a path on the organization's own site; see ",[369,586,588],{"href":587},"#the-redirect-rule","The redirect rule",". Leave it out to land on the organization's home page.",[357,591,592,593,596,597,373],{},"Any other field is refused with ",[408,594,595],{},"400 bad_request"," (",[408,598,599],{},"property \u003Cname> should not exist",[601,602,607],"pre",{"className":603,"code":604,"language":605,"meta":606,"style":606},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast","curl -s -X POST \"https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F\u003CorganizationId>\u002Fauth\u002Fsignin\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"studentId\": \"652f1c9b8e4b2a0012a3c4d5\", \"redirect\": \"\u002Fdashboard\" }'\n","bash","",[408,608,609,636,654,664],{"__ignoreMap":606},[610,611,614,618,622,625,629,632],"span",{"class":612,"line":613},"line",1,[610,615,617],{"class":616},"soyes","curl",[610,619,621],{"class":620},"s-5SL"," -s",[610,623,624],{"class":620}," -X",[610,626,628],{"class":627},"sT6z2"," POST",[610,630,631],{"class":627}," \"https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F\u003CorganizationId>\u002Fauth\u002Fsignin\"",[610,633,635],{"class":634},"sHUrx"," \\\n",[610,637,639,642,645,649,652],{"class":612,"line":638},2,[610,640,641],{"class":620},"  -H",[610,643,644],{"class":627}," \"Authorization: Bearer ",[610,646,648],{"class":647},"suds8","$TOKEN",[610,650,651],{"class":627},"\"",[610,653,635],{"class":634},[610,655,657,659,662],{"class":612,"line":656},3,[610,658,641],{"class":620},[610,660,661],{"class":627}," \"Content-Type: application\u002Fjson\"",[610,663,635],{"class":634},[610,665,667,670],{"class":612,"line":666},4,[610,668,669],{"class":620},"  -d",[610,671,672],{"class":627}," '{ \"studentId\": \"652f1c9b8e4b2a0012a3c4d5\", \"redirect\": \"\u002Fdashboard\" }'\n",[601,674,678],{"className":675,"code":676,"language":677,"meta":606,"style":606},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast","{\n  \"success\": true,\n  \"message\": \"Sign-in link generated successfully.\",\n  \"data\": {\n    \"url\": \"https:\u002F\u002Fauth.main-team.org\u002Fapi\u002Fuser\u002Foauth\u002Finvoke?accessToken=q7x2m9k4d1c8…&redirectUrl=%2Fdashboard\",\n    \"organization\": \"stem\",\n    \"studentId\": \"652f1c9b8e4b2a0012a3c4d5\",\n    \"expiresIn\": 120\n  }\n}\n","json",[408,679,680,685,700,712,720,733,746,759,770,776],{"__ignoreMap":606},[610,681,682],{"class":612,"line":613},[610,683,684],{"class":647},"{\n",[610,686,687,691,694,697],{"class":612,"line":638},[610,688,690],{"class":689},"sne4z","  \"success\"",[610,692,693],{"class":647},": ",[610,695,696],{"class":620},"true",[610,698,699],{"class":647},",\n",[610,701,702,705,707,710],{"class":612,"line":656},[610,703,704],{"class":689},"  \"message\"",[610,706,693],{"class":647},[610,708,709],{"class":627},"\"Sign-in link generated successfully.\"",[610,711,699],{"class":647},[610,713,714,717],{"class":612,"line":666},[610,715,716],{"class":689},"  \"data\"",[610,718,719],{"class":647},": {\n",[610,721,723,726,728,731],{"class":612,"line":722},5,[610,724,725],{"class":689},"    \"url\"",[610,727,693],{"class":647},[610,729,730],{"class":627},"\"https:\u002F\u002Fauth.main-team.org\u002Fapi\u002Fuser\u002Foauth\u002Finvoke?accessToken=q7x2m9k4d1c8…&redirectUrl=%2Fdashboard\"",[610,732,699],{"class":647},[610,734,736,739,741,744],{"class":612,"line":735},6,[610,737,738],{"class":689},"    \"organization\"",[610,740,693],{"class":647},[610,742,743],{"class":627},"\"stem\"",[610,745,699],{"class":647},[610,747,749,752,754,757],{"class":612,"line":748},7,[610,750,751],{"class":689},"    \"studentId\"",[610,753,693],{"class":647},[610,755,756],{"class":627},"\"652f1c9b8e4b2a0012a3c4d5\"",[610,758,699],{"class":647},[610,760,762,765,767],{"class":612,"line":761},8,[610,763,764],{"class":689},"    \"expiresIn\"",[610,766,693],{"class":647},[610,768,769],{"class":620},"120\n",[610,771,773],{"class":612,"line":772},9,[610,774,775],{"class":647},"  }\n",[610,777,779],{"class":612,"line":778},10,[610,780,781],{"class":647},"}\n",[357,783,784,785,788],{},"The token in ",[408,786,787],{},"url"," is 256 characters long; it is shortened here.",[528,790,792],{"id":791},"response-fields","Response fields",[383,794,795,804],{},[386,796,797],{},[389,798,799,801],{},[392,800,540],{},[392,802,803],{},"Meaning",[399,805,806,815,828,837],{},[389,807,808,812],{},[404,809,810],{},[408,811,787],{},[404,813,814],{},"The link. Send the student's browser to it. Treat it as opaque: don't parse it, store it or build one yourself, because its host and shape may change.",[389,816,817,822],{},[404,818,819],{},[408,820,821],{},"organization",[404,823,824,825,440],{},"The slug of the organization the link signs in to, for example ",[408,826,827],{},"stem",[389,829,830,834],{},[404,831,832],{},[408,833,451],{},[404,835,836],{},"The core id of the student, as you sent it.",[389,838,839,844],{},[404,840,841],{},[408,842,843],{},"expiresIn",[404,845,846,847,440],{},"Seconds the link stays valid from the moment it was issued. Always ",[408,848,849],{},"120",[357,851,852],{},"Your API account gets nothing else. No token, cookie or session is issued to you, only the URL.",[375,854,588],{"id":855},"the-redirect-rule",[357,857,858,860,861,864],{},[408,859,576],{}," must be a ",[364,862,863],{},"site-relative path",". The API refuses anything that could send the student to another site, so a link can never be turned into an open redirect.",[357,866,867,868,870],{},"A valid ",[408,869,576],{},":",[872,873,874,882,899],"ul",{},[875,876,877,878,881],"li",{},"starts with exactly one ",[408,879,880],{},"\u002F",";",[875,883,884,885,887,888,596,891,894,895,898],{},"does not continue with a second ",[408,886,880],{}," or a ",[408,889,890],{},"\\",[408,892,893],{},"\u002F\u002Fhost"," and ",[408,896,897],{},"\u002F\\host"," both point at another site in a browser);",[875,900,901,902,904],{},"contains no whitespace and no ",[408,903,890],{}," anywhere.",[383,906,907,918],{},[386,908,909],{},[389,910,911,915],{},[392,912,913],{},[408,914,576],{},[392,916,917],{},"Result",[399,919,920,930,938,947,957,967,979,989,999,1009],{},[389,921,922,927],{},[404,923,924],{},[408,925,926],{},"\u002Fdashboard",[404,928,929],{},"accepted",[389,931,932,936],{},[404,933,934],{},[408,935,880],{},[404,937,929],{},[389,939,940,945],{},[404,941,942],{},[408,943,944],{},"\u002Fexams?tab=upcoming",[404,946,929],{},[389,948,949,954],{},[404,950,951],{},[408,952,953],{},"\u002Fmy%20exams",[404,955,956],{},"accepted (percent-encode spaces)",[389,958,959,964],{},[404,960,961],{},[408,962,963],{},"\u002Fprofile\u002F{userId}",[404,965,966],{},"accepted; see below",[389,968,969,974],{},[404,970,971],{},[408,972,973],{},"dashboard",[404,975,976,977],{},"refused: no leading ",[408,978,880],{},[389,980,981,986],{},[404,982,983],{},[408,984,985],{},"https:\u002F\u002Fexample.com\u002F",[404,987,988],{},"refused: absolute URL",[389,990,991,996],{},[404,992,993],{},[408,994,995],{},"\u002F\u002Fexample.com",[404,997,998],{},"refused: protocol-relative",[389,1000,1001,1006],{},[404,1002,1003],{},[408,1004,1005],{},"\u002F\\example.com",[404,1007,1008],{},"refused: backslash",[389,1010,1011,1016],{},[404,1012,1013],{},[408,1014,1015],{},"\u002Fmy exams",[404,1017,1018],{},"refused: whitespace",[357,1020,1021,1022,1024,1025,1027,1028,440],{},"A refused ",[408,1023,576],{}," answers ",[408,1026,595],{}," with the message ",[408,1029,1030],{},"redirect must be a site-relative path starting with \"\u002F\" (e.g. \"\u002Fdashboard\")",[357,1032,1033,1034,1037,1038,1041,1042,1044,1045,1047,1048,1050],{},"The literal text ",[408,1035,1036],{},"{userId}"," in the path is replaced with the student's id ",[364,1039,1040],{},"inside that organization"," when they land. That id is not the core id you know them by (see ",[369,1043,36],{"href":37},"), so use ",[408,1046,1036],{}," whenever a panel path needs the student's own id. Only the first ",[408,1049,1036],{}," in the path is replaced, so use it once.",[375,1052,1054],{"id":1053},"what-is-checked-in-order","What is checked, in order",[357,1056,1057,1058,1061],{},"Every request goes through the same steps, and the first one that fails decides the answer. Branch on the status and ",[408,1059,1060],{},"error.code",", never on the message text.",[383,1063,1064,1077],{},[386,1065,1066],{},[389,1067,1068,1071,1074],{},[392,1069,1070],{},"#",[392,1072,1073],{},"Check",[392,1075,1076],{},"Refusal",[399,1078,1079,1097,1110,1129,1148,1166,1188,1203,1219],{},[389,1080,1081,1084,1087],{},[404,1082,1083],{},"1",[404,1085,1086],{},"The body is at most 100 kB of UTF-8 JSON",[404,1088,1089,1092,1093,1096],{},[408,1090,1091],{},"413 payload_too_large"," or ",[408,1094,1095],{},"415 unsupported_media_type",", before anything else is read",[389,1098,1099,1102,1105],{},[404,1100,1101],{},"2",[404,1103,1104],{},"Your token is valid",[404,1106,1107],{},[408,1108,1109],{},"401 unauthorized",[389,1111,1112,1115,1120],{},[404,1113,1114],{},"3",[404,1116,1117,1119],{},[408,1118,515],{}," names an organization",[404,1121,1122,1125,1126],{},[408,1123,1124],{},"404 not_found",", ",[408,1127,1128],{},"Organization not found!",[389,1130,1131,1134,1140],{},[404,1132,1133],{},"4",[404,1135,1136,1137,1139],{},"Your account holds ",[408,1138,410],{}," on that organization",[404,1141,1142,1125,1145],{},[408,1143,1144],{},"403 forbidden",[408,1146,1147],{},"Insufficient role permissions",[389,1149,1150,1153,1156],{},[404,1151,1152],{},"5",[404,1154,1155],{},"You are within the rate limit for this operation",[404,1157,1158,1161,1162,1165],{},[408,1159,1160],{},"429 too_many_requests","; wait ",[408,1163,1164],{},"Retry-After"," seconds",[389,1167,1168,1171,1180],{},[404,1169,1170],{},"6",[404,1172,1173,1174,1176,1177,1179],{},"The body is valid: ",[408,1175,451],{}," is a 24-character hex id, ",[408,1178,576],{}," follows the rule, and there are no other fields",[404,1181,1182,1184,1185],{},[408,1183,595],{},", for example ",[408,1186,1187],{},"studentId must be a mongodb id",[389,1189,1190,1193,1196],{},[404,1191,1192],{},"7",[404,1194,1195],{},"The student exists, is a student, and belongs to your account",[404,1197,1198,1125,1200],{},[408,1199,1124],{},[408,1201,1202],{},"Student not found!",[389,1204,1205,1208,1211],{},[404,1206,1207],{},"8",[404,1209,1210],{},"The student has access to this organization",[404,1212,1213,1125,1215,1218],{},[408,1214,1144],{},[408,1216,1217],{},"Student is not activated for organization stem."," (the slug varies)",[389,1220,1221,1224,1227],{},[404,1222,1223],{},"9",[404,1225,1226],{},"The link is issued",[404,1228,1229,1125,1232,1235],{},[408,1230,1231],{},"500 internal_error",[408,1233,1234],{},"Could not issue a sign-in token, please retry."," This is rare and safe to retry.",[357,1237,1238,1239,1242],{},"A request refused at step 8 changes nothing. The permission refusal at step 4 and the access refusal at step 8 are both ",[408,1240,1241],{},"403",", but they have different fixes: the first needs your operator, the second needs you (see the next section). Tell them apart by the message.",[375,1244,466],{"id":1245},"granting-access-to-an-organization",[357,1247,1248,1249,1252,1253,1256,1257,1024,1260,1262,1263,440],{},"Each student has a list of organizations they can use this season. At registration it defaults to ",[408,1250,1251],{},"[\"common\"]",", which means every organization. If you registered a student with a narrower list, for example ",[408,1254,1255],{},"[\"stem\"]",", a link to ",[408,1258,1259],{},"neo",[408,1261,1144],{}," until you add ",[408,1264,1259],{},[357,1266,1267,1268,1273,1274,1277,1278,1281,1282,1284,1285,1288,1289,1292],{},"A successful update through ",[369,1269,1270],{"href":202},[408,1271,1272],{},"PUT \u002Fv1\u002F\u003CorganizationId>\u002Fstudent\u002F\u003CstudentId>"," that leaves ",[408,1275,1276],{},"activatedPlatformsThisSeason"," out of the body adds that organization to the student's list. It adds nothing when the list already holds ",[408,1279,1280],{},"common",". An update that does send ",[408,1283,1276],{}," adds the values in it instead, and adds the organization in the path only if you name it. No update ever removes an entry. The update works even for a student who doesn't have access to that organization yet, which is how you grant it. The body can be empty: ",[408,1286,1287],{},"{}"," grants access and changes nothing else. The ",[369,1290,1291],{"href":63},"Students guide"," covers the update in full.",[375,1294,1296],{"id":1295},"send-the-student-to-the-link","Send the student to the link",[357,1298,1299],{},"The link works once, for 120 seconds. Request it when the student asks to go to the panel, and redirect their browser to it in the same response. Never generate links in advance.",[528,1301,1303],{"id":1302},"nodejs-express","Node.js (Express)",[601,1305,1309],{"className":1306,"code":1307,"language":1308,"meta":606,"style":606},"language-js shiki shiki-themes github-light-high-contrast github-dark-high-contrast","import express from 'express';\n\nconst app = express();\nconst API = 'https:\u002F\u002Fapi.main-team.org\u002Fv1';\n\n\u002F\u002F Behind your own login. POST, so link previewers and prefetchers never trigger it.\napp.post('\u002Fgo-to-panel', requireLogin, async (req, res) => {\n  \u002F\u002F Look the student up from your own session, never from the request body.\n  const { studentId, organizationId } = await studentForUser(req.user);\n\n  const apiRes = await fetch(`${API}\u002F${organizationId}\u002Fauth\u002Fsignin`, {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${await getToken()}`, \u002F\u002F see \u002Fapi\u002Ftutorials\u002Ftoken-handling\n      'Content-Type': 'application\u002Fjson',\n    },\n    body: JSON.stringify({ studentId, redirect: '\u002Fdashboard' }),\n  });\n  const body = await apiRes.json();\n\n  if (!apiRes.ok) {\n    \u002F\u002F Log the code and request_id, never the body of a successful response.\n    console.warn('sign-in link refused', apiRes.status, body.error?.code, body.error?.request_id);\n    return res.status(502).send('We could not open the panel. Please try again.');\n  }\n\n  res.set('Cache-Control', 'no-store');\n  res.set('Referrer-Policy', 'no-referrer');\n  res.redirect(302, body.data.url);\n});\n","js",[408,1310,1311,1328,1334,1352,1366,1370,1376,1415,1420,1450,1454,1488,1499,1505,1531,1544,1550,1573,1579,1598,1603,1617,1623,1640,1670,1675,1680,1701,1720,1735],{"__ignoreMap":606},[610,1312,1313,1316,1319,1322,1325],{"class":612,"line":613},[610,1314,1315],{"class":634},"import",[610,1317,1318],{"class":647}," express ",[610,1320,1321],{"class":634},"from",[610,1323,1324],{"class":627}," 'express'",[610,1326,1327],{"class":647},";\n",[610,1329,1330],{"class":612,"line":638},[610,1331,1333],{"emptyLinePlaceholder":1332},true,"\n",[610,1335,1336,1339,1342,1345,1349],{"class":612,"line":656},[610,1337,1338],{"class":634},"const",[610,1340,1341],{"class":620}," app",[610,1343,1344],{"class":634}," =",[610,1346,1348],{"class":1347},"sKwhi"," express",[610,1350,1351],{"class":647},"();\n",[610,1353,1354,1356,1359,1361,1364],{"class":612,"line":666},[610,1355,1338],{"class":634},[610,1357,1358],{"class":620}," API",[610,1360,1344],{"class":634},[610,1362,1363],{"class":627}," 'https:\u002F\u002Fapi.main-team.org\u002Fv1'",[610,1365,1327],{"class":647},[610,1367,1368],{"class":612,"line":722},[610,1369,1333],{"emptyLinePlaceholder":1332},[610,1371,1372],{"class":612,"line":735},[610,1373,1375],{"class":1374},"sLBg1","\u002F\u002F Behind your own login. POST, so link previewers and prefetchers never trigger it.\n",[610,1377,1378,1381,1384,1387,1390,1393,1396,1398,1401,1403,1406,1409,1412],{"class":612,"line":748},[610,1379,1380],{"class":647},"app.",[610,1382,1383],{"class":1347},"post",[610,1385,1386],{"class":647},"(",[610,1388,1389],{"class":627},"'\u002Fgo-to-panel'",[610,1391,1392],{"class":647},", requireLogin, ",[610,1394,1395],{"class":634},"async",[610,1397,596],{"class":647},[610,1399,1400],{"class":616},"req",[610,1402,1125],{"class":647},[610,1404,1405],{"class":616},"res",[610,1407,1408],{"class":647},") ",[610,1410,1411],{"class":634},"=>",[610,1413,1414],{"class":647}," {\n",[610,1416,1417],{"class":612,"line":761},[610,1418,1419],{"class":1374},"  \u002F\u002F Look the student up from your own session, never from the request body.\n",[610,1421,1422,1425,1428,1430,1432,1435,1438,1441,1444,1447],{"class":612,"line":772},[610,1423,1424],{"class":634},"  const",[610,1426,1427],{"class":647}," { ",[610,1429,451],{"class":620},[610,1431,1125],{"class":647},[610,1433,1434],{"class":620},"organizationId",[610,1436,1437],{"class":647}," } ",[610,1439,1440],{"class":634},"=",[610,1442,1443],{"class":634}," await",[610,1445,1446],{"class":1347}," studentForUser",[610,1448,1449],{"class":647},"(req.user);\n",[610,1451,1452],{"class":612,"line":778},[610,1453,1333],{"emptyLinePlaceholder":1332},[610,1455,1457,1459,1462,1464,1466,1469,1471,1474,1477,1480,1482,1485],{"class":612,"line":1456},11,[610,1458,1424],{"class":634},[610,1460,1461],{"class":620}," apiRes",[610,1463,1344],{"class":634},[610,1465,1443],{"class":634},[610,1467,1468],{"class":1347}," fetch",[610,1470,1386],{"class":647},[610,1472,1473],{"class":627},"`${",[610,1475,1476],{"class":620},"API",[610,1478,1479],{"class":627},"}\u002F${",[610,1481,1434],{"class":647},[610,1483,1484],{"class":627},"}\u002Fauth\u002Fsignin`",[610,1486,1487],{"class":647},", {\n",[610,1489,1491,1494,1497],{"class":612,"line":1490},12,[610,1492,1493],{"class":647},"    method: ",[610,1495,1496],{"class":627},"'POST'",[610,1498,699],{"class":647},[610,1500,1502],{"class":612,"line":1501},13,[610,1503,1504],{"class":647},"    headers: {\n",[610,1506,1508,1511,1514,1517,1520,1523,1526,1528],{"class":612,"line":1507},14,[610,1509,1510],{"class":647},"      Authorization: ",[610,1512,1513],{"class":627},"`Bearer ${",[610,1515,1516],{"class":634},"await",[610,1518,1519],{"class":1347}," getToken",[610,1521,1522],{"class":627},"()",[610,1524,1525],{"class":627},"}`",[610,1527,1125],{"class":647},[610,1529,1530],{"class":1374},"\u002F\u002F see \u002Fapi\u002Ftutorials\u002Ftoken-handling\n",[610,1532,1534,1537,1539,1542],{"class":612,"line":1533},15,[610,1535,1536],{"class":627},"      'Content-Type'",[610,1538,693],{"class":647},[610,1540,1541],{"class":627},"'application\u002Fjson'",[610,1543,699],{"class":647},[610,1545,1547],{"class":612,"line":1546},16,[610,1548,1549],{"class":647},"    },\n",[610,1551,1553,1556,1559,1561,1564,1567,1570],{"class":612,"line":1552},17,[610,1554,1555],{"class":647},"    body: ",[610,1557,1558],{"class":620},"JSON",[610,1560,440],{"class":647},[610,1562,1563],{"class":1347},"stringify",[610,1565,1566],{"class":647},"({ studentId, redirect: ",[610,1568,1569],{"class":627},"'\u002Fdashboard'",[610,1571,1572],{"class":647}," }),\n",[610,1574,1576],{"class":612,"line":1575},18,[610,1577,1578],{"class":647},"  });\n",[610,1580,1582,1584,1587,1589,1591,1594,1596],{"class":612,"line":1581},19,[610,1583,1424],{"class":634},[610,1585,1586],{"class":620}," body",[610,1588,1344],{"class":634},[610,1590,1443],{"class":634},[610,1592,1593],{"class":647}," apiRes.",[610,1595,677],{"class":1347},[610,1597,1351],{"class":647},[610,1599,1601],{"class":612,"line":1600},20,[610,1602,1333],{"emptyLinePlaceholder":1332},[610,1604,1606,1609,1611,1614],{"class":612,"line":1605},21,[610,1607,1608],{"class":634},"  if",[610,1610,596],{"class":647},[610,1612,1613],{"class":634},"!",[610,1615,1616],{"class":647},"apiRes.ok) {\n",[610,1618,1620],{"class":612,"line":1619},22,[610,1621,1622],{"class":1374},"    \u002F\u002F Log the code and request_id, never the body of a successful response.\n",[610,1624,1626,1629,1632,1634,1637],{"class":612,"line":1625},23,[610,1627,1628],{"class":647},"    console.",[610,1630,1631],{"class":1347},"warn",[610,1633,1386],{"class":647},[610,1635,1636],{"class":627},"'sign-in link refused'",[610,1638,1639],{"class":647},", apiRes.status, body.error?.code, body.error?.request_id);\n",[610,1641,1643,1646,1649,1652,1654,1657,1659,1662,1664,1667],{"class":612,"line":1642},24,[610,1644,1645],{"class":634},"    return",[610,1647,1648],{"class":647}," res.",[610,1650,1651],{"class":1347},"status",[610,1653,1386],{"class":647},[610,1655,1656],{"class":620},"502",[610,1658,373],{"class":647},[610,1660,1661],{"class":1347},"send",[610,1663,1386],{"class":647},[610,1665,1666],{"class":627},"'We could not open the panel. Please try again.'",[610,1668,1669],{"class":647},");\n",[610,1671,1673],{"class":612,"line":1672},25,[610,1674,775],{"class":647},[610,1676,1678],{"class":612,"line":1677},26,[610,1679,1333],{"emptyLinePlaceholder":1332},[610,1681,1683,1686,1689,1691,1694,1696,1699],{"class":612,"line":1682},27,[610,1684,1685],{"class":647},"  res.",[610,1687,1688],{"class":1347},"set",[610,1690,1386],{"class":647},[610,1692,1693],{"class":627},"'Cache-Control'",[610,1695,1125],{"class":647},[610,1697,1698],{"class":627},"'no-store'",[610,1700,1669],{"class":647},[610,1702,1704,1706,1708,1710,1713,1715,1718],{"class":612,"line":1703},28,[610,1705,1685],{"class":647},[610,1707,1688],{"class":1347},[610,1709,1386],{"class":647},[610,1711,1712],{"class":627},"'Referrer-Policy'",[610,1714,1125],{"class":647},[610,1716,1717],{"class":627},"'no-referrer'",[610,1719,1669],{"class":647},[610,1721,1723,1725,1727,1729,1732],{"class":612,"line":1722},29,[610,1724,1685],{"class":647},[610,1726,576],{"class":1347},[610,1728,1386],{"class":647},[610,1730,1731],{"class":620},"302",[610,1733,1734],{"class":647},", body.data.url);\n",[610,1736,1738],{"class":612,"line":1737},30,[610,1739,1740],{"class":647},"});\n",[528,1742,311],{"id":1743},"php",[601,1745,1748],{"className":1746,"code":1747,"language":1743,"meta":606,"style":606},"language-php shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003C?php\n\u002F\u002F go-to-panel.php: reached by a POST form behind your own login.\n[$studentId, $organizationId] = studentForCurrentUser(); \u002F\u002F from your session\n\n$ch = curl_init(\"https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F{$organizationId}\u002Fauth\u002Fsignin\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getToken(), \u002F\u002F see \u002Fapi\u002Ftutorials\u002Ftoken-handling\n        'Content-Type: application\u002Fjson',\n    ],\n    CURLOPT_POSTFIELDS => json_encode(['studentId' => $studentId, 'redirect' => '\u002Fdashboard']),\n    CURLOPT_TIMEOUT => 30,\n]);\n$body = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n\nif ($status !== 200) {\n    error_log(\"sign-in link refused: {$status} {$body['error']['code']} {$body['error']['request_id']}\");\n    http_response_code(502);\n    exit('We could not open the panel. Please try again.');\n}\n\nheader('Cache-Control: no-store');\nheader('Referrer-Policy: no-referrer');\nheader('Location: ' . $body['data']['url'], true, 302);\nexit;\n",[408,1749,1750,1758,1763,1779,1783,1806,1814,1827,1838,1848,1863,1870,1875,1907,1919,1924,1946,1964,1972,1976,1993,2022,2033,2044,2048,2052,2064,2075,2109],{"__ignoreMap":606},[610,1751,1752,1755],{"class":612,"line":613},[610,1753,1754],{"class":634},"\u003C?",[610,1756,1757],{"class":620},"php\n",[610,1759,1760],{"class":612,"line":638},[610,1761,1762],{"class":1374},"\u002F\u002F go-to-panel.php: reached by a POST form behind your own login.\n",[610,1764,1765,1768,1770,1773,1776],{"class":612,"line":656},[610,1766,1767],{"class":647},"[$studentId, $organizationId] ",[610,1769,1440],{"class":634},[610,1771,1772],{"class":1347}," studentForCurrentUser",[610,1774,1775],{"class":647},"(); ",[610,1777,1778],{"class":1374},"\u002F\u002F from your session\n",[610,1780,1781],{"class":612,"line":666},[610,1782,1333],{"emptyLinePlaceholder":1332},[610,1784,1785,1788,1790,1793,1795,1798,1801,1804],{"class":612,"line":722},[610,1786,1787],{"class":647},"$ch ",[610,1789,1440],{"class":634},[610,1791,1792],{"class":620}," curl_init",[610,1794,1386],{"class":647},[610,1796,1797],{"class":627},"\"https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F{",[610,1799,1800],{"class":647},"$organizationId",[610,1802,1803],{"class":627},"}\u002Fauth\u002Fsignin\"",[610,1805,1669],{"class":647},[610,1807,1808,1811],{"class":612,"line":735},[610,1809,1810],{"class":620},"curl_setopt_array",[610,1812,1813],{"class":647},"($ch, [\n",[610,1815,1816,1819,1822,1825],{"class":612,"line":748},[610,1817,1818],{"class":620},"    CURLOPT_POST",[610,1820,1821],{"class":634}," =>",[610,1823,1824],{"class":620}," true",[610,1826,699],{"class":647},[610,1828,1829,1832,1834,1836],{"class":612,"line":761},[610,1830,1831],{"class":620},"    CURLOPT_RETURNTRANSFER",[610,1833,1821],{"class":634},[610,1835,1824],{"class":620},[610,1837,699],{"class":647},[610,1839,1840,1843,1845],{"class":612,"line":772},[610,1841,1842],{"class":620},"    CURLOPT_HTTPHEADER",[610,1844,1821],{"class":634},[610,1846,1847],{"class":647}," [\n",[610,1849,1850,1853,1856,1858,1861],{"class":612,"line":778},[610,1851,1852],{"class":627},"        'Authorization: Bearer '",[610,1854,1855],{"class":634}," .",[610,1857,1519],{"class":1347},[610,1859,1860],{"class":647},"(), ",[610,1862,1530],{"class":1374},[610,1864,1865,1868],{"class":612,"line":1456},[610,1866,1867],{"class":627},"        'Content-Type: application\u002Fjson'",[610,1869,699],{"class":647},[610,1871,1872],{"class":612,"line":1490},[610,1873,1874],{"class":647},"    ],\n",[610,1876,1877,1880,1882,1885,1888,1891,1893,1896,1899,1901,1904],{"class":612,"line":1501},[610,1878,1879],{"class":620},"    CURLOPT_POSTFIELDS",[610,1881,1821],{"class":634},[610,1883,1884],{"class":620}," json_encode",[610,1886,1887],{"class":647},"([",[610,1889,1890],{"class":627},"'studentId'",[610,1892,1821],{"class":634},[610,1894,1895],{"class":647}," $studentId, ",[610,1897,1898],{"class":627},"'redirect'",[610,1900,1821],{"class":634},[610,1902,1903],{"class":627}," '\u002Fdashboard'",[610,1905,1906],{"class":647},"]),\n",[610,1908,1909,1912,1914,1917],{"class":612,"line":1507},[610,1910,1911],{"class":620},"    CURLOPT_TIMEOUT",[610,1913,1821],{"class":634},[610,1915,1916],{"class":620}," 30",[610,1918,699],{"class":647},[610,1920,1921],{"class":612,"line":1533},[610,1922,1923],{"class":647},"]);\n",[610,1925,1926,1929,1931,1934,1936,1939,1942,1944],{"class":612,"line":1546},[610,1927,1928],{"class":647},"$body ",[610,1930,1440],{"class":634},[610,1932,1933],{"class":620}," json_decode",[610,1935,1386],{"class":647},[610,1937,1938],{"class":620},"curl_exec",[610,1940,1941],{"class":647},"($ch), ",[610,1943,696],{"class":620},[610,1945,1669],{"class":647},[610,1947,1948,1951,1953,1956,1959,1962],{"class":612,"line":1552},[610,1949,1950],{"class":647},"$status ",[610,1952,1440],{"class":634},[610,1954,1955],{"class":620}," curl_getinfo",[610,1957,1958],{"class":647},"($ch, ",[610,1960,1961],{"class":620},"CURLINFO_RESPONSE_CODE",[610,1963,1669],{"class":647},[610,1965,1966,1969],{"class":612,"line":1575},[610,1967,1968],{"class":620},"curl_close",[610,1970,1971],{"class":647},"($ch);\n",[610,1973,1974],{"class":612,"line":1581},[610,1975,1333],{"emptyLinePlaceholder":1332},[610,1977,1978,1981,1984,1987,1990],{"class":612,"line":1600},[610,1979,1980],{"class":634},"if",[610,1982,1983],{"class":647}," ($status ",[610,1985,1986],{"class":634},"!==",[610,1988,1989],{"class":620}," 200",[610,1991,1992],{"class":647},") {\n",[610,1994,1995,1998,2000,2003,2006,2009,2012,2015,2017,2020],{"class":612,"line":1605},[610,1996,1997],{"class":620},"    error_log",[610,1999,1386],{"class":647},[610,2001,2002],{"class":627},"\"sign-in link refused: {",[610,2004,2005],{"class":647},"$status",[610,2007,2008],{"class":627},"} {",[610,2010,2011],{"class":647},"$body",[610,2013,2014],{"class":627},"['error']['code']} {",[610,2016,2011],{"class":647},[610,2018,2019],{"class":627},"['error']['request_id']}\"",[610,2021,1669],{"class":647},[610,2023,2024,2027,2029,2031],{"class":612,"line":1619},[610,2025,2026],{"class":620},"    http_response_code",[610,2028,1386],{"class":647},[610,2030,1656],{"class":620},[610,2032,1669],{"class":647},[610,2034,2035,2038,2040,2042],{"class":612,"line":1625},[610,2036,2037],{"class":634},"    exit",[610,2039,1386],{"class":647},[610,2041,1666],{"class":627},[610,2043,1669],{"class":647},[610,2045,2046],{"class":612,"line":1642},[610,2047,781],{"class":647},[610,2049,2050],{"class":612,"line":1672},[610,2051,1333],{"emptyLinePlaceholder":1332},[610,2053,2054,2057,2059,2062],{"class":612,"line":1677},[610,2055,2056],{"class":620},"header",[610,2058,1386],{"class":647},[610,2060,2061],{"class":627},"'Cache-Control: no-store'",[610,2063,1669],{"class":647},[610,2065,2066,2068,2070,2073],{"class":612,"line":1682},[610,2067,2056],{"class":620},[610,2069,1386],{"class":647},[610,2071,2072],{"class":627},"'Referrer-Policy: no-referrer'",[610,2074,1669],{"class":647},[610,2076,2077,2079,2081,2084,2086,2089,2092,2095,2098,2101,2103,2105,2107],{"class":612,"line":1703},[610,2078,2056],{"class":620},[610,2080,1386],{"class":647},[610,2082,2083],{"class":627},"'Location: '",[610,2085,1855],{"class":634},[610,2087,2088],{"class":647}," $body[",[610,2090,2091],{"class":627},"'data'",[610,2093,2094],{"class":647},"][",[610,2096,2097],{"class":627},"'url'",[610,2099,2100],{"class":647},"], ",[610,2102,696],{"class":620},[610,2104,1125],{"class":647},[610,2106,1731],{"class":620},[610,2108,1669],{"class":647},[610,2110,2111,2114],{"class":612,"line":1722},[610,2112,2113],{"class":634},"exit",[610,2115,1327],{"class":647},[357,2117,448,2118,2120],{},[369,2119,103],{"href":104}," tutorial builds these into complete routes.",[375,2122,2124],{"id":2123},"what-the-student-experiences","What the student experiences",[2126,2127,2128,2134,2137,2143],"ol",{},[875,2129,2130,2131,440],{},"Their browser opens the link. It passes through the platform's sign-in host, ",[408,2132,2133],{},"auth.main-team.org",[875,2135,2136],{},"The platform signs them in to the organization and sends them on to that organization's panel.",[875,2138,2139,2140,2142],{},"They land on the ",[408,2141,576],{}," path, or on the organization's home page if you left it out.",[875,2144,2145,2146,440],{},"If their email address isn't confirmed yet, the panel asks them to confirm it, and keeps asking until they do. See ",[369,2147,482],{"href":481},[357,2149,2150,2151,2154],{},"If the link was already used, or more than 120 seconds have passed, the browser shows an error instead: ",[408,2152,2153],{},"Invalid or expired access token",". The fix is always a new link; there is no way to revive an old one.",[357,2156,2157,2158,2161],{},"Rarely, the organization can't complete the sign-in, and the browser shows an error that starts with ",[408,2159,2160],{},"Error signing in to organization",". That link is spent too. Request a new one and try again, and contact support with the time if it keeps happening.",[375,2163,2165],{"id":2164},"single-use-what-uses-up-a-link","Single-use: what uses up a link",[357,2167,2168],{},"The first request to the URL spends it, whoever makes it. Plenty of software requests URLs without a person clicking:",[872,2170,2171,2174,2177,2180],{},[875,2172,2173],{},"chat and email apps that build link previews;",[875,2175,2176],{},"email security scanners that open every link in a message;",[875,2178,2179],{},"browser prefetching and \"preload\" hints;",[875,2181,2182],{},"monitoring or logging tools that replay URLs.",[357,2184,2185,2186,2188],{},"Any of these can spend a link before the student gets to it, and the student then sees ",[408,2187,2153],{},". So:",[872,2190,2191,2194,2197,2200],{},[875,2192,2193],{},"Never send a link by email, SMS or chat. Send the browser to it directly, as in the examples above.",[875,2195,2196],{},"Never show it on a page for the student to click later.",[875,2198,2199],{},"Never write it to logs, analytics or error reports. Anyone who sees an unused link can sign in as the student.",[875,2201,2202],{},"Retrying the API call is fine. A new call returns a new link. Retrying the URL itself never helps.",[484,2204,2205],{"type":486},[357,2206,2207,2208,894,2211,2214,2215,2217],{},"Treat a link like a password that lasts two minutes. Anyone who holds it can sign in as the student. Send ",[408,2209,2210],{},"Referrer-Policy: no-referrer",[408,2212,2213],{},"Cache-Control: no-store"," on the response that redirects to it, and keep it out of your logs. The ",[369,2216,51],{"href":52}," page lists the rest.",[375,2219,372],{"id":2220},"why-the-first-sign-in-matters",[357,2222,2223,2224,2227],{},"Every organization keeps its own copy of each student, linked to the core record by ",[408,2225,2226],{},"mainId",". You can't create that copy through the API. The organization creates it the first time the student signs in to it, and following a sign-in link counts.",[357,2229,2230],{},"Until a student has signed in to an organization once:",[383,2232,2233,2243],{},[386,2234,2235],{},[389,2236,2237,2240],{},[392,2238,2239],{},"Operation on that organization",[392,2241,2242],{},"Answer",[399,2244,2245,2260,2278,2293],{},[389,2246,2247,2252],{},[404,2248,2249],{},[369,2250,2251],{"href":238},"Create an application",[404,2253,2254,693,2257],{},[408,2255,2256],{},"409 conflict",[408,2258,2259],{},"Student has never signed in to stem, so stem holds no record for them. Generate a sign-in link first with POST \u002F:organizationId\u002Fauth\u002Fsignin.",[389,2261,2262,2273],{},[404,2263,2264,1125,2267,1092,2270],{},[369,2265,2266],{"href":244},"List the student's applications",[369,2268,2269],{"href":264},"certificates",[369,2271,2272],{"href":270},"reports",[404,2274,2275,2276],{},"the same ",[408,2277,2256],{},[389,2279,2280,2285],{},[404,2281,2282],{},[369,2283,2284],{"href":205},"Link a supervisor",[404,2286,2287,1125,2289,2292],{},[408,2288,1124],{},[408,2290,2291],{},"Not found!"," (this route gives one answer to every refusal)",[389,2294,2295,2300],{},[404,2296,2297],{},[369,2298,2299],{"href":226},"List the exams the student can apply to",[404,2301,2302],{},"works; nothing needs to exist for this read",[357,2304,2305,2306,2309],{},"So the usual order for a new student is: register, send a sign-in link, and only then create applications. The ",[369,2307,2308],{"href":101},"Register a student and apply"," tutorial walks through it.",[375,2311,482],{"id":2312},"email-confirmation",[357,2314,2315],{},"The link doesn't depend on the student's email address at all. It comes back to you in the API response and is never mailed anywhere, so an address nobody has proved receives nothing.",[872,2317,2318,2325],{},[875,2319,2320,2321,2324],{},"A student registered through the API starts out with ",[408,2322,2323],{},"emailConfirmed: false"," and can use links straight away.",[875,2326,2327],{},"If you change a student's email address, confirmation is withdrawn until the student proves the new address. Links still work.",[357,2329,2330],{},"When a student whose address isn't confirmed lands in the panel, the panel asks them to confirm it:",[2126,2332,2333,2336,2343,2346],{},[875,2334,2335],{},"The panel shows the address, partly masked, and offers to send a code to it.",[875,2337,2338,2339,2342],{},"The student receives an email with a 6-digit code. It comes from ",[408,2340,2341],{},"no-reply@main-team.org",", so tell students to check their spam folder if it doesn't arrive.",[875,2344,2345],{},"They type the code into the panel. A code is valid for 15 minutes, and they can ask for a new one after 60 seconds.",[875,2347,2348,2349,2352,2353,2355],{},"Once the code is accepted, the address is confirmed on the student's core record, and ",[408,2350,2351],{},"emailConfirmed"," on the student reads ",[408,2354,696],{}," in the API from then on.",[357,2357,2358,2359,2363],{},"The prompt stays until the address is confirmed. It appears on every page of the panel, and the student can't dismiss it or put it off; the only way out is to sign out. It covers My Exams too, the page where a student starts an exam, so a student must confirm before they can start one. A student already inside an exam room is not interrupted. Have your students confirm well before an exam day, for example right after their first sign-in, so an exam never waits on an email. The ",[369,2360,2362],{"href":2361},"\u002Fapi\u002Fenvironments#sandbox","sandbox"," sends no emails, so its panels don't ask.",[357,2365,2366,2367,2369,2370,2375],{},"Only the confirmation on the student's core record counts. The panel goes by the ",[408,2368,2351],{}," that ",[369,2371,2372],{"href":186},[408,2373,2374],{},"GET \u002Fv1\u002Fstudent\u002F{studentId}"," returns. Make sure the address you register is one the student reads: a student can't receive a code at an address that isn't theirs. Tell your students, before their first sign-in, that they will need to reach their mailbox.",[357,2377,2378,2379,2381],{},"Your integration doesn't take part in this. You can't send the code or confirm an address through the API. Read ",[408,2380,2351],{}," on the student record to see where a student stands.",[357,2383,2384,2385,440],{},"Confirmation matters for passwords: once a student has confirmed their address, you can no longer set their password, and a link is the way in. See ",[369,2386,68],{"href":69},[375,2388,2390],{"id":2389},"limits","Limits",[383,2392,2393,2403],{},[386,2394,2395],{},[389,2396,2397,2400],{},[392,2398,2399],{},"Limit",[392,2401,2402],{},"Value",[399,2404,2405,2413,2420,2427],{},[389,2406,2407,2410],{},[404,2408,2409],{},"Link lifetime",[404,2411,2412],{},"120 seconds from issue",[389,2414,2415,2418],{},[404,2416,2417],{},"Uses per link",[404,2419,1083],{},[389,2421,2422,2424],{},[404,2423,531],{},[404,2425,2426],{},"100 kB",[389,2428,2429,2432],{},[404,2430,2431],{},"Requests to this operation",[404,2433,2434,2435],{},"100 per 60 seconds per API account; see ",[369,2436,45],{"href":46},[375,2438,2440],{"id":2439},"related","Related",[872,2442,2443,2451,2455,2460,2465],{},[875,2444,2445,2450],{},[369,2446,2447],{"href":211},[408,2448,2449],{},"POST \u002Fv1\u002F{organizationId}\u002Fauth\u002Fsignin",": the full reference",[875,2452,2453],{},[369,2454,103],{"href":104},[875,2456,2457,2459],{},[369,2458,68],{"href":69},", the alternative to links",[875,2461,2462,2464],{},[369,2463,342],{"href":343},": \"Invalid or expired access token\"",[875,2466,2467,2468,1125,2474,1125,2480],{},"Error codes: ",[369,2469,2471],{"href":2470},"\u002Fapi\u002Ferrors#forbidden",[408,2472,2473],{},"forbidden",[369,2475,2477],{"href":2476},"\u002Fapi\u002Ferrors#not_found",[408,2478,2479],{},"not_found",[369,2481,2483],{"href":2482},"\u002Fapi\u002Ferrors#bad_request",[408,2484,2485],{},"bad_request",[2487,2488,2489],"style",{},"html pre.shiki code .soyes, html code.shiki .soyes{--shiki-default:#702C00;--shiki-dark:#FFB757}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}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 .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 .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .sKwhi, html code.shiki .sKwhi{--shiki-default:#622CBC;--shiki-dark:#DBB7FF}html pre.shiki code .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}",{"title":606,"searchDepth":638,"depth":656,"links":2491},[2492,2493,2497,2498,2499,2500,2504,2505,2506,2507,2508,2509],{"id":377,"depth":638,"text":378},{"id":504,"depth":638,"text":505,"children":2494},[2495,2496],{"id":530,"depth":656,"text":531},{"id":791,"depth":656,"text":792},{"id":855,"depth":638,"text":588},{"id":1053,"depth":638,"text":1054},{"id":1245,"depth":638,"text":466},{"id":1295,"depth":638,"text":1296,"children":2501},[2502,2503],{"id":1302,"depth":656,"text":1303},{"id":1743,"depth":656,"text":311},{"id":2123,"depth":638,"text":2124},{"id":2164,"depth":638,"text":2165},{"id":2220,"depth":638,"text":372},{"id":2312,"depth":638,"text":482},{"id":2389,"depth":638,"text":2390},{"id":2439,"depth":638,"text":2440},"Send one of your students straight into an organization's panel with a single-use link that expires 120 seconds after you request it.","md",{},[2514],"createSigninLink","60",{"title":80,"description":2510},"api\u002Fguides\u002Fsign-in-links","xK8JeOVjQYbYrDmPtaZrehe36GaMIXBzbrxCCRDSLqw",[2520],{"operationId":2514,"slug":2521,"method":141,"path":2522,"tag":80,"summary":210,"deprecated":134,"public":134,"permission":2523,"scope":821,"order":1619},"create-signin-link","\u002Fv1\u002F{organizationId}\u002Fauth\u002Fsignin","auth\u002Fsignin:$org:$ID",1791554615153]