[{"data":1,"prerenderedAt":5734},["ShallowReactive",2],{"api-nav":3,"api-guide:\u002Fapi\u002Ftutorials\u002Ftoken-handling":351,"api-spec:guide:\u002Fapi\u002Ftutorials\u002Ftoken-handling":5720},[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":5712,"extension":5713,"meta":5714,"navTitle":112,"navigation":715,"operations":5715,"order":5716,"path":113,"section":96,"seo":5717,"status":14,"stem":5718,"__hash__":5719},"apiGuides\u002Fapi\u002Ftutorials\u002Ftoken-handling.md","Handle tokens in production",{"type":355,"value":356,"toc":5697},"minimark",[357,366,371,514,531,535,538,594,605,620,624,662,666,2275,2278,2701,2705,2708,2722,2725,4466,4471,4898,4902,4911,4964,4975,4979,4989,5000,5041,5100,5150,5161,5165,5171,5203,5213,5217,5245,5248,5252,5259,5390,5398,5402,5408,5434,5579,5588,5592,5659,5663,5693],[358,359,360,361,365],"p",{},"There is no token endpoint on this API. Your server signs its own tokens with your ",[362,363,364],"code",{},"apiSecret",", which\nmeans the correctness and safety of every token is up to your code. In this tutorial you build a token\nmanager that signs correct tokens, reuses each one until shortly before it expires, corrects for a\ndrifting clock, and revokes a token when you are done with it. You also plan for several servers and\nfor the day a secret leaks.",[367,368,370],"h2",{"id":369},"the-rules-your-token-must-meet","The rules your token must meet",[372,373,374,387],"table",{},[375,376,377],"thead",{},[378,379,380,384],"tr",{},[381,382,383],"th",{},"Part",[381,385,386],{},"Rule",[388,389,390,402,421,436,451,463,478,490,504],"tbody",{},[378,391,392,396],{},[393,394,395],"td",{},"Algorithm",[393,397,398,401],{},[362,399,400],{},"HS256"," (HMAC with SHA-256), and nothing else",[378,403,404,410],{},[393,405,406,407],{},"Header ",[362,408,409],{},"kid",[393,411,412,413,416,417,420],{},"Your ",[362,414,415],{},"apiKey",", exactly as issued: ",[362,418,419],{},"key_"," followed by 24 characters",[378,422,423,429],{},[393,424,425,426],{},"Payload ",[362,427,428],{},"sub",[393,430,412,431,433,434],{},[362,432,415],{}," again. It must equal ",[362,435,409],{},[378,437,438,443],{},[393,439,425,440],{},[362,441,442],{},"iat",[393,444,445,446,450],{},"Issued-at time, in ",[447,448,449],"strong",{},"seconds"," since the Unix epoch. Required",[378,452,453,458],{},[393,454,425,455],{},[362,456,457],{},"exp",[393,459,460,461],{},"Expiry time, in seconds. Required, and after ",[362,462,442],{},[378,464,465,468],{},[393,466,467],{},"Lifetime",[393,469,470,473,474,477],{},[362,471,472],{},"exp - iat"," at most ",[447,475,476],{},"3600"," seconds",[378,479,480,483],{},[393,481,482],{},"Clock tolerance",[393,484,485,487,488],{},[362,486,442],{}," may be up to 30 seconds ahead of the server's clock. A token is still accepted up to 30 seconds after ",[362,489,457],{},[378,491,492,495],{},[393,493,494],{},"Signing key",[393,496,412,497,499,500,503],{},[362,498,364],{},", the whole string as issued, ",[362,501,502],{},"secret_"," prefix included. Do not decode it",[378,505,506,509],{},[393,507,508],{},"Transport",[393,510,511],{},[362,512,513],{},"Authorization: Bearer \u003Ctoken>",[358,515,516,517,520,521,524,525,530],{},"Every failure answers the same ",[362,518,519],{},"401 unauthorized"," with the same message:\n",[362,522,523],{},"Authentication is required or the provided credentials are invalid."," The response never says which\nrule failed, so that a leaked key or token tells its finder nothing. This page's\n",[526,527,529],"a",{"href":528},"#when-you-get-a-401","401 checklist"," is how you find out.",[367,532,534],{"id":533},"a-test-vector","A test vector",[358,536,537],{},"Sign a token with these exact inputs and compare. If your code produces a different string, fix it\nbefore you call the API:",[372,539,540,550],{},[375,541,542],{},[378,543,544,547],{},[381,545,546],{},"Input",[381,548,549],{},"Value",[388,551,552,563,574,584],{},[378,553,554,558],{},[393,555,556],{},[362,557,415],{},[393,559,560],{},[362,561,562],{},"key_Q2xpZW50RXhhbXBsZUtleTAx",[378,564,565,569],{},[393,566,567],{},[362,568,364],{},[393,570,571],{},[362,572,573],{},"secret_test-vector-01",[378,575,576,579],{},[393,577,578],{},"Header",[393,580,581],{},[362,582,583],{},"{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"key_Q2xpZW50RXhhbXBsZUtleTAx\"}",[378,585,586,589],{},[393,587,588],{},"Payload",[393,590,591],{},[362,592,593],{},"{\"sub\":\"key_Q2xpZW50RXhhbXBsZUtleTAx\",\"iat\":1767225600,\"exp\":1767226500}",[595,596,603],"pre",{"className":597,"code":599,"filename":600,"language":601,"meta":602},[598],"language-text","eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleV9RMnhwWlc1MFJYaGhiWEJzWlV0bGVUQXgifQ.eyJzdWIiOiJrZXlfUTJ4cFpXNTBSWGhoYlhCc1pVdGxlVEF4IiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjY1MDB9.LLLSuqwj4CNrhH1ffsGSvhRjTa7Gl86prmkyYTaymbk\n","Expected token","text","",[362,604,599],{"__ignoreMap":602},[358,606,607,608,611,612,615,616,619],{},"The Node.js and PHP code on this page, the shell recipe in\n",[526,609,100],{"href":610},"\u002Fapi\u002Ftutorials\u002Fregister-and-apply#a-token-for-curl",", and the ",[362,613,614],{},"jsonwebtoken"," npm\npackage with ",[362,617,618],{},"{ algorithm: 'HS256', keyid: apiKey }"," all produce this string. The key and secret above\nare examples only and work nowhere.",[367,621,623],{"id":622},"the-strategy-sign-locally-reuse-renew-early","The strategy: sign locally, reuse, renew early",[625,626,627,634,640,649],"ul",{},[628,629,630,633],"li",{},[447,631,632],{},"Choose a lifetime"," between a few minutes and the 3600-second maximum. This page uses 900 seconds\n(15 minutes). Shorter lifetimes limit the damage if a token leaks; longer ones sign less often.\nSigning is a single HMAC, so the cost is negligible either way.",[628,635,636,639],{},[447,637,638],{},"Reuse the token"," for every request until it is about to expire. Fewer live tokens make revoking\none meaningful, and they make your logs easier to read.",[628,641,642,648],{},[447,643,644,645,647],{},"Renew 60 seconds before ",[362,646,457],{},","," so a token never expires while a request is on its way.",[628,650,651,658,659,661],{},[447,652,653,654,657],{},"On a ",[362,655,656],{},"401",", throw the cached token away, sign a new one, and retry once."," If the retry also fails,\nstop and alert: a second ",[362,660,656],{}," is not a timing problem.",[367,663,665],{"id":664},"nodejs-a-token-manager","Node.js: a token manager",[595,667,672],{"className":668,"code":669,"filename":670,"language":671,"meta":602,"style":602},"language-js shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u002F\u002F token-manager.mjs: signed tokens for the Main Team API, reused and renewed early.\n\u002F\u002F Node.js 20 or newer, no dependencies.\nimport { createHmac } from 'node:crypto';\n\nexport const BASE_URL = 'https:\u002F\u002Fapi.main-team.org\u002Fv1';\nconst API_KEY_FORMAT = \u002F^key_[A-Za-z0-9_-]{24}$\u002F;\nconst MAX_LIFETIME = 3600;\n\nconst b64url = (value) => Buffer.from(JSON.stringify(value)).toString('base64url');\n\nexport class TokenManager {\n  #apiKey;\n  #apiSecret;\n  #lifetime;\n  #renewMargin;\n  #skew = 0; \u002F\u002F server clock minus local clock, in seconds\n  #current = null; \u002F\u002F { token, exp }\n\n  constructor({ apiKey, apiSecret, lifetime = 900, renewMargin = 60 }) {\n    if (!API_KEY_FORMAT.test(apiKey ?? '')) {\n      throw new Error('apiKey is not in the issued format (key_ and 24 characters). Are key and secret swapped?');\n    }\n    if (typeof apiSecret !== 'string' || !apiSecret.startsWith('secret_')) {\n      throw new Error('apiSecret is not in the issued format (it starts with secret_).');\n    }\n    if (!Number.isInteger(lifetime) || lifetime > MAX_LIFETIME || lifetime \u003C= renewMargin) {\n      throw new Error(`lifetime must be whole seconds, above ${renewMargin} and at most ${MAX_LIFETIME}.`);\n    }\n    this.#apiKey = apiKey;\n    this.#apiSecret = apiSecret;\n    this.#lifetime = lifetime;\n    this.#renewMargin = renewMargin;\n  }\n\n  \u002F** Corrects every future token for a clock that is off. See measureSkew(). *\u002F\n  setSkew(seconds) {\n    this.#skew = seconds;\n    this.#current = null;\n  }\n\n  \u002F** A valid token: the cached one, or a fresh one near expiry. *\u002F\n  get() {\n    const now = Math.floor(Date.now() \u002F 1000) + this.#skew;\n    if (this.#current && this.#current.exp - this.#renewMargin > now) return this.#current.token;\n    const exp = now + this.#lifetime;\n    const header = b64url({ alg: 'HS256', typ: 'JWT', kid: this.#apiKey });\n    const payload = b64url({ sub: this.#apiKey, iat: now, exp });\n    const signature = createHmac('sha256', this.#apiSecret)\n      .update(`${header}.${payload}`)\n      .digest('base64url');\n    this.#current = { token: `${header}.${payload}.${signature}`, exp };\n    return this.#current.token;\n  }\n\n  \u002F** The cached token, if any, without signing one. *\u002F\n  peek() {\n    return this.#current?.token ?? null;\n  }\n\n  \u002F** Forgets the cached token, after a 401 or after revoking it. *\u002F\n  invalidate() {\n    this.#current = null;\n  }\n}\n\n\u002F** Server clock minus local clock, in whole seconds, from the Date header of the health check. *\u002F\nexport async function measureSkew() {\n  const before = Date.now();\n  const res = await fetch(`${BASE_URL}\u002Fhealth`, { signal: AbortSignal.timeout(5000) });\n  const after = Date.now();\n  await res.text();\n  const server = Date.parse(res.headers.get('date') ?? '');\n  return Number.isNaN(server) ? 0 : Math.round((server - (before + after) \u002F 2) \u002F 1000);\n}\n\n\u002F** Sends a request with the managed token, retrying a 401 once with a fresh one. *\u002F\nexport async function authorizedFetch(tokens, path, init = {}) {\n  for (let attempt = 1; ; attempt++) {\n    const res = await fetch(`${BASE_URL}${path}`, {\n      ...init,\n      headers: { ...init.headers, Authorization: `Bearer ${tokens.get()}` },\n      signal: AbortSignal.timeout(30_000),\n    });\n    if (res.status !== 401 || attempt === 2) return res;\n    await res.text();\n    tokens.invalidate();\n  }\n}\n\n\u002F** Revokes a token. Returns how many seconds the revocation is held. *\u002F\nexport async function revokeToken(token) {\n  const res = await fetch(`${BASE_URL}\u002Fapi-account\u002Frevoke-token`, {\n    method: 'POST',\n    headers: { Authorization: `Bearer ${token}` },\n    signal: AbortSignal.timeout(10_000),\n  });\n  const body = await res.json().catch(() => null);\n  if (!res.ok) throw new Error(`revoke-token answered ${res.status} ${body?.error?.code ?? ''}`);\n  return body.data.expiresIn;\n}\n","token-manager.mjs","js",[362,673,674,683,689,710,717,738,768,783,788,843,848,862,870,878,886,894,911,927,932,971,1002,1021,1027,1065,1081,1086,1125,1152,1157,1172,1185,1198,1211,1217,1222,1228,1241,1254,1268,1273,1278,1284,1293,1335,1375,1395,1427,1447,1472,1501,1515,1545,1555,1560,1565,1571,1579,1595,1600,1605,1611,1619,1632,1637,1643,1648,1654,1670,1689,1729,1745,1758,1792,1846,1851,1856,1862,1894,1921,1950,1959,1988,2004,2010,2039,2051,2062,2067,2072,2077,2083,2102,2126,2138,2152,2167,2173,2205,2262,2270],{"__ignoreMap":602},[675,676,679],"span",{"class":677,"line":678},"line",1,[675,680,682],{"class":681},"sLBg1","\u002F\u002F token-manager.mjs: signed tokens for the Main Team API, reused and renewed early.\n",[675,684,686],{"class":677,"line":685},2,[675,687,688],{"class":681},"\u002F\u002F Node.js 20 or newer, no dependencies.\n",[675,690,692,696,700,703,707],{"class":677,"line":691},3,[675,693,695],{"class":694},"sHUrx","import",[675,697,699],{"class":698},"suds8"," { createHmac } ",[675,701,702],{"class":694},"from",[675,704,706],{"class":705},"sT6z2"," 'node:crypto'",[675,708,709],{"class":698},";\n",[675,711,713],{"class":677,"line":712},4,[675,714,716],{"emptyLinePlaceholder":715},true,"\n",[675,718,720,723,726,730,733,736],{"class":677,"line":719},5,[675,721,722],{"class":694},"export",[675,724,725],{"class":694}," const",[675,727,729],{"class":728},"s-5SL"," BASE_URL",[675,731,732],{"class":694}," =",[675,734,735],{"class":705}," 'https:\u002F\u002Fapi.main-team.org\u002Fv1'",[675,737,709],{"class":698},[675,739,741,744,747,749,752,755,757,760,763,766],{"class":677,"line":740},6,[675,742,743],{"class":694},"const",[675,745,746],{"class":728}," API_KEY_FORMAT",[675,748,732],{"class":694},[675,750,751],{"class":705}," \u002F",[675,753,754],{"class":694},"^",[675,756,419],{"class":705},[675,758,759],{"class":728},"[A-Za-z0-9_-]",[675,761,762],{"class":694},"{24}$",[675,764,765],{"class":705},"\u002F",[675,767,709],{"class":698},[675,769,771,773,776,778,781],{"class":677,"line":770},7,[675,772,743],{"class":694},[675,774,775],{"class":728}," MAX_LIFETIME",[675,777,732],{"class":694},[675,779,780],{"class":728}," 3600",[675,782,709],{"class":698},[675,784,786],{"class":677,"line":785},8,[675,787,716],{"emptyLinePlaceholder":715},[675,789,791,793,797,799,802,806,809,812,815,817,820,823,826,829,832,835,837,840],{"class":677,"line":790},9,[675,792,743],{"class":694},[675,794,796],{"class":795},"sKwhi"," b64url",[675,798,732],{"class":694},[675,800,801],{"class":698}," (",[675,803,805],{"class":804},"soyes","value",[675,807,808],{"class":698},") ",[675,810,811],{"class":694},"=>",[675,813,814],{"class":698}," Buffer.",[675,816,702],{"class":795},[675,818,819],{"class":698},"(",[675,821,822],{"class":728},"JSON",[675,824,825],{"class":698},".",[675,827,828],{"class":795},"stringify",[675,830,831],{"class":698},"(value)).",[675,833,834],{"class":795},"toString",[675,836,819],{"class":698},[675,838,839],{"class":705},"'base64url'",[675,841,842],{"class":698},");\n",[675,844,846],{"class":677,"line":845},10,[675,847,716],{"emptyLinePlaceholder":715},[675,849,851,853,856,859],{"class":677,"line":850},11,[675,852,722],{"class":694},[675,854,855],{"class":694}," class",[675,857,858],{"class":804}," TokenManager",[675,860,861],{"class":698}," {\n",[675,863,865,868],{"class":677,"line":864},12,[675,866,867],{"class":804},"  #apiKey",[675,869,709],{"class":698},[675,871,873,876],{"class":677,"line":872},13,[675,874,875],{"class":804},"  #apiSecret",[675,877,709],{"class":698},[675,879,881,884],{"class":677,"line":880},14,[675,882,883],{"class":804},"  #lifetime",[675,885,709],{"class":698},[675,887,889,892],{"class":677,"line":888},15,[675,890,891],{"class":804},"  #renewMargin",[675,893,709],{"class":698},[675,895,897,900,902,905,908],{"class":677,"line":896},16,[675,898,899],{"class":804},"  #skew",[675,901,732],{"class":694},[675,903,904],{"class":728}," 0",[675,906,907],{"class":698},"; ",[675,909,910],{"class":681},"\u002F\u002F server clock minus local clock, in seconds\n",[675,912,914,917,919,922,924],{"class":677,"line":913},17,[675,915,916],{"class":804},"  #current",[675,918,732],{"class":694},[675,920,921],{"class":728}," null",[675,923,907],{"class":698},[675,925,926],{"class":681},"\u002F\u002F { token, exp }\n",[675,928,930],{"class":677,"line":929},18,[675,931,716],{"emptyLinePlaceholder":715},[675,933,935,938,941,943,946,948,950,953,955,958,960,963,965,968],{"class":677,"line":934},19,[675,936,937],{"class":694},"  constructor",[675,939,940],{"class":698},"({ ",[675,942,415],{"class":804},[675,944,945],{"class":698},", ",[675,947,364],{"class":804},[675,949,945],{"class":698},[675,951,952],{"class":804},"lifetime",[675,954,732],{"class":694},[675,956,957],{"class":728}," 900",[675,959,945],{"class":698},[675,961,962],{"class":804},"renewMargin",[675,964,732],{"class":694},[675,966,967],{"class":728}," 60",[675,969,970],{"class":698}," }) {\n",[675,972,974,977,979,982,985,987,990,993,996,999],{"class":677,"line":973},20,[675,975,976],{"class":694},"    if",[675,978,801],{"class":698},[675,980,981],{"class":694},"!",[675,983,984],{"class":728},"API_KEY_FORMAT",[675,986,825],{"class":698},[675,988,989],{"class":795},"test",[675,991,992],{"class":698},"(apiKey ",[675,994,995],{"class":694},"??",[675,997,998],{"class":705}," ''",[675,1000,1001],{"class":698},")) {\n",[675,1003,1005,1008,1011,1014,1016,1019],{"class":677,"line":1004},21,[675,1006,1007],{"class":694},"      throw",[675,1009,1010],{"class":694}," new",[675,1012,1013],{"class":795}," Error",[675,1015,819],{"class":698},[675,1017,1018],{"class":705},"'apiKey is not in the issued format (key_ and 24 characters). Are key and secret swapped?'",[675,1020,842],{"class":698},[675,1022,1024],{"class":677,"line":1023},22,[675,1025,1026],{"class":698},"    }\n",[675,1028,1030,1032,1034,1037,1040,1043,1046,1049,1052,1055,1058,1060,1063],{"class":677,"line":1029},23,[675,1031,976],{"class":694},[675,1033,801],{"class":698},[675,1035,1036],{"class":694},"typeof",[675,1038,1039],{"class":698}," apiSecret ",[675,1041,1042],{"class":694},"!==",[675,1044,1045],{"class":705}," 'string'",[675,1047,1048],{"class":694}," ||",[675,1050,1051],{"class":694}," !",[675,1053,1054],{"class":698},"apiSecret.",[675,1056,1057],{"class":795},"startsWith",[675,1059,819],{"class":698},[675,1061,1062],{"class":705},"'secret_'",[675,1064,1001],{"class":698},[675,1066,1068,1070,1072,1074,1076,1079],{"class":677,"line":1067},24,[675,1069,1007],{"class":694},[675,1071,1010],{"class":694},[675,1073,1013],{"class":795},[675,1075,819],{"class":698},[675,1077,1078],{"class":705},"'apiSecret is not in the issued format (it starts with secret_).'",[675,1080,842],{"class":698},[675,1082,1084],{"class":677,"line":1083},25,[675,1085,1026],{"class":698},[675,1087,1089,1091,1093,1095,1098,1101,1104,1107,1110,1113,1115,1117,1119,1122],{"class":677,"line":1088},26,[675,1090,976],{"class":694},[675,1092,801],{"class":698},[675,1094,981],{"class":694},[675,1096,1097],{"class":698},"Number.",[675,1099,1100],{"class":795},"isInteger",[675,1102,1103],{"class":698},"(lifetime) ",[675,1105,1106],{"class":694},"||",[675,1108,1109],{"class":698}," lifetime ",[675,1111,1112],{"class":694},">",[675,1114,775],{"class":728},[675,1116,1048],{"class":694},[675,1118,1109],{"class":698},[675,1120,1121],{"class":694},"\u003C=",[675,1123,1124],{"class":698}," renewMargin) {\n",[675,1126,1128,1130,1132,1134,1136,1139,1141,1144,1147,1150],{"class":677,"line":1127},27,[675,1129,1007],{"class":694},[675,1131,1010],{"class":694},[675,1133,1013],{"class":795},[675,1135,819],{"class":698},[675,1137,1138],{"class":705},"`lifetime must be whole seconds, above ${",[675,1140,962],{"class":698},[675,1142,1143],{"class":705},"} and at most ${",[675,1145,1146],{"class":728},"MAX_LIFETIME",[675,1148,1149],{"class":705},"}.`",[675,1151,842],{"class":698},[675,1153,1155],{"class":677,"line":1154},28,[675,1156,1026],{"class":698},[675,1158,1160,1163,1166,1169],{"class":677,"line":1159},29,[675,1161,1162],{"class":728},"    this",[675,1164,1165],{"class":698},".#apiKey ",[675,1167,1168],{"class":694},"=",[675,1170,1171],{"class":698}," apiKey;\n",[675,1173,1175,1177,1180,1182],{"class":677,"line":1174},30,[675,1176,1162],{"class":728},[675,1178,1179],{"class":698},".#apiSecret ",[675,1181,1168],{"class":694},[675,1183,1184],{"class":698}," apiSecret;\n",[675,1186,1188,1190,1193,1195],{"class":677,"line":1187},31,[675,1189,1162],{"class":728},[675,1191,1192],{"class":698},".#lifetime ",[675,1194,1168],{"class":694},[675,1196,1197],{"class":698}," lifetime;\n",[675,1199,1201,1203,1206,1208],{"class":677,"line":1200},32,[675,1202,1162],{"class":728},[675,1204,1205],{"class":698},".#renewMargin ",[675,1207,1168],{"class":694},[675,1209,1210],{"class":698}," renewMargin;\n",[675,1212,1214],{"class":677,"line":1213},33,[675,1215,1216],{"class":698},"  }\n",[675,1218,1220],{"class":677,"line":1219},34,[675,1221,716],{"emptyLinePlaceholder":715},[675,1223,1225],{"class":677,"line":1224},35,[675,1226,1227],{"class":681},"  \u002F** Corrects every future token for a clock that is off. See measureSkew(). *\u002F\n",[675,1229,1231,1234,1236,1238],{"class":677,"line":1230},36,[675,1232,1233],{"class":795},"  setSkew",[675,1235,819],{"class":698},[675,1237,449],{"class":804},[675,1239,1240],{"class":698},") {\n",[675,1242,1244,1246,1249,1251],{"class":677,"line":1243},37,[675,1245,1162],{"class":728},[675,1247,1248],{"class":698},".#skew ",[675,1250,1168],{"class":694},[675,1252,1253],{"class":698}," seconds;\n",[675,1255,1257,1259,1262,1264,1266],{"class":677,"line":1256},38,[675,1258,1162],{"class":728},[675,1260,1261],{"class":698},".#current ",[675,1263,1168],{"class":694},[675,1265,921],{"class":728},[675,1267,709],{"class":698},[675,1269,1271],{"class":677,"line":1270},39,[675,1272,1216],{"class":698},[675,1274,1276],{"class":677,"line":1275},40,[675,1277,716],{"emptyLinePlaceholder":715},[675,1279,1281],{"class":677,"line":1280},41,[675,1282,1283],{"class":681},"  \u002F** A valid token: the cached one, or a fresh one near expiry. *\u002F\n",[675,1285,1287,1290],{"class":677,"line":1286},42,[675,1288,1289],{"class":795},"  get",[675,1291,1292],{"class":698},"() {\n",[675,1294,1296,1299,1302,1304,1307,1310,1313,1316,1319,1321,1324,1326,1329,1332],{"class":677,"line":1295},43,[675,1297,1298],{"class":694},"    const",[675,1300,1301],{"class":728}," now",[675,1303,732],{"class":694},[675,1305,1306],{"class":698}," Math.",[675,1308,1309],{"class":795},"floor",[675,1311,1312],{"class":698},"(Date.",[675,1314,1315],{"class":795},"now",[675,1317,1318],{"class":698},"() ",[675,1320,765],{"class":694},[675,1322,1323],{"class":728}," 1000",[675,1325,808],{"class":698},[675,1327,1328],{"class":694},"+",[675,1330,1331],{"class":728}," this",[675,1333,1334],{"class":698},".#skew;\n",[675,1336,1338,1340,1342,1345,1347,1350,1352,1355,1358,1360,1362,1364,1367,1370,1372],{"class":677,"line":1337},44,[675,1339,976],{"class":694},[675,1341,801],{"class":698},[675,1343,1344],{"class":728},"this",[675,1346,1261],{"class":698},[675,1348,1349],{"class":694},"&&",[675,1351,1331],{"class":728},[675,1353,1354],{"class":698},".#current.exp ",[675,1356,1357],{"class":694},"-",[675,1359,1331],{"class":728},[675,1361,1205],{"class":698},[675,1363,1112],{"class":694},[675,1365,1366],{"class":698}," now) ",[675,1368,1369],{"class":694},"return",[675,1371,1331],{"class":728},[675,1373,1374],{"class":698},".#current.token;\n",[675,1376,1378,1380,1383,1385,1388,1390,1392],{"class":677,"line":1377},45,[675,1379,1298],{"class":694},[675,1381,1382],{"class":728}," exp",[675,1384,732],{"class":694},[675,1386,1387],{"class":698}," now ",[675,1389,1328],{"class":694},[675,1391,1331],{"class":728},[675,1393,1394],{"class":698},".#lifetime;\n",[675,1396,1398,1400,1403,1405,1407,1410,1413,1416,1419,1422,1424],{"class":677,"line":1397},46,[675,1399,1298],{"class":694},[675,1401,1402],{"class":728}," header",[675,1404,732],{"class":694},[675,1406,796],{"class":795},[675,1408,1409],{"class":698},"({ alg: ",[675,1411,1412],{"class":705},"'HS256'",[675,1414,1415],{"class":698},", typ: ",[675,1417,1418],{"class":705},"'JWT'",[675,1420,1421],{"class":698},", kid: ",[675,1423,1344],{"class":728},[675,1425,1426],{"class":698},".#apiKey });\n",[675,1428,1430,1432,1435,1437,1439,1442,1444],{"class":677,"line":1429},47,[675,1431,1298],{"class":694},[675,1433,1434],{"class":728}," payload",[675,1436,732],{"class":694},[675,1438,796],{"class":795},[675,1440,1441],{"class":698},"({ sub: ",[675,1443,1344],{"class":728},[675,1445,1446],{"class":698},".#apiKey, iat: now, exp });\n",[675,1448,1450,1452,1455,1457,1460,1462,1465,1467,1469],{"class":677,"line":1449},48,[675,1451,1298],{"class":694},[675,1453,1454],{"class":728}," signature",[675,1456,732],{"class":694},[675,1458,1459],{"class":795}," createHmac",[675,1461,819],{"class":698},[675,1463,1464],{"class":705},"'sha256'",[675,1466,945],{"class":698},[675,1468,1344],{"class":728},[675,1470,1471],{"class":698},".#apiSecret)\n",[675,1473,1475,1478,1481,1483,1486,1489,1492,1495,1498],{"class":677,"line":1474},49,[675,1476,1477],{"class":698},"      .",[675,1479,1480],{"class":795},"update",[675,1482,819],{"class":698},[675,1484,1485],{"class":705},"`${",[675,1487,1488],{"class":698},"header",[675,1490,1491],{"class":705},"}.${",[675,1493,1494],{"class":698},"payload",[675,1496,1497],{"class":705},"}`",[675,1499,1500],{"class":698},")\n",[675,1502,1504,1506,1509,1511,1513],{"class":677,"line":1503},50,[675,1505,1477],{"class":698},[675,1507,1508],{"class":795},"digest",[675,1510,819],{"class":698},[675,1512,839],{"class":705},[675,1514,842],{"class":698},[675,1516,1518,1520,1522,1524,1527,1529,1531,1533,1535,1537,1540,1542],{"class":677,"line":1517},51,[675,1519,1162],{"class":728},[675,1521,1261],{"class":698},[675,1523,1168],{"class":694},[675,1525,1526],{"class":698}," { token: ",[675,1528,1485],{"class":705},[675,1530,1488],{"class":698},[675,1532,1491],{"class":705},[675,1534,1494],{"class":698},[675,1536,1491],{"class":705},[675,1538,1539],{"class":698},"signature",[675,1541,1497],{"class":705},[675,1543,1544],{"class":698},", exp };\n",[675,1546,1548,1551,1553],{"class":677,"line":1547},52,[675,1549,1550],{"class":694},"    return",[675,1552,1331],{"class":728},[675,1554,1374],{"class":698},[675,1556,1558],{"class":677,"line":1557},53,[675,1559,1216],{"class":698},[675,1561,1563],{"class":677,"line":1562},54,[675,1564,716],{"emptyLinePlaceholder":715},[675,1566,1568],{"class":677,"line":1567},55,[675,1569,1570],{"class":681},"  \u002F** The cached token, if any, without signing one. *\u002F\n",[675,1572,1574,1577],{"class":677,"line":1573},56,[675,1575,1576],{"class":795},"  peek",[675,1578,1292],{"class":698},[675,1580,1582,1584,1586,1589,1591,1593],{"class":677,"line":1581},57,[675,1583,1550],{"class":694},[675,1585,1331],{"class":728},[675,1587,1588],{"class":698},".#current?.token ",[675,1590,995],{"class":694},[675,1592,921],{"class":728},[675,1594,709],{"class":698},[675,1596,1598],{"class":677,"line":1597},58,[675,1599,1216],{"class":698},[675,1601,1603],{"class":677,"line":1602},59,[675,1604,716],{"emptyLinePlaceholder":715},[675,1606,1608],{"class":677,"line":1607},60,[675,1609,1610],{"class":681},"  \u002F** Forgets the cached token, after a 401 or after revoking it. *\u002F\n",[675,1612,1614,1617],{"class":677,"line":1613},61,[675,1615,1616],{"class":795},"  invalidate",[675,1618,1292],{"class":698},[675,1620,1622,1624,1626,1628,1630],{"class":677,"line":1621},62,[675,1623,1162],{"class":728},[675,1625,1261],{"class":698},[675,1627,1168],{"class":694},[675,1629,921],{"class":728},[675,1631,709],{"class":698},[675,1633,1635],{"class":677,"line":1634},63,[675,1636,1216],{"class":698},[675,1638,1640],{"class":677,"line":1639},64,[675,1641,1642],{"class":698},"}\n",[675,1644,1646],{"class":677,"line":1645},65,[675,1647,716],{"emptyLinePlaceholder":715},[675,1649,1651],{"class":677,"line":1650},66,[675,1652,1653],{"class":681},"\u002F** Server clock minus local clock, in whole seconds, from the Date header of the health check. *\u002F\n",[675,1655,1657,1659,1662,1665,1668],{"class":677,"line":1656},67,[675,1658,722],{"class":694},[675,1660,1661],{"class":694}," async",[675,1663,1664],{"class":694}," function",[675,1666,1667],{"class":795}," measureSkew",[675,1669,1292],{"class":698},[675,1671,1673,1676,1679,1681,1684,1686],{"class":677,"line":1672},68,[675,1674,1675],{"class":694},"  const",[675,1677,1678],{"class":728}," before",[675,1680,732],{"class":694},[675,1682,1683],{"class":698}," Date.",[675,1685,1315],{"class":795},[675,1687,1688],{"class":698},"();\n",[675,1690,1692,1694,1697,1699,1702,1705,1707,1709,1712,1715,1718,1721,1723,1726],{"class":677,"line":1691},69,[675,1693,1675],{"class":694},[675,1695,1696],{"class":728}," res",[675,1698,732],{"class":694},[675,1700,1701],{"class":694}," await",[675,1703,1704],{"class":795}," fetch",[675,1706,819],{"class":698},[675,1708,1485],{"class":705},[675,1710,1711],{"class":728},"BASE_URL",[675,1713,1714],{"class":705},"}\u002Fhealth`",[675,1716,1717],{"class":698},", { signal: AbortSignal.",[675,1719,1720],{"class":795},"timeout",[675,1722,819],{"class":698},[675,1724,1725],{"class":728},"5000",[675,1727,1728],{"class":698},") });\n",[675,1730,1732,1734,1737,1739,1741,1743],{"class":677,"line":1731},70,[675,1733,1675],{"class":694},[675,1735,1736],{"class":728}," after",[675,1738,732],{"class":694},[675,1740,1683],{"class":698},[675,1742,1315],{"class":795},[675,1744,1688],{"class":698},[675,1746,1748,1751,1754,1756],{"class":677,"line":1747},71,[675,1749,1750],{"class":694},"  await",[675,1752,1753],{"class":698}," res.",[675,1755,601],{"class":795},[675,1757,1688],{"class":698},[675,1759,1761,1763,1766,1768,1770,1773,1776,1779,1781,1784,1786,1788,1790],{"class":677,"line":1760},72,[675,1762,1675],{"class":694},[675,1764,1765],{"class":728}," server",[675,1767,732],{"class":694},[675,1769,1683],{"class":698},[675,1771,1772],{"class":795},"parse",[675,1774,1775],{"class":698},"(res.headers.",[675,1777,1778],{"class":795},"get",[675,1780,819],{"class":698},[675,1782,1783],{"class":705},"'date'",[675,1785,808],{"class":698},[675,1787,995],{"class":694},[675,1789,998],{"class":705},[675,1791,842],{"class":698},[675,1793,1795,1798,1801,1804,1807,1810,1812,1815,1817,1820,1823,1825,1828,1830,1833,1835,1838,1840,1842,1844],{"class":677,"line":1794},73,[675,1796,1797],{"class":694},"  return",[675,1799,1800],{"class":698}," Number.",[675,1802,1803],{"class":795},"isNaN",[675,1805,1806],{"class":698},"(server) ",[675,1808,1809],{"class":694},"?",[675,1811,904],{"class":728},[675,1813,1814],{"class":694}," :",[675,1816,1306],{"class":698},[675,1818,1819],{"class":795},"round",[675,1821,1822],{"class":698},"((server ",[675,1824,1357],{"class":694},[675,1826,1827],{"class":698}," (before ",[675,1829,1328],{"class":694},[675,1831,1832],{"class":698}," after) ",[675,1834,765],{"class":694},[675,1836,1837],{"class":728}," 2",[675,1839,808],{"class":698},[675,1841,765],{"class":694},[675,1843,1323],{"class":728},[675,1845,842],{"class":698},[675,1847,1849],{"class":677,"line":1848},74,[675,1850,1642],{"class":698},[675,1852,1854],{"class":677,"line":1853},75,[675,1855,716],{"emptyLinePlaceholder":715},[675,1857,1859],{"class":677,"line":1858},76,[675,1860,1861],{"class":681},"\u002F** Sends a request with the managed token, retrying a 401 once with a fresh one. *\u002F\n",[675,1863,1865,1867,1869,1871,1874,1876,1879,1881,1884,1886,1889,1891],{"class":677,"line":1864},77,[675,1866,722],{"class":694},[675,1868,1661],{"class":694},[675,1870,1664],{"class":694},[675,1872,1873],{"class":795}," authorizedFetch",[675,1875,819],{"class":698},[675,1877,1878],{"class":804},"tokens",[675,1880,945],{"class":698},[675,1882,1883],{"class":804},"path",[675,1885,945],{"class":698},[675,1887,1888],{"class":804},"init",[675,1890,732],{"class":694},[675,1892,1893],{"class":698}," {}) {\n",[675,1895,1897,1900,1902,1905,1908,1910,1913,1916,1919],{"class":677,"line":1896},78,[675,1898,1899],{"class":694},"  for",[675,1901,801],{"class":698},[675,1903,1904],{"class":694},"let",[675,1906,1907],{"class":698}," attempt ",[675,1909,1168],{"class":694},[675,1911,1912],{"class":728}," 1",[675,1914,1915],{"class":698},"; ; attempt",[675,1917,1918],{"class":694},"++",[675,1920,1240],{"class":698},[675,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1943,1945,1947],{"class":677,"line":1923},79,[675,1925,1298],{"class":694},[675,1927,1696],{"class":728},[675,1929,732],{"class":694},[675,1931,1701],{"class":694},[675,1933,1704],{"class":795},[675,1935,819],{"class":698},[675,1937,1485],{"class":705},[675,1939,1711],{"class":728},[675,1941,1942],{"class":705},"}${",[675,1944,1883],{"class":698},[675,1946,1497],{"class":705},[675,1948,1949],{"class":698},", {\n",[675,1951,1953,1956],{"class":677,"line":1952},80,[675,1954,1955],{"class":694},"      ...",[675,1957,1958],{"class":698},"init,\n",[675,1960,1962,1965,1968,1971,1974,1976,1978,1980,1983,1985],{"class":677,"line":1961},81,[675,1963,1964],{"class":698},"      headers: { ",[675,1966,1967],{"class":694},"...",[675,1969,1970],{"class":698},"init.headers, Authorization: ",[675,1972,1973],{"class":705},"`Bearer ${",[675,1975,1878],{"class":698},[675,1977,825],{"class":705},[675,1979,1778],{"class":795},[675,1981,1982],{"class":705},"()",[675,1984,1497],{"class":705},[675,1986,1987],{"class":698}," },\n",[675,1989,1991,1994,1996,1998,2001],{"class":677,"line":1990},82,[675,1992,1993],{"class":698},"      signal: AbortSignal.",[675,1995,1720],{"class":795},[675,1997,819],{"class":698},[675,1999,2000],{"class":728},"30_000",[675,2002,2003],{"class":698},"),\n",[675,2005,2007],{"class":677,"line":2006},83,[675,2008,2009],{"class":698},"    });\n",[675,2011,2013,2015,2018,2020,2023,2025,2027,2030,2032,2034,2036],{"class":677,"line":2012},84,[675,2014,976],{"class":694},[675,2016,2017],{"class":698}," (res.status ",[675,2019,1042],{"class":694},[675,2021,2022],{"class":728}," 401",[675,2024,1048],{"class":694},[675,2026,1907],{"class":698},[675,2028,2029],{"class":694},"===",[675,2031,1837],{"class":728},[675,2033,808],{"class":698},[675,2035,1369],{"class":694},[675,2037,2038],{"class":698}," res;\n",[675,2040,2042,2045,2047,2049],{"class":677,"line":2041},85,[675,2043,2044],{"class":694},"    await",[675,2046,1753],{"class":698},[675,2048,601],{"class":795},[675,2050,1688],{"class":698},[675,2052,2054,2057,2060],{"class":677,"line":2053},86,[675,2055,2056],{"class":698},"    tokens.",[675,2058,2059],{"class":795},"invalidate",[675,2061,1688],{"class":698},[675,2063,2065],{"class":677,"line":2064},87,[675,2066,1216],{"class":698},[675,2068,2070],{"class":677,"line":2069},88,[675,2071,1642],{"class":698},[675,2073,2075],{"class":677,"line":2074},89,[675,2076,716],{"emptyLinePlaceholder":715},[675,2078,2080],{"class":677,"line":2079},90,[675,2081,2082],{"class":681},"\u002F** Revokes a token. Returns how many seconds the revocation is held. *\u002F\n",[675,2084,2086,2088,2090,2092,2095,2097,2100],{"class":677,"line":2085},91,[675,2087,722],{"class":694},[675,2089,1661],{"class":694},[675,2091,1664],{"class":694},[675,2093,2094],{"class":795}," revokeToken",[675,2096,819],{"class":698},[675,2098,2099],{"class":804},"token",[675,2101,1240],{"class":698},[675,2103,2105,2107,2109,2111,2113,2115,2117,2119,2121,2124],{"class":677,"line":2104},92,[675,2106,1675],{"class":694},[675,2108,1696],{"class":728},[675,2110,732],{"class":694},[675,2112,1701],{"class":694},[675,2114,1704],{"class":795},[675,2116,819],{"class":698},[675,2118,1485],{"class":705},[675,2120,1711],{"class":728},[675,2122,2123],{"class":705},"}\u002Fapi-account\u002Frevoke-token`",[675,2125,1949],{"class":698},[675,2127,2129,2132,2135],{"class":677,"line":2128},93,[675,2130,2131],{"class":698},"    method: ",[675,2133,2134],{"class":705},"'POST'",[675,2136,2137],{"class":698},",\n",[675,2139,2141,2144,2146,2148,2150],{"class":677,"line":2140},94,[675,2142,2143],{"class":698},"    headers: { Authorization: ",[675,2145,1973],{"class":705},[675,2147,2099],{"class":698},[675,2149,1497],{"class":705},[675,2151,1987],{"class":698},[675,2153,2155,2158,2160,2162,2165],{"class":677,"line":2154},95,[675,2156,2157],{"class":698},"    signal: AbortSignal.",[675,2159,1720],{"class":795},[675,2161,819],{"class":698},[675,2163,2164],{"class":728},"10_000",[675,2166,2003],{"class":698},[675,2168,2170],{"class":677,"line":2169},96,[675,2171,2172],{"class":698},"  });\n",[675,2174,2176,2178,2181,2183,2185,2187,2190,2193,2196,2199,2201,2203],{"class":677,"line":2175},97,[675,2177,1675],{"class":694},[675,2179,2180],{"class":728}," body",[675,2182,732],{"class":694},[675,2184,1701],{"class":694},[675,2186,1753],{"class":698},[675,2188,2189],{"class":795},"json",[675,2191,2192],{"class":698},"().",[675,2194,2195],{"class":795},"catch",[675,2197,2198],{"class":698},"(() ",[675,2200,811],{"class":694},[675,2202,921],{"class":728},[675,2204,842],{"class":698},[675,2206,2208,2211,2213,2215,2218,2221,2223,2225,2227,2230,2233,2235,2238,2241,2244,2247,2250,2252,2254,2257,2260],{"class":677,"line":2207},98,[675,2209,2210],{"class":694},"  if",[675,2212,801],{"class":698},[675,2214,981],{"class":694},[675,2216,2217],{"class":698},"res.ok) ",[675,2219,2220],{"class":694},"throw",[675,2222,1010],{"class":694},[675,2224,1013],{"class":795},[675,2226,819],{"class":698},[675,2228,2229],{"class":705},"`revoke-token answered ${",[675,2231,2232],{"class":698},"res",[675,2234,825],{"class":705},[675,2236,2237],{"class":698},"status",[675,2239,2240],{"class":705},"} ${",[675,2242,2243],{"class":698},"body",[675,2245,2246],{"class":705},"?.",[675,2248,2249],{"class":698},"error",[675,2251,2246],{"class":705},[675,2253,362],{"class":698},[675,2255,2256],{"class":694}," ??",[675,2258,2259],{"class":705}," ''}`",[675,2261,842],{"class":698},[675,2263,2265,2267],{"class":677,"line":2264},99,[675,2266,1797],{"class":694},[675,2268,2269],{"class":698}," body.data.expiresIn;\n",[675,2271,2273],{"class":677,"line":2272},100,[675,2274,1642],{"class":698},[358,2276,2277],{},"Wire it into a long-running service. The service checks its clock at start-up, confirms its\ncredentials, and revokes its token when it shuts down:",[595,2279,2282],{"className":668,"code":2280,"filename":2281,"language":671,"meta":602,"style":602},"\u002F\u002F service.mjs: a long-running service that uses the token manager.\nimport { authorizedFetch, measureSkew, revokeToken, TokenManager } from '.\u002Ftoken-manager.mjs';\n\nconst tokens = new TokenManager({\n  apiKey: process.env.MTO_API_KEY,\n  apiSecret: process.env.MTO_API_SECRET,\n});\n\n\u002F\u002F 1. The clock. A few seconds is harmless; more than 30 breaks every token.\nconst skew = await measureSkew();\nif (Math.abs(skew) > 10) console.warn(`Local clock is off by ${skew} s; correcting. Fix NTP on this host.`);\ntokens.setSkew(skew);\n\n\u002F\u002F 2. The credentials, once, at start-up.\nconst res = await authorizedFetch(tokens, '\u002Fapi-account\u002Fvalidate-me');\nif (!res.ok) throw new Error(`Credentials refused: ${res.status}`);\nconst account = await res.json();\nconsole.log(`API account ${account.companyName}, ${account.roles.length} roles`);\n\n\u002F\u002F 3. Revoke the token on the way out, so it cannot be replayed from a log or a dump.\nprocess.once('SIGTERM', async () => {\n  const token = tokens.peek();\n  if (token) {\n    try {\n      console.log(`Token revoked for ${await revokeToken(token)} s`);\n    } catch (error) {\n      console.warn(`Could not revoke the token: ${error.message}`);\n    }\n  }\n  process.exit(0);\n});\n\n\u002F\u002F ... the rest of your service calls authorizedFetch(tokens, path, init) ...\n","service.mjs",[362,2283,2284,2289,2303,2307,2323,2333,2343,2348,2352,2357,2372,2410,2421,2425,2430,2450,2481,2498,2539,2543,2548,2573,2590,2597,2604,2633,2643,2665,2669,2673,2688,2692,2696],{"__ignoreMap":602},[675,2285,2286],{"class":677,"line":678},[675,2287,2288],{"class":681},"\u002F\u002F service.mjs: a long-running service that uses the token manager.\n",[675,2290,2291,2293,2296,2298,2301],{"class":677,"line":685},[675,2292,695],{"class":694},[675,2294,2295],{"class":698}," { authorizedFetch, measureSkew, revokeToken, TokenManager } ",[675,2297,702],{"class":694},[675,2299,2300],{"class":705}," '.\u002Ftoken-manager.mjs'",[675,2302,709],{"class":698},[675,2304,2305],{"class":677,"line":691},[675,2306,716],{"emptyLinePlaceholder":715},[675,2308,2309,2311,2314,2316,2318,2320],{"class":677,"line":712},[675,2310,743],{"class":694},[675,2312,2313],{"class":728}," tokens",[675,2315,732],{"class":694},[675,2317,1010],{"class":694},[675,2319,858],{"class":795},[675,2321,2322],{"class":698},"({\n",[675,2324,2325,2328,2331],{"class":677,"line":719},[675,2326,2327],{"class":698},"  apiKey: process.env.",[675,2329,2330],{"class":728},"MTO_API_KEY",[675,2332,2137],{"class":698},[675,2334,2335,2338,2341],{"class":677,"line":740},[675,2336,2337],{"class":698},"  apiSecret: process.env.",[675,2339,2340],{"class":728},"MTO_API_SECRET",[675,2342,2137],{"class":698},[675,2344,2345],{"class":677,"line":770},[675,2346,2347],{"class":698},"});\n",[675,2349,2350],{"class":677,"line":785},[675,2351,716],{"emptyLinePlaceholder":715},[675,2353,2354],{"class":677,"line":790},[675,2355,2356],{"class":681},"\u002F\u002F 1. The clock. A few seconds is harmless; more than 30 breaks every token.\n",[675,2358,2359,2361,2364,2366,2368,2370],{"class":677,"line":845},[675,2360,743],{"class":694},[675,2362,2363],{"class":728}," skew",[675,2365,732],{"class":694},[675,2367,1701],{"class":694},[675,2369,1667],{"class":795},[675,2371,1688],{"class":698},[675,2373,2374,2377,2380,2383,2386,2388,2391,2394,2397,2399,2402,2405,2408],{"class":677,"line":850},[675,2375,2376],{"class":694},"if",[675,2378,2379],{"class":698}," (Math.",[675,2381,2382],{"class":795},"abs",[675,2384,2385],{"class":698},"(skew) ",[675,2387,1112],{"class":694},[675,2389,2390],{"class":728}," 10",[675,2392,2393],{"class":698},") console.",[675,2395,2396],{"class":795},"warn",[675,2398,819],{"class":698},[675,2400,2401],{"class":705},"`Local clock is off by ${",[675,2403,2404],{"class":698},"skew",[675,2406,2407],{"class":705},"} s; correcting. Fix NTP on this host.`",[675,2409,842],{"class":698},[675,2411,2412,2415,2418],{"class":677,"line":864},[675,2413,2414],{"class":698},"tokens.",[675,2416,2417],{"class":795},"setSkew",[675,2419,2420],{"class":698},"(skew);\n",[675,2422,2423],{"class":677,"line":872},[675,2424,716],{"emptyLinePlaceholder":715},[675,2426,2427],{"class":677,"line":880},[675,2428,2429],{"class":681},"\u002F\u002F 2. The credentials, once, at start-up.\n",[675,2431,2432,2434,2436,2438,2440,2442,2445,2448],{"class":677,"line":888},[675,2433,743],{"class":694},[675,2435,1696],{"class":728},[675,2437,732],{"class":694},[675,2439,1701],{"class":694},[675,2441,1873],{"class":795},[675,2443,2444],{"class":698},"(tokens, ",[675,2446,2447],{"class":705},"'\u002Fapi-account\u002Fvalidate-me'",[675,2449,842],{"class":698},[675,2451,2452,2454,2456,2458,2460,2462,2464,2466,2468,2471,2473,2475,2477,2479],{"class":677,"line":896},[675,2453,2376],{"class":694},[675,2455,801],{"class":698},[675,2457,981],{"class":694},[675,2459,2217],{"class":698},[675,2461,2220],{"class":694},[675,2463,1010],{"class":694},[675,2465,1013],{"class":795},[675,2467,819],{"class":698},[675,2469,2470],{"class":705},"`Credentials refused: ${",[675,2472,2232],{"class":698},[675,2474,825],{"class":705},[675,2476,2237],{"class":698},[675,2478,1497],{"class":705},[675,2480,842],{"class":698},[675,2482,2483,2485,2488,2490,2492,2494,2496],{"class":677,"line":913},[675,2484,743],{"class":694},[675,2486,2487],{"class":728}," account",[675,2489,732],{"class":694},[675,2491,1701],{"class":694},[675,2493,1753],{"class":698},[675,2495,2189],{"class":795},[675,2497,1688],{"class":698},[675,2499,2500,2503,2506,2508,2511,2514,2516,2519,2522,2524,2526,2529,2531,2534,2537],{"class":677,"line":929},[675,2501,2502],{"class":698},"console.",[675,2504,2505],{"class":795},"log",[675,2507,819],{"class":698},[675,2509,2510],{"class":705},"`API account ${",[675,2512,2513],{"class":698},"account",[675,2515,825],{"class":705},[675,2517,2518],{"class":698},"companyName",[675,2520,2521],{"class":705},"}, ${",[675,2523,2513],{"class":698},[675,2525,825],{"class":705},[675,2527,2528],{"class":698},"roles",[675,2530,825],{"class":705},[675,2532,2533],{"class":728},"length",[675,2535,2536],{"class":705},"} roles`",[675,2538,842],{"class":698},[675,2540,2541],{"class":677,"line":934},[675,2542,716],{"emptyLinePlaceholder":715},[675,2544,2545],{"class":677,"line":973},[675,2546,2547],{"class":681},"\u002F\u002F 3. Revoke the token on the way out, so it cannot be replayed from a log or a dump.\n",[675,2549,2550,2553,2556,2558,2561,2563,2566,2569,2571],{"class":677,"line":1004},[675,2551,2552],{"class":698},"process.",[675,2554,2555],{"class":795},"once",[675,2557,819],{"class":698},[675,2559,2560],{"class":705},"'SIGTERM'",[675,2562,945],{"class":698},[675,2564,2565],{"class":694},"async",[675,2567,2568],{"class":698}," () ",[675,2570,811],{"class":694},[675,2572,861],{"class":698},[675,2574,2575,2577,2580,2582,2585,2588],{"class":677,"line":1023},[675,2576,1675],{"class":694},[675,2578,2579],{"class":728}," token",[675,2581,732],{"class":694},[675,2583,2584],{"class":698}," tokens.",[675,2586,2587],{"class":795},"peek",[675,2589,1688],{"class":698},[675,2591,2592,2594],{"class":677,"line":1029},[675,2593,2210],{"class":694},[675,2595,2596],{"class":698}," (token) {\n",[675,2598,2599,2602],{"class":677,"line":1067},[675,2600,2601],{"class":694},"    try",[675,2603,861],{"class":698},[675,2605,2606,2609,2611,2613,2616,2619,2621,2623,2625,2628,2631],{"class":677,"line":1083},[675,2607,2608],{"class":698},"      console.",[675,2610,2505],{"class":795},[675,2612,819],{"class":698},[675,2614,2615],{"class":705},"`Token revoked for ${",[675,2617,2618],{"class":694},"await",[675,2620,2094],{"class":795},[675,2622,819],{"class":705},[675,2624,2099],{"class":698},[675,2626,2627],{"class":705},")",[675,2629,2630],{"class":705},"} s`",[675,2632,842],{"class":698},[675,2634,2635,2638,2640],{"class":677,"line":1088},[675,2636,2637],{"class":698},"    } ",[675,2639,2195],{"class":694},[675,2641,2642],{"class":698}," (error) {\n",[675,2644,2645,2647,2649,2651,2654,2656,2658,2661,2663],{"class":677,"line":1127},[675,2646,2608],{"class":698},[675,2648,2396],{"class":795},[675,2650,819],{"class":698},[675,2652,2653],{"class":705},"`Could not revoke the token: ${",[675,2655,2249],{"class":698},[675,2657,825],{"class":705},[675,2659,2660],{"class":698},"message",[675,2662,1497],{"class":705},[675,2664,842],{"class":698},[675,2666,2667],{"class":677,"line":1154},[675,2668,1026],{"class":698},[675,2670,2671],{"class":677,"line":1159},[675,2672,1216],{"class":698},[675,2674,2675,2678,2681,2683,2686],{"class":677,"line":1174},[675,2676,2677],{"class":698},"  process.",[675,2679,2680],{"class":795},"exit",[675,2682,819],{"class":698},[675,2684,2685],{"class":728},"0",[675,2687,842],{"class":698},[675,2689,2690],{"class":677,"line":1187},[675,2691,2347],{"class":698},[675,2693,2694],{"class":677,"line":1200},[675,2695,716],{"emptyLinePlaceholder":715},[675,2697,2698],{"class":677,"line":1213},[675,2699,2700],{"class":681},"\u002F\u002F ... the rest of your service calls authorizedFetch(tokens, path, init) ...\n",[367,2702,2704],{"id":2703},"php-sharing-a-token-between-requests","PHP: sharing a token between requests",[358,2706,2707],{},"Under PHP-FPM or Apache, every request starts with empty memory, so an in-process cache lives for one\nrequest only. There are two sound choices:",[625,2709,2710,2716],{},[628,2711,2712,2715],{},[447,2713,2714],{},"Sign a token per request."," It is one HMAC, so this costs almost nothing. It is the simplest option\nand fine for most sites.",[628,2717,2718,2721],{},[447,2719,2720],{},"Share one token through APCu,"," so every request on the machine reuses it until it nears expiry.\nThis keeps the number of live tokens small, which makes revoking one meaningful.",[358,2723,2724],{},"The class below does the second when APCu is available and falls back to the first when it is not.\nAPCu is usually disabled for command-line PHP, so scripts and cron jobs sign per run.",[595,2726,2731],{"className":2727,"code":2728,"filename":2729,"language":2730,"meta":602,"style":602},"language-php shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003C?php\n\u002F\u002F token-cache.php: signed tokens for the Main Team API, shared through APCu when available.\n\u002F\u002F PHP 8.1 or newer with ext-curl and ext-json. APCu is optional.\ndeclare(strict_types=1);\n\nfinal class MainTeamTokens\n{\n    public const BASE_URL = 'https:\u002F\u002Fapi.main-team.org\u002Fv1';\n    private const MAX_LIFETIME = 3600;\n\n    private ?array $local = null; \u002F\u002F ['token' => string, 'exp' => int]\n\n    public function __construct(\n        private string $apiKey,\n        private string $apiSecret,\n        private int $lifetime = 900,\n        private int $renewMargin = 60,\n        private int $skew = 0, \u002F\u002F server clock minus local clock, in seconds\n    ) {\n        if (!preg_match('\u002F^key_[A-Za-z0-9_-]{24}$\u002F', $apiKey)) {\n            throw new InvalidArgumentException('apiKey is not in the issued format (key_ and 24 characters). Are key and secret swapped?');\n        }\n        if (!str_starts_with($apiSecret, 'secret_')) {\n            throw new InvalidArgumentException('apiSecret is not in the issued format (it starts with secret_).');\n        }\n        if ($lifetime > self::MAX_LIFETIME || $lifetime \u003C= $renewMargin) {\n            throw new InvalidArgumentException(\"lifetime must be above $renewMargin and at most \" . self::MAX_LIFETIME . ' seconds.');\n        }\n    }\n\n    private static function b64url(string $bytes): string\n    {\n        return rtrim(strtr(base64_encode($bytes), '+\u002F', '-_'), '=');\n    }\n\n    private function cacheKey(): string\n    {\n        return 'mainteam-token:' . $this->apiKey;\n    }\n\n    private static function apcu(): bool\n    {\n        return function_exists('apcu_enabled') && apcu_enabled();\n    }\n\n    \u002F** A valid token: the shared one, or a fresh one near expiry. *\u002F\n    public function get(): string\n    {\n        $now = time() + $this->skew;\n        $cached = $this->local;\n        if ($cached === null && self::apcu()) {\n            $fetched = apcu_fetch($this->cacheKey(), $hit);\n            $cached = $hit && is_array($fetched) ? $fetched : null;\n        }\n        if ($cached !== null && $cached['exp'] - $this->renewMargin > $now) {\n            $this->local = $cached;\n            return $cached['token'];\n        }\n\n        $exp = $now + $this->lifetime;\n        $header = self::b64url(json_encode(['alg' => 'HS256', 'typ' => 'JWT', 'kid' => $this->apiKey]));\n        $payload = self::b64url(json_encode(['sub' => $this->apiKey, 'iat' => $now, 'exp' => $exp]));\n        $signature = self::b64url(hash_hmac('sha256', \"$header.$payload\", $this->apiSecret, true));\n        $this->local = ['token' => \"$header.$payload.$signature\", 'exp' => $exp];\n        if (self::apcu()) {\n            apcu_store($this->cacheKey(), $this->local, $this->lifetime - $this->renewMargin);\n        }\n        return $this->local['token'];\n    }\n\n    \u002F** Forgets the token, after a 401 or after revoking it. *\u002F\n    public function invalidate(): void\n    {\n        $this->local = null;\n        if (self::apcu()) {\n            apcu_delete($this->cacheKey());\n        }\n    }\n\n    \u002F** Server clock minus local clock, in whole seconds, from the Date header of the health check. *\u002F\n    public static function measureSkew(): int\n    {\n        $date = null;\n        $ch = curl_init(self::BASE_URL . '\u002Fhealth');\n        curl_setopt_array($ch, [\n            CURLOPT_RETURNTRANSFER => true,\n            CURLOPT_TIMEOUT => 5,\n            CURLOPT_HEADERFUNCTION => function ($ch, string $line) use (&$date): int {\n                if (stripos($line, 'date:') === 0) {\n                    $date = trim(substr($line, 5));\n                }\n                return strlen($line);\n            },\n        ]);\n        $before = microtime(true);\n        curl_exec($ch);\n        $after = microtime(true);\n        curl_close($ch);\n        $server = $date === null ? false : strtotime($date);\n        return $server === false ? 0 : (int) round($server - ($before + $after) \u002F 2);\n    }\n\n    \u002F** Revokes a token. Returns how many seconds the revocation is held. *\u002F\n    public static function revoke(string $token): int\n    {\n        $ch = curl_init(self::BASE_URL . '\u002Fapi-account\u002Frevoke-token');\n        curl_setopt_array($ch, [\n            CURLOPT_POSTFIELDS => '',\n            CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],\n            CURLOPT_RETURNTRANSFER => true,\n            CURLOPT_TIMEOUT => 10,\n        ]);\n        $text = curl_exec($ch);\n        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\n        curl_close($ch);\n        $body = is_string($text) ? json_decode($text, true) : null;\n        if ($status !== 200 || !isset($body['data']['expiresIn'])) {\n            throw new RuntimeException(\"revoke-token answered $status \" . ($body['error']['code'] ?? ''));\n        }\n        return (int) $body['data']['expiresIn'];\n    }\n}\n","token-cache.php","php",[362,2732,2733,2741,2746,2751,2768,2772,2782,2787,2802,2817,2821,2840,2844,2856,2867,2876,2892,2907,2924,2929,2960,2976,2981,2999,3013,3017,3040,3073,3077,3081,3085,3110,3115,3152,3156,3160,3175,3179,3197,3201,3205,3223,3227,3248,3252,3256,3261,3276,3280,3301,3315,3337,3360,3389,3393,3428,3443,3456,3460,3464,3483,3536,3580,3630,3673,3686,3725,3729,3744,3748,3752,3757,3773,3777,3791,3803,3819,3823,3827,3831,3836,3853,3857,3868,3891,3899,3911,3923,3957,3981,4003,4008,4019,4024,4029,4045,4053,4068,4075,4103,4148,4153,4158,4164,4187,4192,4214,4221,4233,4251,4262,4273,4278,4291,4316,4323,4355,4390,4431,4436,4456,4461],{"__ignoreMap":602},[675,2734,2735,2738],{"class":677,"line":678},[675,2736,2737],{"class":694},"\u003C?",[675,2739,2740],{"class":728},"php\n",[675,2742,2743],{"class":677,"line":685},[675,2744,2745],{"class":681},"\u002F\u002F token-cache.php: signed tokens for the Main Team API, shared through APCu when available.\n",[675,2747,2748],{"class":677,"line":691},[675,2749,2750],{"class":681},"\u002F\u002F PHP 8.1 or newer with ext-curl and ext-json. APCu is optional.\n",[675,2752,2753,2756,2758,2761,2763,2766],{"class":677,"line":712},[675,2754,2755],{"class":694},"declare",[675,2757,819],{"class":698},[675,2759,2760],{"class":728},"strict_types",[675,2762,1168],{"class":694},[675,2764,2765],{"class":728},"1",[675,2767,842],{"class":698},[675,2769,2770],{"class":677,"line":719},[675,2771,716],{"emptyLinePlaceholder":715},[675,2773,2774,2777,2779],{"class":677,"line":740},[675,2775,2776],{"class":694},"final",[675,2778,855],{"class":694},[675,2780,2781],{"class":804}," MainTeamTokens\n",[675,2783,2784],{"class":677,"line":770},[675,2785,2786],{"class":698},"{\n",[675,2788,2789,2792,2794,2796,2798,2800],{"class":677,"line":785},[675,2790,2791],{"class":694},"    public",[675,2793,725],{"class":694},[675,2795,729],{"class":728},[675,2797,732],{"class":694},[675,2799,735],{"class":705},[675,2801,709],{"class":698},[675,2803,2804,2807,2809,2811,2813,2815],{"class":677,"line":790},[675,2805,2806],{"class":694},"    private",[675,2808,725],{"class":694},[675,2810,775],{"class":728},[675,2812,732],{"class":694},[675,2814,780],{"class":728},[675,2816,709],{"class":698},[675,2818,2819],{"class":677,"line":845},[675,2820,716],{"emptyLinePlaceholder":715},[675,2822,2823,2825,2828,2831,2833,2835,2837],{"class":677,"line":850},[675,2824,2806],{"class":694},[675,2826,2827],{"class":694}," ?array",[675,2829,2830],{"class":698}," $local ",[675,2832,1168],{"class":694},[675,2834,921],{"class":728},[675,2836,907],{"class":698},[675,2838,2839],{"class":681},"\u002F\u002F ['token' => string, 'exp' => int]\n",[675,2841,2842],{"class":677,"line":864},[675,2843,716],{"emptyLinePlaceholder":715},[675,2845,2846,2848,2850,2853],{"class":677,"line":872},[675,2847,2791],{"class":694},[675,2849,1664],{"class":694},[675,2851,2852],{"class":728}," __construct",[675,2854,2855],{"class":698},"(\n",[675,2857,2858,2861,2864],{"class":677,"line":880},[675,2859,2860],{"class":694},"        private",[675,2862,2863],{"class":694}," string",[675,2865,2866],{"class":698}," $apiKey,\n",[675,2868,2869,2871,2873],{"class":677,"line":888},[675,2870,2860],{"class":694},[675,2872,2863],{"class":694},[675,2874,2875],{"class":698}," $apiSecret,\n",[675,2877,2878,2880,2883,2886,2888,2890],{"class":677,"line":896},[675,2879,2860],{"class":694},[675,2881,2882],{"class":694}," int",[675,2884,2885],{"class":698}," $lifetime ",[675,2887,1168],{"class":694},[675,2889,957],{"class":728},[675,2891,2137],{"class":698},[675,2893,2894,2896,2898,2901,2903,2905],{"class":677,"line":913},[675,2895,2860],{"class":694},[675,2897,2882],{"class":694},[675,2899,2900],{"class":698}," $renewMargin ",[675,2902,1168],{"class":694},[675,2904,967],{"class":728},[675,2906,2137],{"class":698},[675,2908,2909,2911,2913,2916,2918,2920,2922],{"class":677,"line":929},[675,2910,2860],{"class":694},[675,2912,2882],{"class":694},[675,2914,2915],{"class":698}," $skew ",[675,2917,1168],{"class":694},[675,2919,904],{"class":728},[675,2921,945],{"class":698},[675,2923,910],{"class":681},[675,2925,2926],{"class":677,"line":934},[675,2927,2928],{"class":698},"    ) {\n",[675,2930,2931,2934,2936,2938,2941,2943,2946,2948,2951,2954,2957],{"class":677,"line":973},[675,2932,2933],{"class":694},"        if",[675,2935,801],{"class":698},[675,2937,981],{"class":694},[675,2939,2940],{"class":728},"preg_match",[675,2942,819],{"class":698},[675,2944,2945],{"class":705},"'\u002F",[675,2947,754],{"class":694},[675,2949,2950],{"class":705},"key_[A-Za-z0-9_-]{24}",[675,2952,2953],{"class":694},"$",[675,2955,2956],{"class":705},"\u002F'",[675,2958,2959],{"class":698},", $apiKey)) {\n",[675,2961,2962,2965,2967,2970,2972,2974],{"class":677,"line":1004},[675,2963,2964],{"class":694},"            throw",[675,2966,1010],{"class":694},[675,2968,2969],{"class":728}," InvalidArgumentException",[675,2971,819],{"class":698},[675,2973,1018],{"class":705},[675,2975,842],{"class":698},[675,2977,2978],{"class":677,"line":1023},[675,2979,2980],{"class":698},"        }\n",[675,2982,2983,2985,2987,2989,2992,2995,2997],{"class":677,"line":1029},[675,2984,2933],{"class":694},[675,2986,801],{"class":698},[675,2988,981],{"class":694},[675,2990,2991],{"class":728},"str_starts_with",[675,2993,2994],{"class":698},"($apiSecret, ",[675,2996,1062],{"class":705},[675,2998,1001],{"class":698},[675,3000,3001,3003,3005,3007,3009,3011],{"class":677,"line":1067},[675,3002,2964],{"class":694},[675,3004,1010],{"class":694},[675,3006,2969],{"class":728},[675,3008,819],{"class":698},[675,3010,1078],{"class":705},[675,3012,842],{"class":698},[675,3014,3015],{"class":677,"line":1083},[675,3016,2980],{"class":698},[675,3018,3019,3021,3024,3026,3029,3031,3033,3035,3037],{"class":677,"line":1088},[675,3020,2933],{"class":694},[675,3022,3023],{"class":698}," ($lifetime ",[675,3025,1112],{"class":694},[675,3027,3028],{"class":694}," self::",[675,3030,1146],{"class":728},[675,3032,1048],{"class":694},[675,3034,2885],{"class":698},[675,3036,1121],{"class":694},[675,3038,3039],{"class":698}," $renewMargin) {\n",[675,3041,3042,3044,3046,3048,3050,3053,3056,3059,3062,3064,3066,3068,3071],{"class":677,"line":1127},[675,3043,2964],{"class":694},[675,3045,1010],{"class":694},[675,3047,2969],{"class":728},[675,3049,819],{"class":698},[675,3051,3052],{"class":705},"\"lifetime must be above ",[675,3054,3055],{"class":698},"$renewMargin",[675,3057,3058],{"class":705}," and at most \"",[675,3060,3061],{"class":694}," .",[675,3063,3028],{"class":694},[675,3065,1146],{"class":728},[675,3067,3061],{"class":694},[675,3069,3070],{"class":705}," ' seconds.'",[675,3072,842],{"class":698},[675,3074,3075],{"class":677,"line":1154},[675,3076,2980],{"class":698},[675,3078,3079],{"class":677,"line":1159},[675,3080,1026],{"class":698},[675,3082,3083],{"class":677,"line":1174},[675,3084,716],{"emptyLinePlaceholder":715},[675,3086,3087,3089,3092,3094,3096,3098,3101,3104,3107],{"class":677,"line":1187},[675,3088,2806],{"class":694},[675,3090,3091],{"class":694}," static",[675,3093,1664],{"class":694},[675,3095,796],{"class":795},[675,3097,819],{"class":698},[675,3099,3100],{"class":694},"string",[675,3102,3103],{"class":698}," $bytes)",[675,3105,3106],{"class":694},":",[675,3108,3109],{"class":694}," string\n",[675,3111,3112],{"class":677,"line":1200},[675,3113,3114],{"class":698},"    {\n",[675,3116,3117,3120,3123,3125,3128,3130,3133,3136,3139,3141,3144,3147,3150],{"class":677,"line":1213},[675,3118,3119],{"class":694},"        return",[675,3121,3122],{"class":728}," rtrim",[675,3124,819],{"class":698},[675,3126,3127],{"class":728},"strtr",[675,3129,819],{"class":698},[675,3131,3132],{"class":728},"base64_encode",[675,3134,3135],{"class":698},"($bytes), ",[675,3137,3138],{"class":705},"'+\u002F'",[675,3140,945],{"class":698},[675,3142,3143],{"class":705},"'-_'",[675,3145,3146],{"class":698},"), ",[675,3148,3149],{"class":705},"'='",[675,3151,842],{"class":698},[675,3153,3154],{"class":677,"line":1219},[675,3155,1026],{"class":698},[675,3157,3158],{"class":677,"line":1224},[675,3159,716],{"emptyLinePlaceholder":715},[675,3161,3162,3164,3166,3169,3171,3173],{"class":677,"line":1230},[675,3163,2806],{"class":694},[675,3165,1664],{"class":694},[675,3167,3168],{"class":795}," cacheKey",[675,3170,1982],{"class":698},[675,3172,3106],{"class":694},[675,3174,3109],{"class":694},[675,3176,3177],{"class":677,"line":1243},[675,3178,3114],{"class":698},[675,3180,3181,3183,3186,3188,3191,3194],{"class":677,"line":1256},[675,3182,3119],{"class":694},[675,3184,3185],{"class":705}," 'mainteam-token:'",[675,3187,3061],{"class":694},[675,3189,3190],{"class":728}," $this",[675,3192,3193],{"class":694},"->",[675,3195,3196],{"class":698},"apiKey;\n",[675,3198,3199],{"class":677,"line":1270},[675,3200,1026],{"class":698},[675,3202,3203],{"class":677,"line":1275},[675,3204,716],{"emptyLinePlaceholder":715},[675,3206,3207,3209,3211,3213,3216,3218,3220],{"class":677,"line":1280},[675,3208,2806],{"class":694},[675,3210,3091],{"class":694},[675,3212,1664],{"class":694},[675,3214,3215],{"class":795}," apcu",[675,3217,1982],{"class":698},[675,3219,3106],{"class":694},[675,3221,3222],{"class":694}," bool\n",[675,3224,3225],{"class":677,"line":1286},[675,3226,3114],{"class":698},[675,3228,3229,3231,3234,3236,3239,3241,3243,3246],{"class":677,"line":1295},[675,3230,3119],{"class":694},[675,3232,3233],{"class":728}," function_exists",[675,3235,819],{"class":698},[675,3237,3238],{"class":705},"'apcu_enabled'",[675,3240,808],{"class":698},[675,3242,1349],{"class":694},[675,3244,3245],{"class":795}," apcu_enabled",[675,3247,1688],{"class":698},[675,3249,3250],{"class":677,"line":1337},[675,3251,1026],{"class":698},[675,3253,3254],{"class":677,"line":1377},[675,3255,716],{"emptyLinePlaceholder":715},[675,3257,3258],{"class":677,"line":1397},[675,3259,3260],{"class":681},"    \u002F** A valid token: the shared one, or a fresh one near expiry. *\u002F\n",[675,3262,3263,3265,3267,3270,3272,3274],{"class":677,"line":1429},[675,3264,2791],{"class":694},[675,3266,1664],{"class":694},[675,3268,3269],{"class":795}," get",[675,3271,1982],{"class":698},[675,3273,3106],{"class":694},[675,3275,3109],{"class":694},[675,3277,3278],{"class":677,"line":1449},[675,3279,3114],{"class":698},[675,3281,3282,3285,3287,3290,3292,3294,3296,3298],{"class":677,"line":1474},[675,3283,3284],{"class":698},"        $now ",[675,3286,1168],{"class":694},[675,3288,3289],{"class":728}," time",[675,3291,1318],{"class":698},[675,3293,1328],{"class":694},[675,3295,3190],{"class":728},[675,3297,3193],{"class":694},[675,3299,3300],{"class":698},"skew;\n",[675,3302,3303,3306,3308,3310,3312],{"class":677,"line":1503},[675,3304,3305],{"class":698},"        $cached ",[675,3307,1168],{"class":694},[675,3309,3190],{"class":728},[675,3311,3193],{"class":694},[675,3313,3314],{"class":698},"local;\n",[675,3316,3317,3319,3322,3324,3326,3329,3331,3334],{"class":677,"line":1517},[675,3318,2933],{"class":694},[675,3320,3321],{"class":698}," ($cached ",[675,3323,2029],{"class":694},[675,3325,921],{"class":728},[675,3327,3328],{"class":694}," &&",[675,3330,3028],{"class":694},[675,3332,3333],{"class":795},"apcu",[675,3335,3336],{"class":698},"()) {\n",[675,3338,3339,3342,3344,3347,3349,3352,3354,3357],{"class":677,"line":1547},[675,3340,3341],{"class":698},"            $fetched ",[675,3343,1168],{"class":694},[675,3345,3346],{"class":795}," apcu_fetch",[675,3348,819],{"class":698},[675,3350,3351],{"class":728},"$this",[675,3353,3193],{"class":694},[675,3355,3356],{"class":795},"cacheKey",[675,3358,3359],{"class":698},"(), $hit);\n",[675,3361,3362,3365,3367,3370,3372,3375,3378,3380,3383,3385,3387],{"class":677,"line":1557},[675,3363,3364],{"class":698},"            $cached ",[675,3366,1168],{"class":694},[675,3368,3369],{"class":698}," $hit ",[675,3371,1349],{"class":694},[675,3373,3374],{"class":728}," is_array",[675,3376,3377],{"class":698},"($fetched) ",[675,3379,1809],{"class":694},[675,3381,3382],{"class":698}," $fetched ",[675,3384,3106],{"class":694},[675,3386,921],{"class":728},[675,3388,709],{"class":698},[675,3390,3391],{"class":677,"line":1562},[675,3392,2980],{"class":698},[675,3394,3395,3397,3399,3401,3403,3405,3408,3411,3414,3416,3418,3420,3423,3425],{"class":677,"line":1567},[675,3396,2933],{"class":694},[675,3398,3321],{"class":698},[675,3400,1042],{"class":694},[675,3402,921],{"class":728},[675,3404,3328],{"class":694},[675,3406,3407],{"class":698}," $cached[",[675,3409,3410],{"class":705},"'exp'",[675,3412,3413],{"class":698},"] ",[675,3415,1357],{"class":694},[675,3417,3190],{"class":728},[675,3419,3193],{"class":694},[675,3421,3422],{"class":698},"renewMargin ",[675,3424,1112],{"class":694},[675,3426,3427],{"class":698}," $now) {\n",[675,3429,3430,3433,3435,3438,3440],{"class":677,"line":1573},[675,3431,3432],{"class":728},"            $this",[675,3434,3193],{"class":694},[675,3436,3437],{"class":698},"local ",[675,3439,1168],{"class":694},[675,3441,3442],{"class":698}," $cached;\n",[675,3444,3445,3448,3450,3453],{"class":677,"line":1581},[675,3446,3447],{"class":694},"            return",[675,3449,3407],{"class":698},[675,3451,3452],{"class":705},"'token'",[675,3454,3455],{"class":698},"];\n",[675,3457,3458],{"class":677,"line":1597},[675,3459,2980],{"class":698},[675,3461,3462],{"class":677,"line":1602},[675,3463,716],{"emptyLinePlaceholder":715},[675,3465,3466,3469,3471,3474,3476,3478,3480],{"class":677,"line":1607},[675,3467,3468],{"class":698},"        $exp ",[675,3470,1168],{"class":694},[675,3472,3473],{"class":698}," $now ",[675,3475,1328],{"class":694},[675,3477,3190],{"class":728},[675,3479,3193],{"class":694},[675,3481,3482],{"class":698},"lifetime;\n",[675,3484,3485,3488,3490,3492,3495,3497,3500,3503,3506,3509,3512,3514,3517,3519,3522,3524,3527,3529,3531,3533],{"class":677,"line":1613},[675,3486,3487],{"class":698},"        $header ",[675,3489,1168],{"class":694},[675,3491,3028],{"class":694},[675,3493,3494],{"class":795},"b64url",[675,3496,819],{"class":698},[675,3498,3499],{"class":728},"json_encode",[675,3501,3502],{"class":698},"([",[675,3504,3505],{"class":705},"'alg'",[675,3507,3508],{"class":694}," =>",[675,3510,3511],{"class":705}," 'HS256'",[675,3513,945],{"class":698},[675,3515,3516],{"class":705},"'typ'",[675,3518,3508],{"class":694},[675,3520,3521],{"class":705}," 'JWT'",[675,3523,945],{"class":698},[675,3525,3526],{"class":705},"'kid'",[675,3528,3508],{"class":694},[675,3530,3190],{"class":728},[675,3532,3193],{"class":694},[675,3534,3535],{"class":698},"apiKey]));\n",[675,3537,3538,3541,3543,3545,3547,3549,3551,3553,3556,3558,3560,3562,3565,3568,3570,3573,3575,3577],{"class":677,"line":1621},[675,3539,3540],{"class":698},"        $payload ",[675,3542,1168],{"class":694},[675,3544,3028],{"class":694},[675,3546,3494],{"class":795},[675,3548,819],{"class":698},[675,3550,3499],{"class":728},[675,3552,3502],{"class":698},[675,3554,3555],{"class":705},"'sub'",[675,3557,3508],{"class":694},[675,3559,3190],{"class":728},[675,3561,3193],{"class":694},[675,3563,3564],{"class":698},"apiKey, ",[675,3566,3567],{"class":705},"'iat'",[675,3569,3508],{"class":694},[675,3571,3572],{"class":698}," $now, ",[675,3574,3410],{"class":705},[675,3576,3508],{"class":694},[675,3578,3579],{"class":698}," $exp]));\n",[675,3581,3582,3585,3587,3589,3591,3593,3596,3598,3600,3602,3605,3608,3610,3613,3615,3617,3619,3621,3624,3627],{"class":677,"line":1634},[675,3583,3584],{"class":698},"        $signature ",[675,3586,1168],{"class":694},[675,3588,3028],{"class":694},[675,3590,3494],{"class":795},[675,3592,819],{"class":698},[675,3594,3595],{"class":728},"hash_hmac",[675,3597,819],{"class":698},[675,3599,1464],{"class":705},[675,3601,945],{"class":698},[675,3603,3604],{"class":705},"\"",[675,3606,3607],{"class":698},"$header",[675,3609,825],{"class":705},[675,3611,3612],{"class":698},"$payload",[675,3614,3604],{"class":705},[675,3616,945],{"class":698},[675,3618,3351],{"class":728},[675,3620,3193],{"class":694},[675,3622,3623],{"class":698},"apiSecret, ",[675,3625,3626],{"class":728},"true",[675,3628,3629],{"class":698},"));\n",[675,3631,3632,3635,3637,3639,3641,3644,3646,3648,3651,3653,3655,3657,3659,3662,3664,3666,3668,3670],{"class":677,"line":1639},[675,3633,3634],{"class":728},"        $this",[675,3636,3193],{"class":694},[675,3638,3437],{"class":698},[675,3640,1168],{"class":694},[675,3642,3643],{"class":698}," [",[675,3645,3452],{"class":705},[675,3647,3508],{"class":694},[675,3649,3650],{"class":705}," \"",[675,3652,3607],{"class":698},[675,3654,825],{"class":705},[675,3656,3612],{"class":698},[675,3658,825],{"class":705},[675,3660,3661],{"class":698},"$signature",[675,3663,3604],{"class":705},[675,3665,945],{"class":698},[675,3667,3410],{"class":705},[675,3669,3508],{"class":694},[675,3671,3672],{"class":698}," $exp];\n",[675,3674,3675,3677,3679,3682,3684],{"class":677,"line":1645},[675,3676,2933],{"class":694},[675,3678,801],{"class":698},[675,3680,3681],{"class":694},"self::",[675,3683,3333],{"class":795},[675,3685,3336],{"class":698},[675,3687,3688,3691,3693,3695,3697,3699,3702,3704,3706,3709,3711,3713,3716,3718,3720,3722],{"class":677,"line":1650},[675,3689,3690],{"class":795},"            apcu_store",[675,3692,819],{"class":698},[675,3694,3351],{"class":728},[675,3696,3193],{"class":694},[675,3698,3356],{"class":795},[675,3700,3701],{"class":698},"(), ",[675,3703,3351],{"class":728},[675,3705,3193],{"class":694},[675,3707,3708],{"class":698},"local, ",[675,3710,3351],{"class":728},[675,3712,3193],{"class":694},[675,3714,3715],{"class":698},"lifetime ",[675,3717,1357],{"class":694},[675,3719,3190],{"class":728},[675,3721,3193],{"class":694},[675,3723,3724],{"class":698},"renewMargin);\n",[675,3726,3727],{"class":677,"line":1656},[675,3728,2980],{"class":698},[675,3730,3731,3733,3735,3737,3740,3742],{"class":677,"line":1672},[675,3732,3119],{"class":694},[675,3734,3190],{"class":728},[675,3736,3193],{"class":694},[675,3738,3739],{"class":698},"local[",[675,3741,3452],{"class":705},[675,3743,3455],{"class":698},[675,3745,3746],{"class":677,"line":1691},[675,3747,1026],{"class":698},[675,3749,3750],{"class":677,"line":1731},[675,3751,716],{"emptyLinePlaceholder":715},[675,3753,3754],{"class":677,"line":1747},[675,3755,3756],{"class":681},"    \u002F** Forgets the token, after a 401 or after revoking it. *\u002F\n",[675,3758,3759,3761,3763,3766,3768,3770],{"class":677,"line":1760},[675,3760,2791],{"class":694},[675,3762,1664],{"class":694},[675,3764,3765],{"class":795}," invalidate",[675,3767,1982],{"class":698},[675,3769,3106],{"class":694},[675,3771,3772],{"class":694}," void\n",[675,3774,3775],{"class":677,"line":1794},[675,3776,3114],{"class":698},[675,3778,3779,3781,3783,3785,3787,3789],{"class":677,"line":1848},[675,3780,3634],{"class":728},[675,3782,3193],{"class":694},[675,3784,3437],{"class":698},[675,3786,1168],{"class":694},[675,3788,921],{"class":728},[675,3790,709],{"class":698},[675,3792,3793,3795,3797,3799,3801],{"class":677,"line":1853},[675,3794,2933],{"class":694},[675,3796,801],{"class":698},[675,3798,3681],{"class":694},[675,3800,3333],{"class":795},[675,3802,3336],{"class":698},[675,3804,3805,3808,3810,3812,3814,3816],{"class":677,"line":1858},[675,3806,3807],{"class":795},"            apcu_delete",[675,3809,819],{"class":698},[675,3811,3351],{"class":728},[675,3813,3193],{"class":694},[675,3815,3356],{"class":795},[675,3817,3818],{"class":698},"());\n",[675,3820,3821],{"class":677,"line":1864},[675,3822,2980],{"class":698},[675,3824,3825],{"class":677,"line":1896},[675,3826,1026],{"class":698},[675,3828,3829],{"class":677,"line":1923},[675,3830,716],{"emptyLinePlaceholder":715},[675,3832,3833],{"class":677,"line":1952},[675,3834,3835],{"class":681},"    \u002F** Server clock minus local clock, in whole seconds, from the Date header of the health check. *\u002F\n",[675,3837,3838,3840,3842,3844,3846,3848,3850],{"class":677,"line":1961},[675,3839,2791],{"class":694},[675,3841,3091],{"class":694},[675,3843,1664],{"class":694},[675,3845,1667],{"class":795},[675,3847,1982],{"class":698},[675,3849,3106],{"class":694},[675,3851,3852],{"class":694}," int\n",[675,3854,3855],{"class":677,"line":1990},[675,3856,3114],{"class":698},[675,3858,3859,3862,3864,3866],{"class":677,"line":2006},[675,3860,3861],{"class":698},"        $date ",[675,3863,1168],{"class":694},[675,3865,921],{"class":728},[675,3867,709],{"class":698},[675,3869,3870,3873,3875,3878,3880,3882,3884,3886,3889],{"class":677,"line":2012},[675,3871,3872],{"class":698},"        $ch ",[675,3874,1168],{"class":694},[675,3876,3877],{"class":728}," curl_init",[675,3879,819],{"class":698},[675,3881,3681],{"class":694},[675,3883,1711],{"class":728},[675,3885,3061],{"class":694},[675,3887,3888],{"class":705}," '\u002Fhealth'",[675,3890,842],{"class":698},[675,3892,3893,3896],{"class":677,"line":2041},[675,3894,3895],{"class":728},"        curl_setopt_array",[675,3897,3898],{"class":698},"($ch, [\n",[675,3900,3901,3904,3906,3909],{"class":677,"line":2053},[675,3902,3903],{"class":728},"            CURLOPT_RETURNTRANSFER",[675,3905,3508],{"class":694},[675,3907,3908],{"class":728}," true",[675,3910,2137],{"class":698},[675,3912,3913,3916,3918,3921],{"class":677,"line":2064},[675,3914,3915],{"class":728},"            CURLOPT_TIMEOUT",[675,3917,3508],{"class":694},[675,3919,3920],{"class":728}," 5",[675,3922,2137],{"class":698},[675,3924,3925,3928,3930,3932,3935,3937,3940,3943,3945,3948,3951,3953,3955],{"class":677,"line":2069},[675,3926,3927],{"class":728},"            CURLOPT_HEADERFUNCTION",[675,3929,3508],{"class":694},[675,3931,1664],{"class":694},[675,3933,3934],{"class":698}," ($ch, ",[675,3936,3100],{"class":694},[675,3938,3939],{"class":698}," $line) ",[675,3941,3942],{"class":694},"use",[675,3944,801],{"class":698},[675,3946,3947],{"class":694},"&",[675,3949,3950],{"class":698},"$date)",[675,3952,3106],{"class":694},[675,3954,2882],{"class":694},[675,3956,861],{"class":698},[675,3958,3959,3962,3964,3967,3970,3973,3975,3977,3979],{"class":677,"line":2074},[675,3960,3961],{"class":694},"                if",[675,3963,801],{"class":698},[675,3965,3966],{"class":728},"stripos",[675,3968,3969],{"class":698},"($line, ",[675,3971,3972],{"class":705},"'date:'",[675,3974,808],{"class":698},[675,3976,2029],{"class":694},[675,3978,904],{"class":728},[675,3980,1240],{"class":698},[675,3982,3983,3986,3988,3991,3993,3996,3998,4001],{"class":677,"line":2079},[675,3984,3985],{"class":698},"                    $date ",[675,3987,1168],{"class":694},[675,3989,3990],{"class":728}," trim",[675,3992,819],{"class":698},[675,3994,3995],{"class":728},"substr",[675,3997,3969],{"class":698},[675,3999,4000],{"class":728},"5",[675,4002,3629],{"class":698},[675,4004,4005],{"class":677,"line":2085},[675,4006,4007],{"class":698},"                }\n",[675,4009,4010,4013,4016],{"class":677,"line":2104},[675,4011,4012],{"class":694},"                return",[675,4014,4015],{"class":728}," strlen",[675,4017,4018],{"class":698},"($line);\n",[675,4020,4021],{"class":677,"line":2128},[675,4022,4023],{"class":698},"            },\n",[675,4025,4026],{"class":677,"line":2140},[675,4027,4028],{"class":698},"        ]);\n",[675,4030,4031,4034,4036,4039,4041,4043],{"class":677,"line":2154},[675,4032,4033],{"class":698},"        $before ",[675,4035,1168],{"class":694},[675,4037,4038],{"class":728}," microtime",[675,4040,819],{"class":698},[675,4042,3626],{"class":728},[675,4044,842],{"class":698},[675,4046,4047,4050],{"class":677,"line":2169},[675,4048,4049],{"class":728},"        curl_exec",[675,4051,4052],{"class":698},"($ch);\n",[675,4054,4055,4058,4060,4062,4064,4066],{"class":677,"line":2175},[675,4056,4057],{"class":698},"        $after ",[675,4059,1168],{"class":694},[675,4061,4038],{"class":728},[675,4063,819],{"class":698},[675,4065,3626],{"class":728},[675,4067,842],{"class":698},[675,4069,4070,4073],{"class":677,"line":2207},[675,4071,4072],{"class":728},"        curl_close",[675,4074,4052],{"class":698},[675,4076,4077,4080,4082,4085,4087,4089,4092,4095,4097,4100],{"class":677,"line":2264},[675,4078,4079],{"class":698},"        $server ",[675,4081,1168],{"class":694},[675,4083,4084],{"class":698}," $date ",[675,4086,2029],{"class":694},[675,4088,921],{"class":728},[675,4090,4091],{"class":694}," ?",[675,4093,4094],{"class":728}," false",[675,4096,1814],{"class":694},[675,4098,4099],{"class":728}," strtotime",[675,4101,4102],{"class":698},"($date);\n",[675,4104,4105,4107,4110,4112,4114,4116,4118,4120,4122,4125,4127,4129,4132,4134,4137,4139,4142,4144,4146],{"class":677,"line":2272},[675,4106,3119],{"class":694},[675,4108,4109],{"class":698}," $server ",[675,4111,2029],{"class":694},[675,4113,4094],{"class":728},[675,4115,4091],{"class":694},[675,4117,904],{"class":728},[675,4119,1814],{"class":694},[675,4121,801],{"class":698},[675,4123,4124],{"class":694},"int",[675,4126,808],{"class":698},[675,4128,1819],{"class":728},[675,4130,4131],{"class":698},"($server ",[675,4133,1357],{"class":694},[675,4135,4136],{"class":698}," ($before ",[675,4138,1328],{"class":694},[675,4140,4141],{"class":698}," $after) ",[675,4143,765],{"class":694},[675,4145,1837],{"class":728},[675,4147,842],{"class":698},[675,4149,4151],{"class":677,"line":4150},101,[675,4152,1026],{"class":698},[675,4154,4156],{"class":677,"line":4155},102,[675,4157,716],{"emptyLinePlaceholder":715},[675,4159,4161],{"class":677,"line":4160},103,[675,4162,4163],{"class":681},"    \u002F** Revokes a token. Returns how many seconds the revocation is held. *\u002F\n",[675,4165,4167,4169,4171,4173,4176,4178,4180,4183,4185],{"class":677,"line":4166},104,[675,4168,2791],{"class":694},[675,4170,3091],{"class":694},[675,4172,1664],{"class":694},[675,4174,4175],{"class":795}," revoke",[675,4177,819],{"class":698},[675,4179,3100],{"class":694},[675,4181,4182],{"class":698}," $token)",[675,4184,3106],{"class":694},[675,4186,3852],{"class":694},[675,4188,4190],{"class":677,"line":4189},105,[675,4191,3114],{"class":698},[675,4193,4195,4197,4199,4201,4203,4205,4207,4209,4212],{"class":677,"line":4194},106,[675,4196,3872],{"class":698},[675,4198,1168],{"class":694},[675,4200,3877],{"class":728},[675,4202,819],{"class":698},[675,4204,3681],{"class":694},[675,4206,1711],{"class":728},[675,4208,3061],{"class":694},[675,4210,4211],{"class":705}," '\u002Fapi-account\u002Frevoke-token'",[675,4213,842],{"class":698},[675,4215,4217,4219],{"class":677,"line":4216},107,[675,4218,3895],{"class":728},[675,4220,3898],{"class":698},[675,4222,4224,4227,4229,4231],{"class":677,"line":4223},108,[675,4225,4226],{"class":728},"            CURLOPT_POSTFIELDS",[675,4228,3508],{"class":694},[675,4230,998],{"class":705},[675,4232,2137],{"class":698},[675,4234,4236,4239,4241,4243,4246,4248],{"class":677,"line":4235},109,[675,4237,4238],{"class":728},"            CURLOPT_HTTPHEADER",[675,4240,3508],{"class":694},[675,4242,3643],{"class":698},[675,4244,4245],{"class":705},"'Authorization: Bearer '",[675,4247,3061],{"class":694},[675,4249,4250],{"class":698}," $token],\n",[675,4252,4254,4256,4258,4260],{"class":677,"line":4253},110,[675,4255,3903],{"class":728},[675,4257,3508],{"class":694},[675,4259,3908],{"class":728},[675,4261,2137],{"class":698},[675,4263,4265,4267,4269,4271],{"class":677,"line":4264},111,[675,4266,3915],{"class":728},[675,4268,3508],{"class":694},[675,4270,2390],{"class":728},[675,4272,2137],{"class":698},[675,4274,4276],{"class":677,"line":4275},112,[675,4277,4028],{"class":698},[675,4279,4281,4284,4286,4289],{"class":677,"line":4280},113,[675,4282,4283],{"class":698},"        $text ",[675,4285,1168],{"class":694},[675,4287,4288],{"class":728}," curl_exec",[675,4290,4052],{"class":698},[675,4292,4294,4297,4299,4301,4303,4305,4308,4311,4314],{"class":677,"line":4293},114,[675,4295,4296],{"class":698},"        $status ",[675,4298,1168],{"class":694},[675,4300,801],{"class":698},[675,4302,4124],{"class":694},[675,4304,808],{"class":698},[675,4306,4307],{"class":728},"curl_getinfo",[675,4309,4310],{"class":698},"($ch, ",[675,4312,4313],{"class":728},"CURLINFO_RESPONSE_CODE",[675,4315,842],{"class":698},[675,4317,4319,4321],{"class":677,"line":4318},115,[675,4320,4072],{"class":728},[675,4322,4052],{"class":698},[675,4324,4326,4329,4331,4334,4337,4339,4342,4345,4347,4349,4351,4353],{"class":677,"line":4325},116,[675,4327,4328],{"class":698},"        $body ",[675,4330,1168],{"class":694},[675,4332,4333],{"class":728}," is_string",[675,4335,4336],{"class":698},"($text) ",[675,4338,1809],{"class":694},[675,4340,4341],{"class":728}," json_decode",[675,4343,4344],{"class":698},"($text, ",[675,4346,3626],{"class":728},[675,4348,808],{"class":698},[675,4350,3106],{"class":694},[675,4352,921],{"class":728},[675,4354,709],{"class":698},[675,4356,4358,4360,4363,4365,4368,4370,4372,4375,4378,4381,4384,4387],{"class":677,"line":4357},117,[675,4359,2933],{"class":694},[675,4361,4362],{"class":698}," ($status ",[675,4364,1042],{"class":694},[675,4366,4367],{"class":728}," 200",[675,4369,1048],{"class":694},[675,4371,1051],{"class":694},[675,4373,4374],{"class":728},"isset",[675,4376,4377],{"class":698},"($body[",[675,4379,4380],{"class":705},"'data'",[675,4382,4383],{"class":698},"][",[675,4385,4386],{"class":705},"'expiresIn'",[675,4388,4389],{"class":698},"])) {\n",[675,4391,4393,4395,4397,4400,4402,4405,4408,4410,4412,4415,4418,4420,4423,4425,4427,4429],{"class":677,"line":4392},118,[675,4394,2964],{"class":694},[675,4396,1010],{"class":694},[675,4398,4399],{"class":728}," RuntimeException",[675,4401,819],{"class":698},[675,4403,4404],{"class":705},"\"revoke-token answered ",[675,4406,4407],{"class":698},"$status",[675,4409,3650],{"class":705},[675,4411,3061],{"class":694},[675,4413,4414],{"class":698}," ($body[",[675,4416,4417],{"class":705},"'error'",[675,4419,4383],{"class":698},[675,4421,4422],{"class":705},"'code'",[675,4424,3413],{"class":698},[675,4426,995],{"class":694},[675,4428,998],{"class":705},[675,4430,3629],{"class":698},[675,4432,4434],{"class":677,"line":4433},119,[675,4435,2980],{"class":698},[675,4437,4439,4441,4443,4445,4448,4450,4452,4454],{"class":677,"line":4438},120,[675,4440,3119],{"class":694},[675,4442,801],{"class":698},[675,4444,4124],{"class":694},[675,4446,4447],{"class":698},") $body[",[675,4449,4380],{"class":705},[675,4451,4383],{"class":698},[675,4453,4386],{"class":705},[675,4455,3455],{"class":698},[675,4457,4459],{"class":677,"line":4458},121,[675,4460,1026],{"class":698},[675,4462,4464],{"class":677,"line":4463},122,[675,4465,1642],{"class":698},[358,4467,4468,4469,3106],{},"Using it, with a retry on ",[362,4470,656],{},[595,4472,4475],{"className":2727,"code":4473,"filename":4474,"language":2730,"meta":602,"style":602},"\u003C?php\ndeclare(strict_types=1);\nrequire __DIR__ . '\u002Ftoken-cache.php';\n\n$tokens = new MainTeamTokens(getenv('MTO_API_KEY') ?: '', getenv('MTO_API_SECRET') ?: '');\n\nfunction getJson(MainTeamTokens $tokens, string $path): array\n{\n    for ($attempt = 1; ; $attempt++) {\n        $ch = curl_init(MainTeamTokens::BASE_URL . $path);\n        curl_setopt_array($ch, [\n            CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $tokens->get()],\n            CURLOPT_RETURNTRANSFER => true,\n            CURLOPT_CONNECTTIMEOUT => 5,\n            CURLOPT_TIMEOUT => 30,\n        ]);\n        $text = (string) curl_exec($ch);\n        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\n        curl_close($ch);\n        if ($status === 401 && $attempt === 1) {\n            $tokens->invalidate(); \u002F\u002F sign a fresh token and try once more\n            continue;\n        }\n        if ($status !== 200) {\n            throw new RuntimeException(\"GET $path answered $status\");\n        }\n        return json_decode($text, true) ?? [];\n    }\n}\n\n$account = getJson($tokens, '\u002Fapi-account\u002Fvalidate-me');\necho \"API account {$account['companyName']}\\n\";\n","example.php",[362,4476,4477,4483,4497,4512,4516,4562,4566,4592,4596,4615,4637,4643,4665,4675,4686,4697,4701,4718,4738,4744,4765,4780,4787,4791,4803,4828,4832,4849,4853,4857,4861,4877],{"__ignoreMap":602},[675,4478,4479,4481],{"class":677,"line":678},[675,4480,2737],{"class":694},[675,4482,2740],{"class":728},[675,4484,4485,4487,4489,4491,4493,4495],{"class":677,"line":685},[675,4486,2755],{"class":694},[675,4488,819],{"class":698},[675,4490,2760],{"class":728},[675,4492,1168],{"class":694},[675,4494,2765],{"class":728},[675,4496,842],{"class":698},[675,4498,4499,4502,4505,4507,4510],{"class":677,"line":691},[675,4500,4501],{"class":694},"require",[675,4503,4504],{"class":728}," __DIR__",[675,4506,3061],{"class":694},[675,4508,4509],{"class":705}," '\u002Ftoken-cache.php'",[675,4511,709],{"class":698},[675,4513,4514],{"class":677,"line":712},[675,4515,716],{"emptyLinePlaceholder":715},[675,4517,4518,4521,4523,4525,4528,4530,4533,4535,4538,4540,4543,4545,4547,4549,4551,4554,4556,4558,4560],{"class":677,"line":719},[675,4519,4520],{"class":698},"$tokens ",[675,4522,1168],{"class":694},[675,4524,1010],{"class":694},[675,4526,4527],{"class":728}," MainTeamTokens",[675,4529,819],{"class":698},[675,4531,4532],{"class":728},"getenv",[675,4534,819],{"class":698},[675,4536,4537],{"class":705},"'MTO_API_KEY'",[675,4539,808],{"class":698},[675,4541,4542],{"class":694},"?:",[675,4544,998],{"class":705},[675,4546,945],{"class":698},[675,4548,4532],{"class":728},[675,4550,819],{"class":698},[675,4552,4553],{"class":705},"'MTO_API_SECRET'",[675,4555,808],{"class":698},[675,4557,4542],{"class":694},[675,4559,998],{"class":705},[675,4561,842],{"class":698},[675,4563,4564],{"class":677,"line":740},[675,4565,716],{"emptyLinePlaceholder":715},[675,4567,4568,4571,4574,4576,4579,4582,4584,4587,4589],{"class":677,"line":770},[675,4569,4570],{"class":694},"function",[675,4572,4573],{"class":795}," getJson",[675,4575,819],{"class":698},[675,4577,4578],{"class":728},"MainTeamTokens",[675,4580,4581],{"class":698}," $tokens, ",[675,4583,3100],{"class":694},[675,4585,4586],{"class":698}," $path)",[675,4588,3106],{"class":694},[675,4590,4591],{"class":694}," array\n",[675,4593,4594],{"class":677,"line":785},[675,4595,2786],{"class":698},[675,4597,4598,4601,4604,4606,4608,4611,4613],{"class":677,"line":790},[675,4599,4600],{"class":694},"    for",[675,4602,4603],{"class":698}," ($attempt ",[675,4605,1168],{"class":694},[675,4607,1912],{"class":728},[675,4609,4610],{"class":698},"; ; $attempt",[675,4612,1918],{"class":694},[675,4614,1240],{"class":698},[675,4616,4617,4619,4621,4623,4625,4627,4630,4632,4634],{"class":677,"line":845},[675,4618,3872],{"class":698},[675,4620,1168],{"class":694},[675,4622,3877],{"class":728},[675,4624,819],{"class":698},[675,4626,4578],{"class":728},[675,4628,4629],{"class":694},"::",[675,4631,1711],{"class":728},[675,4633,3061],{"class":694},[675,4635,4636],{"class":698}," $path);\n",[675,4638,4639,4641],{"class":677,"line":850},[675,4640,3895],{"class":728},[675,4642,3898],{"class":698},[675,4644,4645,4647,4649,4651,4653,4655,4658,4660,4662],{"class":677,"line":864},[675,4646,4238],{"class":728},[675,4648,3508],{"class":694},[675,4650,3643],{"class":698},[675,4652,4245],{"class":705},[675,4654,3061],{"class":694},[675,4656,4657],{"class":698}," $tokens",[675,4659,3193],{"class":694},[675,4661,1778],{"class":795},[675,4663,4664],{"class":698},"()],\n",[675,4666,4667,4669,4671,4673],{"class":677,"line":872},[675,4668,3903],{"class":728},[675,4670,3508],{"class":694},[675,4672,3908],{"class":728},[675,4674,2137],{"class":698},[675,4676,4677,4680,4682,4684],{"class":677,"line":880},[675,4678,4679],{"class":728},"            CURLOPT_CONNECTTIMEOUT",[675,4681,3508],{"class":694},[675,4683,3920],{"class":728},[675,4685,2137],{"class":698},[675,4687,4688,4690,4692,4695],{"class":677,"line":888},[675,4689,3915],{"class":728},[675,4691,3508],{"class":694},[675,4693,4694],{"class":728}," 30",[675,4696,2137],{"class":698},[675,4698,4699],{"class":677,"line":896},[675,4700,4028],{"class":698},[675,4702,4703,4705,4707,4709,4711,4713,4716],{"class":677,"line":913},[675,4704,4283],{"class":698},[675,4706,1168],{"class":694},[675,4708,801],{"class":698},[675,4710,3100],{"class":694},[675,4712,808],{"class":698},[675,4714,4715],{"class":728},"curl_exec",[675,4717,4052],{"class":698},[675,4719,4720,4722,4724,4726,4728,4730,4732,4734,4736],{"class":677,"line":929},[675,4721,4296],{"class":698},[675,4723,1168],{"class":694},[675,4725,801],{"class":698},[675,4727,4124],{"class":694},[675,4729,808],{"class":698},[675,4731,4307],{"class":728},[675,4733,4310],{"class":698},[675,4735,4313],{"class":728},[675,4737,842],{"class":698},[675,4739,4740,4742],{"class":677,"line":934},[675,4741,4072],{"class":728},[675,4743,4052],{"class":698},[675,4745,4746,4748,4750,4752,4754,4756,4759,4761,4763],{"class":677,"line":973},[675,4747,2933],{"class":694},[675,4749,4362],{"class":698},[675,4751,2029],{"class":694},[675,4753,2022],{"class":728},[675,4755,3328],{"class":694},[675,4757,4758],{"class":698}," $attempt ",[675,4760,2029],{"class":694},[675,4762,1912],{"class":728},[675,4764,1240],{"class":698},[675,4766,4767,4770,4772,4774,4777],{"class":677,"line":1004},[675,4768,4769],{"class":698},"            $tokens",[675,4771,3193],{"class":694},[675,4773,2059],{"class":795},[675,4775,4776],{"class":698},"(); ",[675,4778,4779],{"class":681},"\u002F\u002F sign a fresh token and try once more\n",[675,4781,4782,4785],{"class":677,"line":1023},[675,4783,4784],{"class":694},"            continue",[675,4786,709],{"class":698},[675,4788,4789],{"class":677,"line":1029},[675,4790,2980],{"class":698},[675,4792,4793,4795,4797,4799,4801],{"class":677,"line":1067},[675,4794,2933],{"class":694},[675,4796,4362],{"class":698},[675,4798,1042],{"class":694},[675,4800,4367],{"class":728},[675,4802,1240],{"class":698},[675,4804,4805,4807,4809,4811,4813,4816,4819,4822,4824,4826],{"class":677,"line":1083},[675,4806,2964],{"class":694},[675,4808,1010],{"class":694},[675,4810,4399],{"class":728},[675,4812,819],{"class":698},[675,4814,4815],{"class":705},"\"GET ",[675,4817,4818],{"class":698},"$path",[675,4820,4821],{"class":705}," answered ",[675,4823,4407],{"class":698},[675,4825,3604],{"class":705},[675,4827,842],{"class":698},[675,4829,4830],{"class":677,"line":1088},[675,4831,2980],{"class":698},[675,4833,4834,4836,4838,4840,4842,4844,4846],{"class":677,"line":1127},[675,4835,3119],{"class":694},[675,4837,4341],{"class":728},[675,4839,4344],{"class":698},[675,4841,3626],{"class":728},[675,4843,808],{"class":698},[675,4845,995],{"class":694},[675,4847,4848],{"class":698}," [];\n",[675,4850,4851],{"class":677,"line":1154},[675,4852,1026],{"class":698},[675,4854,4855],{"class":677,"line":1159},[675,4856,1642],{"class":698},[675,4858,4859],{"class":677,"line":1174},[675,4860,716],{"emptyLinePlaceholder":715},[675,4862,4863,4866,4868,4870,4873,4875],{"class":677,"line":1187},[675,4864,4865],{"class":698},"$account ",[675,4867,1168],{"class":694},[675,4869,4573],{"class":795},[675,4871,4872],{"class":698},"($tokens, ",[675,4874,2447],{"class":705},[675,4876,842],{"class":698},[675,4878,4879,4882,4885,4888,4891,4894,4896],{"class":677,"line":1200},[675,4880,4881],{"class":728},"echo",[675,4883,4884],{"class":705}," \"API account {",[675,4886,4887],{"class":698},"$account",[675,4889,4890],{"class":705},"['companyName']}",[675,4892,4893],{"class":694},"\\n",[675,4895,3604],{"class":705},[675,4897,709],{"class":698},[367,4899,4901],{"id":4900},"clock-drift","Clock drift",[358,4903,4904,4905,4907,4908,4910],{},"Your token's ",[362,4906,442],{}," and ",[362,4909,457],{}," come from your server's clock, and the API checks them against its own.\nIt allows 30 seconds of disagreement in either direction:",[372,4912,4913,4923],{},[375,4914,4915],{},[378,4916,4917,4920],{},[381,4918,4919],{},"Your clock is",[381,4921,4922],{},"Effect",[388,4924,4925,4933,4952],{},[378,4926,4927,4930],{},[393,4928,4929],{},"Right, or off by a few seconds",[393,4931,4932],{},"Nothing",[378,4934,4935,4941],{},[393,4936,4937,4938],{},"More than 30 seconds ",[447,4939,4940],{},"fast",[393,4942,4943,4945,4946,4949,4950],{},[362,4944,442],{}," looks like the future. ",[447,4947,4948],{},"Every token is refused"," with ",[362,4951,656],{},[378,4953,4954,4959],{},[393,4955,4956],{},[447,4957,4958],{},"Slow",[393,4960,4961,4962],{},"Tokens expire earlier in real time, by the amount you are slow. Renewing 60 seconds early plus the 30-second tolerance covers up to 90 seconds; beyond that, requests near the end of each token's life get ",[362,4963,656],{},[358,4965,4966,4967,4970,4971,4974],{},"Keep your servers synchronized with NTP. As a safety net, both classes above can measure the\ndifference from the ",[362,4968,4969],{},"Date"," header of ",[362,4972,4973],{},"GET \u002Fv1\u002Fhealth",", which needs no token and doesn't count against your rate limit,\nand then correct every token they sign. Log a warning when the difference exceeds 10 seconds, because\na drifting clock affects more than this API.",[367,4976,4978],{"id":4977},"revoking-a-token","Revoking a token",[358,4980,4981,4984,4985,4988],{},[362,4982,4983],{},"POST \u002Fv1\u002Fapi-account\u002Frevoke-token"," revokes ",[447,4986,4987],{},"the token it is sent with",". Revoke a token:",[625,4990,4991,4994,4997],{},[628,4992,4993],{},"when a long-running process shuts down (good hygiene, not a requirement);",[628,4995,4996],{},"when a token has been exposed: pasted into a ticket, printed in a log, left in a crash dump;",[628,4998,4999],{},"when you gave a token to a separate process and that process is finished.",[595,5001,5006],{"className":5002,"code":5003,"filename":5004,"language":5005,"meta":602,"style":602},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast","curl -s -X POST https:\u002F\u002Fapi.main-team.org\u002Fv1\u002Fapi-account\u002Frevoke-token \\\n  -H \"Authorization: Bearer $TOKEN\"\n","curl","bash",[362,5007,5008,5027],{"__ignoreMap":602},[675,5009,5010,5012,5015,5018,5021,5024],{"class":677,"line":678},[675,5011,5004],{"class":804},[675,5013,5014],{"class":728}," -s",[675,5016,5017],{"class":728}," -X",[675,5019,5020],{"class":705}," POST",[675,5022,5023],{"class":705}," https:\u002F\u002Fapi.main-team.org\u002Fv1\u002Fapi-account\u002Frevoke-token",[675,5025,5026],{"class":694}," \\\n",[675,5028,5029,5032,5035,5038],{"class":677,"line":685},[675,5030,5031],{"class":728},"  -H",[675,5033,5034],{"class":705}," \"Authorization: Bearer ",[675,5036,5037],{"class":698},"$TOKEN",[675,5039,5040],{"class":705},"\"\n",[595,5042,5046],{"className":5043,"code":5044,"filename":5045,"language":2189,"meta":602,"style":602},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast","{\n  \"success\": true,\n  \"message\": \"Token revoked successfully\",\n  \"data\": { \"expiresIn\": 812 }\n}\n","Response 200",[362,5047,5048,5052,5065,5077,5096],{"__ignoreMap":602},[675,5049,5050],{"class":677,"line":678},[675,5051,2786],{"class":698},[675,5053,5054,5058,5061,5063],{"class":677,"line":685},[675,5055,5057],{"class":5056},"sne4z","  \"success\"",[675,5059,5060],{"class":698},": ",[675,5062,3626],{"class":728},[675,5064,2137],{"class":698},[675,5066,5067,5070,5072,5075],{"class":677,"line":691},[675,5068,5069],{"class":5056},"  \"message\"",[675,5071,5060],{"class":698},[675,5073,5074],{"class":705},"\"Token revoked successfully\"",[675,5076,2137],{"class":698},[675,5078,5079,5082,5085,5088,5090,5093],{"class":677,"line":712},[675,5080,5081],{"class":5056},"  \"data\"",[675,5083,5084],{"class":698},": { ",[675,5086,5087],{"class":5056},"\"expiresIn\"",[675,5089,5060],{"class":698},[675,5091,5092],{"class":728},"812",[675,5094,5095],{"class":698}," }\n",[675,5097,5098],{"class":677,"line":719},[675,5099,1642],{"class":698},[625,5101,5102,5108,5114,5117,5136],{},[628,5103,5104,5105,5107],{},"From that moment the token answers ",[362,5106,656],{}," on every request.",[628,5109,5110,5113],{},[362,5111,5112],{},"expiresIn"," is how many seconds the revocation is held: the token's remaining lifetime plus the\n30-second tolerance. After that the token would be refused as expired anyway.",[628,5115,5116],{},"Revoking one token affects nothing else. Your account, your secret and every other token keep\nworking, and you can sign a new token straight away.",[628,5118,5119,5120,5123,5124,5127,5128,5131,5132,5135],{},"Revoking needs the ",[362,5121,5122],{},"api\u002F*"," permission on ",[362,5125,5126],{},"mto",", the same one ",[362,5129,5130],{},"validate-me"," needs. Without it the\nanswer is ",[362,5133,5134],{},"403",", so ask your operator for it before you depend on revocation.",[628,5137,5138,5139,4949,5142,5145,5146,5149],{},"A ",[362,5140,5141],{},"400 bad_request",[362,5143,5144],{},"No token provided."," or ",[362,5147,5148],{},"Invalid token format."," means the token could not be\nread. You will not see it for a token the API has just accepted.",[5151,5152,5154],"callout",{"type":5153},"note",[358,5155,5156,5157,5160],{},"Revocation works on one token, not on the account. To stop ",[447,5158,5159],{},"every"," token at once you need the account\ndeactivated, which only an operator can do.",[367,5162,5164],{"id":5163},"when-the-secret-itself-leaks","When the secret itself leaks",[358,5166,5167,5168,5170],{},"If your ",[362,5169,364],{}," is exposed, revoking tokens does not help: whoever holds the secret can sign new\nones.",[5172,5173,5174,5188,5197],"ol",{},[628,5175,5176,5179,5180,5183,5184,5187],{},[447,5177,5178],{},"Contact your operator at once"," (or ",[526,5181,5182],{"href":346},"support"," at ",[362,5185,5186],{},"info@main-team.org",") and ask for\nthe account to be deactivated. Every token signed with the secret stops working within 60 seconds.",[628,5189,5190,5193,5194,5196],{},[447,5191,5192],{},"Plan the replacement with them."," Secrets cannot be rotated; a new secret comes with a new account\nand a new ",[362,5195,415],{},". Students belong to the account that registered them, so a new account does not\nsee the old account's students. Agree how to handle that before the new account is issued.",[628,5198,5199,5202],{},[447,5200,5201],{},"Find out how it leaked"," before you install the new secret in the same place.",[5151,5204,5206],{"type":5205},"security",[358,5207,5208,5209,5212],{},"Keep the secret where only your server-side code can read it: a secret manager, or an environment\nvariable set by your deployment. Never commit it, never put it in a browser or a mobile app, and never\nwrite it to a log. Redact the ",[362,5210,5211],{},"Authorization"," header in your request logs too: a token is a credential\nfor as long as it lives.",[367,5214,5216],{"id":5215},"several-servers","Several servers",[625,5218,5219,5225,5233,5239],{},[628,5220,5221,5224],{},[447,5222,5223],{},"Each server may sign its own tokens"," with the same key and secret. The API needs no shared state\nfrom you: any correctly signed token is accepted wherever it was made.",[628,5226,5227,5230,5231,825],{},[447,5228,5229],{},"The rate limit is shared."," It counts per account and per operation (100 requests in 60 seconds), not\nper server, so adding servers does not add capacity. Coordinate heavy jobs. See\n",[526,5232,45],{"href":46},[628,5234,5235,5238],{},[447,5236,5237],{},"Give separate workers separate tokens",", so you can revoke one worker's token without touching the\nothers.",[628,5240,5241,5244],{},[447,5242,5243],{},"Consider one signing service."," If many internal services call the API, let one small service hold\nthe secret and hand out short-lived tokens to the others. The secret then lives on one machine\ninstead of many.",[358,5246,5247],{},"Role changes and deactivations made by an operator reach the API within 60 seconds, whichever server\nyou call from.",[367,5249,5251],{"id":5250},"when-you-get-a-401","When you get a 401",[358,5253,5254,5255,5258],{},"Retry once with a freshly signed token. If that also fails, work through this list. The server logs the\nreal reason against the ",[362,5256,5257],{},"request_id"," in the error body, so quote the id if you ask for help.",[625,5260,5263,5278,5285,5298,5314,5324,5342,5351,5360,5367,5374],{"className":5261},[5262],"contains-task-list",[628,5264,5267,5272,5273,5275,5276,825],{"className":5265},[5266],"task-list-item",[5268,5269],"input",{"disabled":715,"type":5270,"ariaLabel":5271},"checkbox","The key and secret are the right way round: the key starts with key_, the secret with secret_."," The key and secret are the right way round: the key starts with ",[362,5274,419],{},", the secret with\n",[362,5277,502],{},[628,5279,5281,5284],{"className":5280},[5266],[5268,5282],{"disabled":715,"type":5270,"ariaLabel":5283},"Neither value is truncated or has whitespace or quotes around it from your configuration."," Neither value is truncated or has whitespace or quotes around it from your configuration.",[628,5286,5288,5291,5292,5060,5294,5297],{"className":5287},[5266],[5268,5289],{"disabled":715,"type":5270,"ariaLabel":5290},"The header reads exactly Authorization: Bearer \u003Ctoken>: Bearer capitalized, then one space."," The header reads exactly ",[362,5293,513],{},[362,5295,5296],{},"Bearer"," capitalized, then one space.",[628,5299,5301,5304,5305,4907,5308,5311,5312,825],{"className":5300},[5266],[5268,5302],{"disabled":715,"type":5270,"ariaLabel":5303},"The header has \"alg\": \"HS256\" and \"kid\" set to the key. Some libraries need an explicit option for kid."," The header has ",[362,5306,5307],{},"\"alg\": \"HS256\"",[362,5309,5310],{},"\"kid\""," set to the key. Some libraries need an explicit\noption for ",[362,5313,409],{},[628,5315,5317,5320,5321,5323],{"className":5316},[5266],[5268,5318],{"disabled":715,"type":5270,"ariaLabel":5319},"The payload has sub equal to the key."," The payload has ",[362,5322,428],{}," equal to the key.",[628,5325,5327,5330,5331,4907,5333,5335,5336,5338,5339,5341],{"className":5326},[5266],[5268,5328],{"disabled":715,"type":5270,"ariaLabel":5329},"iat and exp are both present and in seconds. Milliseconds make the lifetime look enormous and iat look like the far future."," ",[362,5332,442],{},[362,5334,457],{}," are both present and in ",[447,5337,449],{},". Milliseconds make the lifetime look enormous\nand ",[362,5340,442],{}," look like the far future.",[628,5343,5345,5330,5348,5350],{"className":5344},[5266],[5268,5346],{"disabled":715,"type":5270,"ariaLabel":5347},"exp - iat is 3600 or less.",[362,5349,472],{}," is 3600 or less.",[628,5352,5354,5357,5358,825],{"className":5353},[5266],[5268,5355],{"disabled":715,"type":5270,"ariaLabel":5356},"Your clock is within 30 seconds of real time. Compare it with GET \u002Fv1\u002Fhealth."," Your clock is within 30 seconds of real time. Compare it with ",[362,5359,4973],{},[628,5361,5363,5366],{"className":5362},[5266],[5268,5364],{"disabled":715,"type":5270,"ariaLabel":5365},"You did not revoke this token earlier."," You did not revoke this token earlier.",[628,5368,5370,5373],{"className":5369},[5266],[5268,5371],{"disabled":715,"type":5270,"ariaLabel":5372},"The account is still active. An operator may have deactivated it."," The account is still active. An operator may have deactivated it.",[628,5375,5377,5380,5381,5383,5384,5386,5387,825],{"className":5376},[5266],[5268,5378],{"disabled":715,"type":5270,"ariaLabel":5379},"You are calling the environment the account belongs to. Sandbox accounts are separate: a sandbox key gets 401 on production, and a production key gets 401 on the sandbox. See Environments."," You are calling the environment the account belongs to. Sandbox accounts are separate: a\nsandbox key gets ",[362,5382,656],{}," on production, and a production key gets ",[362,5385,656],{}," on the sandbox. See\n",[526,5388,16],{"href":5389},"\u002Fapi\u002Fenvironments#sandbox",[358,5391,5392,5395,5396,825],{},[362,5393,5394],{},"403 forbidden"," is not a token problem: the token was accepted and your account lacks a permission.\nSee ",[526,5397,25],{"href":26},[367,5399,5401],{"id":5400},"check-your-setup-with-validate-me","Check your setup with validate-me",[358,5403,5404,5407],{},[362,5405,5406],{},"GET \u002Fv1\u002Fapi-account\u002Fvalidate-me"," returns your account as the API sees it. That makes it a good\nstart-up check and an easy thing to put on a status page:",[595,5409,5411],{"className":5002,"code":5410,"filename":5004,"language":5005,"meta":602,"style":602},"curl -s https:\u002F\u002Fapi.main-team.org\u002Fv1\u002Fapi-account\u002Fvalidate-me \\\n  -H \"Authorization: Bearer $TOKEN\"\n",[362,5412,5413,5424],{"__ignoreMap":602},[675,5414,5415,5417,5419,5422],{"class":677,"line":678},[675,5416,5004],{"class":804},[675,5418,5014],{"class":728},[675,5420,5421],{"class":705}," https:\u002F\u002Fapi.main-team.org\u002Fv1\u002Fapi-account\u002Fvalidate-me",[675,5423,5026],{"class":694},[675,5425,5426,5428,5430,5432],{"class":677,"line":685},[675,5427,5031],{"class":728},[675,5429,5034],{"class":705},[675,5431,5037],{"class":698},[675,5433,5040],{"class":705},[595,5435,5437],{"className":5043,"code":5436,"filename":5045,"language":2189,"meta":602,"style":602},"{\n  \"_id\": \"66d0c2f4a1b2c3d4e5f60718\",\n  \"apiKey\": \"key_Q2xpZW50RXhhbXBsZUtleTAx\",\n  \"companyName\": \"Example Schools Ltd\",\n  \"scopes\": [],\n  \"roles\": [\n    { \"effect\": \"allow\", \"action\": \"api\u002F*\", \"target\": \"mto\" },\n    { \"effect\": \"allow\", \"action\": \"*\u002Fread\", \"target\": \"*\" }\n  ],\n  \"isActive\": true\n}\n",[362,5438,5439,5443,5455,5467,5479,5487,5495,5530,5560,5565,5575],{"__ignoreMap":602},[675,5440,5441],{"class":677,"line":678},[675,5442,2786],{"class":698},[675,5444,5445,5448,5450,5453],{"class":677,"line":685},[675,5446,5447],{"class":5056},"  \"_id\"",[675,5449,5060],{"class":698},[675,5451,5452],{"class":705},"\"66d0c2f4a1b2c3d4e5f60718\"",[675,5454,2137],{"class":698},[675,5456,5457,5460,5462,5465],{"class":677,"line":691},[675,5458,5459],{"class":5056},"  \"apiKey\"",[675,5461,5060],{"class":698},[675,5463,5464],{"class":705},"\"key_Q2xpZW50RXhhbXBsZUtleTAx\"",[675,5466,2137],{"class":698},[675,5468,5469,5472,5474,5477],{"class":677,"line":712},[675,5470,5471],{"class":5056},"  \"companyName\"",[675,5473,5060],{"class":698},[675,5475,5476],{"class":705},"\"Example Schools Ltd\"",[675,5478,2137],{"class":698},[675,5480,5481,5484],{"class":677,"line":719},[675,5482,5483],{"class":5056},"  \"scopes\"",[675,5485,5486],{"class":698},": [],\n",[675,5488,5489,5492],{"class":677,"line":740},[675,5490,5491],{"class":5056},"  \"roles\"",[675,5493,5494],{"class":698},": [\n",[675,5496,5497,5500,5503,5505,5508,5510,5513,5515,5518,5520,5523,5525,5528],{"class":677,"line":770},[675,5498,5499],{"class":698},"    { ",[675,5501,5502],{"class":5056},"\"effect\"",[675,5504,5060],{"class":698},[675,5506,5507],{"class":705},"\"allow\"",[675,5509,945],{"class":698},[675,5511,5512],{"class":5056},"\"action\"",[675,5514,5060],{"class":698},[675,5516,5517],{"class":705},"\"api\u002F*\"",[675,5519,945],{"class":698},[675,5521,5522],{"class":5056},"\"target\"",[675,5524,5060],{"class":698},[675,5526,5527],{"class":705},"\"mto\"",[675,5529,1987],{"class":698},[675,5531,5532,5534,5536,5538,5540,5542,5544,5546,5549,5551,5553,5555,5558],{"class":677,"line":785},[675,5533,5499],{"class":698},[675,5535,5502],{"class":5056},[675,5537,5060],{"class":698},[675,5539,5507],{"class":705},[675,5541,945],{"class":698},[675,5543,5512],{"class":5056},[675,5545,5060],{"class":698},[675,5547,5548],{"class":705},"\"*\u002Fread\"",[675,5550,945],{"class":698},[675,5552,5522],{"class":5056},[675,5554,5060],{"class":698},[675,5556,5557],{"class":705},"\"*\"",[675,5559,5095],{"class":698},[675,5561,5562],{"class":677,"line":790},[675,5563,5564],{"class":698},"  ],\n",[675,5566,5567,5570,5572],{"class":677,"line":845},[675,5568,5569],{"class":5056},"  \"isActive\"",[675,5571,5060],{"class":698},[675,5573,5574],{"class":728},"true\n",[675,5576,5577],{"class":677,"line":850},[675,5578,1642],{"class":698},[358,5580,5581,5582,5585,5586,825],{},"The account comes back bare, without the ",[362,5583,5584],{},"{ success, message, data }"," envelope every other JSON\nresponse has. The secret is never included. See ",[526,5587,77],{"href":78},[367,5589,5591],{"id":5590},"checklist","Checklist",[625,5593,5595,5602,5619,5628,5638,5645,5652],{"className":5594},[5262],[628,5596,5598,5601],{"className":5597},[5266],[5268,5599],{"disabled":715,"type":5270,"ariaLabel":5600},"The secret is read from a secret store or the environment, and never logged."," The secret is read from a secret store or the environment, and never logged.",[628,5603,5605,5608,5609,4907,5611,5613,5614,4907,5616,5618],{"className":5604},[5266],[5268,5606],{"disabled":715,"type":5270,"ariaLabel":5607},"Tokens are signed on your server with HS256, kid and sub set to the key, iat and exp in seconds."," Tokens are signed on your server with HS256, ",[362,5610,409],{},[362,5612,428],{}," set to the key, ",[362,5615,442],{},[362,5617,457],{}," in\nseconds.",[628,5620,5622,5625,5626,825],{"className":5621},[5266],[5268,5623],{"disabled":715,"type":5270,"ariaLabel":5624},"The lifetime is 3600 seconds or less, and tokens are renewed about a minute before exp."," The lifetime is 3600 seconds or less, and tokens are renewed about a minute before ",[362,5627,457],{},[628,5629,5631,5634,5635,5637],{"className":5630},[5266],[5268,5632],{"disabled":715,"type":5270,"ariaLabel":5633},"A 401 triggers one retry with a fresh token, then an alert."," A ",[362,5636,656],{}," triggers one retry with a fresh token, then an alert.",[628,5639,5641,5644],{"className":5640},[5266],[5268,5642],{"disabled":715,"type":5270,"ariaLabel":5643},"Servers keep their clocks synchronized, and a check warns when one drifts."," Servers keep their clocks synchronized, and a check warns when one drifts.",[628,5646,5648,5651],{"className":5647},[5266],[5268,5649],{"disabled":715,"type":5270,"ariaLabel":5650},"Long-running processes revoke their token on shutdown."," Long-running processes revoke their token on shutdown.",[628,5653,5655,5658],{"className":5654},[5266],[5268,5656],{"disabled":715,"type":5270,"ariaLabel":5657},"You know whom to call to deactivate the account if the secret leaks."," You know whom to call to deactivate the account if the secret leaks.",[367,5660,5662],{"id":5661},"next-steps","Next steps",[625,5664,5665,5670,5675,5681],{},[628,5666,5667,5669],{},[526,5668,19],{"href":20},": the concepts behind this page.",[628,5671,5672,5674],{},[526,5673,51],{"href":52},": secrets, sign-in links and students' data.",[628,5676,5677,5680],{},[526,5678,5679],{"href":315},"Build your own client",": the other facts a client needs.",[628,5682,5683,5684,2137,5687,945,5690,825],{},"Reference: ",[526,5685,5686],{"href":144},"getCurrentApiAccount",[526,5688,5689],{"href":140},"revokeToken",[526,5691,5692],{"href":132},"getHealth",[5694,5695,5696],"style",{},"html pre.shiki code .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}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 .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}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 .soyes, html code.shiki .soyes{--shiki-default:#702C00;--shiki-dark:#FFB757}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}",{"title":602,"searchDepth":685,"depth":691,"links":5698},[5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711],{"id":369,"depth":685,"text":370},{"id":533,"depth":685,"text":534},{"id":622,"depth":685,"text":623},{"id":664,"depth":685,"text":665},{"id":2703,"depth":685,"text":2704},{"id":4900,"depth":685,"text":4901},{"id":4977,"depth":685,"text":4978},{"id":5163,"depth":685,"text":5164},{"id":5215,"depth":685,"text":5216},{"id":5250,"depth":685,"text":5251},{"id":5400,"depth":685,"text":5401},{"id":5590,"depth":685,"text":5591},{"id":5661,"depth":685,"text":5662},"Cache and renew your signed tokens, survive clock drift, run several servers, and revoke tokens on shutdown or after a leak. Node.js and PHP.","md",{},[5686,5689,5692],"50",{"title":353,"description":5712},"api\u002Ftutorials\u002Ftoken-handling","ybCU4oz6Kou7aAJuZ7yjR2pSEH3By3EFF4B8K5_fm_s",[5721,5726,5729],{"operationId":5686,"slug":5722,"method":133,"path":5723,"tag":77,"summary":143,"deprecated":134,"public":134,"permission":5724,"scope":5725,"order":691},"get-current-api-account","\u002Fv1\u002Fapi-account\u002Fvalidate-me","api\u002F*:$org:$ID","flat",{"operationId":5689,"slug":5727,"method":141,"path":5728,"tag":77,"summary":139,"deprecated":134,"public":134,"permission":5724,"scope":5725,"order":685},"revoke-token","\u002Fv1\u002Fapi-account\u002Frevoke-token",{"operationId":5692,"slug":5730,"method":133,"path":5731,"tag":127,"summary":131,"deprecated":134,"public":715,"permission":5732,"scope":5733,"order":678},"get-health","\u002Fv1\u002Fhealth",null,"public",1791554615314]