[{"data":1,"prerenderedAt":1703},["ShallowReactive",2],{"page-\u002Fapi":3},{"id":4,"title":5,"body":6,"description":1696,"extension":1697,"meta":1698,"navigation":1470,"path":1699,"scene":250,"seo":1700,"stem":1701,"__hash__":1702},"pages\u002Fapi.md","API",{"type":7,"value":8,"toc":1678},"minimark",[9,14,18,91,102,106,119,132,135,142,157,161,166,220,226,304,312,316,323,330,340,351,359,363,370,500,514,519,527,535,577,584,588,594,691,694,703,709,713,719,785,793,797,806,812,818,821,825,832,874,877,880,884,890,1051,1054,1088,1095,1103,1108,1122,1125,1136,1140,1151,1283,1290,1296,1300,1303,1337,1341,1413,1419,1447,1451,1454,1653,1659,1662,1674],[10,11,13],"h2",{"id":12},"the-routes-a-key-opens","The routes a key opens",[15,16,17],"p",{},"Your tools call eight routes. Everything else this server answers is a page for a person in a browser.",[19,20,21,33,39,45,51,57,63,69],"ul",{},[22,23,24,28,29,32],"li",{},[25,26,27],"code",{},"POST \u002Fapi\u002Fv1\u002Fscans"," opens a case. The rules are the dashboard's: a staff role, an install you own, a game your plan covers. It answers ",[25,30,31],{},"201"," with the case id. It also carries, once, the key the scanned player uses to read their own case.",[22,34,35,38],{},[25,36,37],{},"GET \u002Fapi\u002Fv1\u002Fscans\u002F{case}"," reads the case back. The state, the session code once it is issued, when that session was issued, the launcher download for it, the count of findings, and the decision. No finding detail. Detail is read in the dashboard, where every read is on the record.",[22,40,41,44],{},[25,42,43],{},"GET \u002Fapi\u002Fv1\u002Fscans"," lists the cases nobody has decided yet, newest first. It is the reconcile a program does after a restart: what is still yours to chase.",[22,46,47,50],{},[25,48,49],{},"POST \u002Fapi\u002Fv1\u002Fscans\u002F{case}\u002Fwithdraw"," closes a case somebody opened in error.",[22,52,53,56],{},[25,54,55],{},"POST \u002Fapi\u002Fv1\u002Fscans\u002F{case}\u002Faccess"," hands out a fresh key for the scanned player, for the case whose first one never reached them. The old one stops working in the same breath.",[22,58,59,62],{},[25,60,61],{},"GET \u002Fapi\u002Fv1\u002Finstalls"," lists the machines your community owns, which is what a new case names.",[22,64,65,68],{},[25,66,67],{},"GET \u002Fapi\u002Fv1\u002Fwhoami"," says who your key is: the community, the role, the games it may scan, and the trial if one is running. It also says whether appeal outcomes already go to your webhook.",[22,70,71,74,75,78,79,82,83,86,87,90],{},[25,72,73],{},"GET \u002Fapi\u002Fv1\u002Fplayers\u002F{player}\u002Fscans"," lists the cases one player was given a code for in your league events, newest first. Each one says its state and its decision, and nothing more. ",[25,76,77],{},"{player}"," is the player key, and you work it out yourself. Take the player's Discord id or email address. Trim the spaces off the ends and turn A to Z into a to z. Hash three things in a row with SHA-512: the bytes ",[25,80,81],{},"glimmer.league.player.v1",", then the ",[25,84,85],{},"tenant"," number ",[25,88,89],{},"whoami"," gives you as 8 bytes with the lowest byte first, then that text. The key is the first 16 bytes of the hash, written as 32 lowercase hexadecimal characters. A case opened outside an event has no player key, so it is not in the list. A key from another community lists nothing.",[15,92,93,94,97,98,101],{},"There is no ninth route for you. ",[25,95,96],{},"POST \u002Fapi\u002Fv1\u002Fbilling\u002Fpay"," exists, and it is Connections Pay's. It is opened by Pay's signature and no API key opens it. It records a paid plan. ",[25,99,100],{},"POST \u002Fapi\u002Fv1\u002Fdiscord\u002Fbind"," exists too, and it is our own Discord bot's. It is opened by the secret that application was registered with, and no API key opens it either. It is how a bot deployment is handed a community's key without a person pasting one through a chat window.",[10,103,105],{"id":104},"the-key","The key",[15,107,108,109,114,115,118],{},"An owner mints a key in the ",[110,111,113],"a",{"href":112},"\u002Foperator\u002Ftenant","staff dashboard"," for one named staff member. It is shown once and stored nowhere. There is no second look. It acts as that person and can never do more than they can. Revoking it takes effect on the next request. Only a plan that includes the API mints one. On any other plan the mint is refused with the code ",[25,116,117],{},"no-api-on-plan",".",[15,120,121,122,124,125,128,129,131],{},"Every plan still gets the plugin package and our Discord bot, and each carries a key. On a plan without the API, that key reaches three routes: opening a case, reading one back, and ",[25,123,89],{},". Those three are all the plugins and the bot call. Every other route answers ",[25,126,127],{},"403"," with the code ",[25,130,117],{},". This is decided at each request, from the plan your community holds at that moment. A plan that adds the API opens every route to every key your community holds, and a plan that ends closes them again.",[15,133,134],{},"That same page lists every key your community was ever issued, the retired ones included, with the day each was minted and who it acts as. An owner retires one there with a button. A key that is out there with nobody holding it is the thing worth ending, and until you can see the list you cannot know there is one.",[15,136,137,138,141],{},"The key is 32 random bytes written as 64 lowercase hexadecimal characters. It travels in one header: ",[25,139,140],{},"Authorization: Bearer \u003Cthe 64 characters>",". There is no cookie, no form token and no sign-in step. The key is the whole credential, so it deserves the care a password gets. A wrong key is counted against the place it came from. After twenty-four wrong keys in five minutes, that place is refused until the five minutes pass, whatever key it sends. A key that works is never counted, so a plugin polling a case is never slowed by its own polling.",[15,143,144,145,148,149,152,153,156],{},"That last sentence is a promise, not an implementation detail. There is no success-path limit: a key that works is never counted against the failure budget, however often it calls. A working key sees ",[25,146,147],{},"attempts-exhausted"," only while wrong keys from its own address have spent that budget. A second plugin on the same host with a stale key can do that. The other ",[25,150,151],{},"429"," is ",[25,154,155],{},"read-slot-busy",". It says this server is reading for somebody else right now, and the remedy is to send the same request again.",[10,158,160],{"id":159},"opening-a-case","Opening a case",[15,162,163,165],{},[25,164,27],{}," takes JSON. A field this page does not name is refused, not ignored. The whole body is at most 32 KiB.",[19,167,168,174,180,194,214],{},[22,169,170,173],{},[25,171,172],{},"install"," is the install's number, exactly as the dashboard shows it. It must be one your community owns. Without it, the case enrols a machine nobody has scanned here before, and the scan binds that install to you.",[22,175,176,179],{},[25,177,178],{},"target"," is the player, spelled however you name players. 1 to 512 bytes, and not blank.",[22,181,182,185,186,189,190,193],{},[25,183,184],{},"profile"," is the game. 1 to 64 characters of letters, digits, ",[25,187,188],{},"-"," and ",[25,191,192],{},"_",". Your plan must cover it, and this server must serve a scan profile of that name.",[22,195,196,199,200,203,204,203,207,210,211,118],{},[25,197,198],{},"category"," is exactly one of ",[25,201,202],{},"reported",", ",[25,205,206],{},"observed",[25,208,209],{},"routine"," or ",[25,212,213],{},"linked",[22,215,216,219],{},[25,217,218],{},"reason"," is why, in words, for the record and for the appeal. Not blank, and at most 4096 bytes.",[15,221,222,223,225],{},"The answer is ",[25,224,31],{},":",[227,228,233],"pre",{"className":229,"code":230,"language":231,"meta":232,"style":232},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\"case\": \"8f14e45fce0a4b0d9f2a1c3e5d7b6a80\", \"state\": \"queued\", \"accused_capability\": \"0b7d4c2f9a1e63508d4a7b2c1e9f0356a8d2b4c6e0f19375426ab8c0d2e4f6a1\"}\n","json","",[25,234,235],{"__ignoreMap":232},[236,237,240,244,247,251,253,255,258,262,264,267,269,272,274,276,278,281,283,285,287,290,292,294,296,299,301],"span",{"class":238,"line":239},"line",1,[236,241,243],{"class":242},"sMK4o","{",[236,245,246],{"class":242},"\"",[236,248,250],{"class":249},"spNyl","case",[236,252,246],{"class":242},[236,254,225],{"class":242},[236,256,257],{"class":242}," \"",[236,259,261],{"class":260},"sfazB","8f14e45fce0a4b0d9f2a1c3e5d7b6a80",[236,263,246],{"class":242},[236,265,266],{"class":242},",",[236,268,257],{"class":242},[236,270,271],{"class":249},"state",[236,273,246],{"class":242},[236,275,225],{"class":242},[236,277,257],{"class":242},[236,279,280],{"class":260},"queued",[236,282,246],{"class":242},[236,284,266],{"class":242},[236,286,257],{"class":242},[236,288,289],{"class":249},"accused_capability",[236,291,246],{"class":242},[236,293,225],{"class":242},[236,295,257],{"class":242},[236,297,298],{"class":260},"0b7d4c2f9a1e63508d4a7b2c1e9f0356a8d2b4c6e0f19375426ab8c0d2e4f6a1",[236,300,246],{"class":242},[236,302,303],{"class":242},"}\n",[15,305,306,308,309,311],{},[25,307,250],{}," is 16 bytes as 32 hexadecimal characters. It names the case everywhere else. ",[25,310,289],{}," is 32 bytes as 64 characters. The two widths differ on purpose, so a mix-up fails rather than half works. The capability is shown this once and never again. It is the scanned player's own login for their case and their appeal. It is the one thing in this answer that goes to them.",[10,313,315],{"id":314},"retrying-a-create-you-never-saw-answered","Retrying a create you never saw answered",[15,317,318,319,322],{},"A create is the one call where a lost answer costs somebody else. The case is open. The capability was minted. The player has no way into their own appeal. So send an ",[25,320,321],{},"Idempotency-Key"," header with it, and a retry is safe.",[15,324,325,326,329],{},"The key is yours. 16 to 128 visible ASCII characters, one per create. A random string is the easy answer. The floor is there because a key has to belong to one create. A caller sending ",[25,327,328],{},"1"," would answer their own second player with the first one's case.",[15,331,332,333,335,336,339],{},"First sight opens the case and answers ",[25,334,31],{},". The same key again, inside a day, answers ",[25,337,338],{},"200",". It carries the case that key already opened, and a fresh capability for it. No route anywhere reads a capability back. A day later the key is free again, and it opens a second case. Without the header, two creates are two cases, which is what they have always been.",[15,341,342,343,346,347,350],{},"A key belongs to the create it opened. The same key sent with a different player, game or machine is refused ",[25,344,345],{},"409 That idempotency key already opened a case for a different scan. A new scan needs a new key.",", code ",[25,348,349],{},"key-reused",". It is not answered with the first create's case. That answer names no player, so nothing in it would tell you it was somebody else's. And the capability in it is that other player's own login for their appeal. The words of your reason are yours to change on a retry. The player, the game and the machine are what the key stands for.",[15,352,353,354,128,356,358],{},"One honest edge, said out loud. If this server dies between recording the case and recording the claim, your retry opens a second case. The other choice was a flag standing in for a record nobody wrote, and this repo does not do that. Close the spare with the withdraw route. That route needs a plan with the API. On any other plan it answers ",[25,355,127],{},[25,357,117],{},", like every route past the three every plan reaches.",[10,360,362],{"id":361},"reading-it-back","Reading it back",[15,364,365,367,368,225],{},[25,366,37],{}," answers ",[25,369,338],{},[227,371,373],{"className":229,"code":372,"language":231,"meta":232,"style":232},"{\"case\": \"8f14e45fce0a4b0d9f2a1c3e5d7b6a80\", \"state\": \"issued\", \"code\": \"…\", \"issued_at\": 1758499200, \"link\": \"https:\u002F\u002Fanticheat.lunarwerx.com\u002Fdownload\u002Fglimmer-….exe\", \"findings\": 0, \"decision\": null, \"open\": true}\n",[25,374,375],{"__ignoreMap":232},[236,376,377,379,381,383,385,387,389,391,393,395,397,399,401,403,405,408,410,412,414,416,418,420,422,425,427,429,431,434,436,438,442,444,446,449,451,453,455,458,460,462,464,467,469,471,474,476,478,481,483,485,488,490,493,495,497],{"class":238,"line":239},[236,378,243],{"class":242},[236,380,246],{"class":242},[236,382,250],{"class":249},[236,384,246],{"class":242},[236,386,225],{"class":242},[236,388,257],{"class":242},[236,390,261],{"class":260},[236,392,246],{"class":242},[236,394,266],{"class":242},[236,396,257],{"class":242},[236,398,271],{"class":249},[236,400,246],{"class":242},[236,402,225],{"class":242},[236,404,257],{"class":242},[236,406,407],{"class":260},"issued",[236,409,246],{"class":242},[236,411,266],{"class":242},[236,413,257],{"class":242},[236,415,25],{"class":249},[236,417,246],{"class":242},[236,419,225],{"class":242},[236,421,257],{"class":242},[236,423,424],{"class":260},"…",[236,426,246],{"class":242},[236,428,266],{"class":242},[236,430,257],{"class":242},[236,432,433],{"class":249},"issued_at",[236,435,246],{"class":242},[236,437,225],{"class":242},[236,439,441],{"class":440},"sbssI"," 1758499200",[236,443,266],{"class":242},[236,445,257],{"class":242},[236,447,448],{"class":249},"link",[236,450,246],{"class":242},[236,452,225],{"class":242},[236,454,257],{"class":242},[236,456,457],{"class":260},"https:\u002F\u002Fanticheat.lunarwerx.com\u002Fdownload\u002Fglimmer-….exe",[236,459,246],{"class":242},[236,461,266],{"class":242},[236,463,257],{"class":242},[236,465,466],{"class":249},"findings",[236,468,246],{"class":242},[236,470,225],{"class":242},[236,472,473],{"class":440}," 0",[236,475,266],{"class":242},[236,477,257],{"class":242},[236,479,480],{"class":249},"decision",[236,482,246],{"class":242},[236,484,225],{"class":242},[236,486,487],{"class":242}," null,",[236,489,257],{"class":242},[236,491,492],{"class":249},"open",[236,494,246],{"class":242},[236,496,225],{"class":242},[236,498,499],{"class":242}," true}\n",[15,501,502,504,505,507,508,510,511,513],{},[25,503,25],{}," is null until the signing worker has issued the session. Its arrival is the thing worth polling for. It is the code the player enters in the client. The launcher from ",[25,506,448],{}," carries it in its file name, so there the player only confirms it. ",[25,509,466],{}," is a count, never the findings themselves. ",[25,512,480],{}," is null until somebody decides.",[15,515,516,518],{},[25,517,492],{}," is true while the case is still waiting on somebody. It turns false the moment a decision is recorded against it, which is the same moment the case leaves the list below, so one fact answers both questions.",[15,520,521,523,524,526],{},[25,522,433],{}," is when that session was issued, in whole seconds since the Unix epoch, taken from the session this server already recorded. It is null for exactly as long as ",[25,525,25],{}," is. Before a session is bound to the case there is nothing to time, and a made-up time is worse than none. Measure a reminder from this, not from the moment your own poll first saw a code.",[15,528,529,531,532,534],{},[25,530,448],{}," is the launcher download for that code, built here so that no client ever assembles the URL for itself. It is null when ",[25,533,25],{}," is, and also when this server serves no launcher of its own.",[15,536,537,538,203,540,203,542,203,545,203,548,203,551,203,554,189,557,560,561,203,564,203,567,189,570,573,574,576],{},"The states are ",[25,539,280],{},[25,541,407],{},[25,543,544],{},"clear",[25,546,547],{},"inconclusive",[25,549,550],{},"evidence-expired",[25,552,553],{},"flagged",[25,555,556],{},"upheld",[25,558,559],{},"withdrawn",". The decisions are ",[25,562,563],{},"action-taken",[25,565,566],{},"no-action",[25,568,569],{},"rescan",[25,571,572],{},"refused",". ",[25,575,572],{}," means the community recorded that the player would not run the scan. It is never a finding. These names are a contract. They are only ever added to.",[15,578,579,189,581,583],{},[25,580,556],{},[25,582,559],{}," are appeal outcomes, decided by the authority, not by a scan. The scan's own outcomes are the six before them. If your community already hears about an appeal through its own webhook, those two are the states to leave alone rather than post twice.",[10,585,587],{"id":586},"listing-what-is-still-open","Listing what is still open",[15,589,590,367,592,225],{},[25,591,43],{},[25,593,338],{},[227,595,597],{"className":229,"code":596,"language":231,"meta":232,"style":232},"{\"scans\": [{\"case\": \"8f14e45fce0a4b0d9f2a1c3e5d7b6a80\", \"state\": \"queued\", \"target\": \"player#0420\", \"opened_at\": 1758499200}], \"next_after\": null}\n",[25,598,599],{"__ignoreMap":232},[236,600,601,603,605,608,610,612,615,617,620,622,624,626,628,630,632,634,636,638,640,642,644,646,648,650,652,654,656,658,661,663,665,667,670,672,674,676,679,681,684,686,688],{"class":238,"line":239},[236,602,243],{"class":242},[236,604,246],{"class":242},[236,606,607],{"class":249},"scans",[236,609,246],{"class":242},[236,611,225],{"class":242},[236,613,614],{"class":242}," [{",[236,616,246],{"class":242},[236,618,250],{"class":619},"sBMFI",[236,621,246],{"class":242},[236,623,225],{"class":242},[236,625,257],{"class":242},[236,627,261],{"class":260},[236,629,246],{"class":242},[236,631,266],{"class":242},[236,633,257],{"class":242},[236,635,271],{"class":619},[236,637,246],{"class":242},[236,639,225],{"class":242},[236,641,257],{"class":242},[236,643,280],{"class":260},[236,645,246],{"class":242},[236,647,266],{"class":242},[236,649,257],{"class":242},[236,651,178],{"class":619},[236,653,246],{"class":242},[236,655,225],{"class":242},[236,657,257],{"class":242},[236,659,660],{"class":260},"player#0420",[236,662,246],{"class":242},[236,664,266],{"class":242},[236,666,257],{"class":242},[236,668,669],{"class":619},"opened_at",[236,671,246],{"class":242},[236,673,225],{"class":242},[236,675,441],{"class":440},[236,677,678],{"class":242},"}],",[236,680,257],{"class":242},[236,682,683],{"class":249},"next_after",[236,685,246],{"class":242},[236,687,225],{"class":242},[236,689,690],{"class":242}," null}\n",[15,692,693],{},"Open means nobody has recorded a decision on it yet, which is the same count your dashboard shows. Newest first, a hundred to a page.",[15,695,696,698,699,702],{},[25,697,683],{}," is a case id while your community has another page, and null at the end. Ask again with ",[25,700,701],{},"?after=\u003Cthat case id>"," and you get the next hundred. Follow it until it is null and you have read everything, without ever fetching an empty page.",[15,704,705,708],{},[25,706,707],{},"?after="," is the only parameter this route takes. Anything else is refused rather than ignored, because a link this server did not write must not be able to change quietly what you are shown.",[10,710,712],{"id":711},"the-machines-you-own","The machines you own",[15,714,715,367,717,225],{},[25,716,61],{},[25,718,338],{},[227,720,722],{"className":229,"code":721,"language":231,"meta":232,"style":232},"{\"installs\": [{\"install\": 1000, \"label\": \"eu-west-1\", \"activated_at\": 1756999200}]}\n",[25,723,724],{"__ignoreMap":232},[236,725,726,728,730,733,735,737,739,741,743,745,747,750,752,754,757,759,761,763,766,768,770,772,775,777,779,782],{"class":238,"line":239},[236,727,243],{"class":242},[236,729,246],{"class":242},[236,731,732],{"class":249},"installs",[236,734,246],{"class":242},[236,736,225],{"class":242},[236,738,614],{"class":242},[236,740,246],{"class":242},[236,742,172],{"class":619},[236,744,246],{"class":242},[236,746,225],{"class":242},[236,748,749],{"class":440}," 1000",[236,751,266],{"class":242},[236,753,257],{"class":242},[236,755,756],{"class":619},"label",[236,758,246],{"class":242},[236,760,225],{"class":242},[236,762,257],{"class":242},[236,764,765],{"class":260},"eu-west-1",[236,767,246],{"class":242},[236,769,266],{"class":242},[236,771,257],{"class":242},[236,773,774],{"class":619},"activated_at",[236,776,246],{"class":242},[236,778,225],{"class":242},[236,780,781],{"class":440}," 1756999200",[236,783,784],{"class":242},"}]}\n",[15,786,787,789,790,792],{},[25,788,172],{}," is the number a new case names. ",[25,791,756],{}," is what your operator called that machine when it was activated: a note for people, never a credential. The install's own signing key is not in this answer and never will be.",[10,794,796],{"id":795},"withdrawing-a-case-opened-in-error","Withdrawing a case opened in error",[15,798,799,801,802,805],{},[25,800,49],{}," takes ",[25,803,804],{},"{\"reason\": \"...\"}",", not blank and at most 4096 bytes, and answers the case exactly as reading it back does.",[15,807,808,809,811],{},"It records what your staff would record in the dashboard: no action, against the person your key acts as, with your reason kept. The case closes, ",[25,810,492],{}," turns false, and it leaves the open list. No session is ever issued for it afterwards, so nobody is asked to scan for a case you took back.",[15,813,814,815,817],{},"It does not set the state to ",[25,816,559],{},". That one is an appeal outcome the authority decides, and this is a case being closed rather than a finding being taken back.",[15,819,820],{},"Send it twice and the second call answers exactly what the first one did, with nothing recorded again. A retry after a timeout is safe.",[10,822,824],{"id":823},"re-handing-the-players-key","Re-handing the player's key",[15,826,827,829,830,225],{},[25,828,55],{}," takes no body and answers ",[25,831,338],{},[227,833,835],{"className":229,"code":834,"language":231,"meta":232,"style":232},"{\"case\": \"8f14e45fce0a4b0d9f2a1c3e5d7b6a80\", \"accused_capability\": \"6b1f2d840a3c95e7182d4c6b0e9f3175a2c4e608b9d1f3527046ac8e20d4f6b3\"}\n",[25,836,837],{"__ignoreMap":232},[236,838,839,841,843,845,847,849,851,853,855,857,859,861,863,865,867,870,872],{"class":238,"line":239},[236,840,243],{"class":242},[236,842,246],{"class":242},[236,844,250],{"class":249},[236,846,246],{"class":242},[236,848,225],{"class":242},[236,850,257],{"class":242},[236,852,261],{"class":260},[236,854,246],{"class":242},[236,856,266],{"class":242},[236,858,257],{"class":242},[236,860,289],{"class":249},[236,862,246],{"class":242},[236,864,225],{"class":242},[236,866,257],{"class":242},[236,868,869],{"class":260},"6b1f2d840a3c95e7182d4c6b0e9f3175a2c4e608b9d1f3527046ac8e20d4f6b3",[236,871,246],{"class":242},[236,873,303],{"class":242},[15,875,876],{},"It is for the case whose capability never reached the player. Your program stored nothing, or the message went to somebody who had left. One transaction drops the key the case had and commits the new one. So a case never holds two live keys. The copy somebody lost stops opening the appeal the moment this answers. The record says a capability was re-minted, by whom and when. The case itself is unchanged.",[15,878,879],{},"There is no route that reads a capability back. A credential a server can hand over twice is a credential a stolen backup hands over. Re-minting is the only path there is, and this is it.",[10,881,883],{"id":882},"who-your-key-is","Who your key is",[15,885,886,367,888,225],{},[25,887,67],{},[25,889,338],{},[227,891,893],{"className":229,"code":892,"language":231,"meta":232,"style":232},"{\"tenant\": 42, \"role\": \"staff\", \"profiles\": [\"rust\"], \"webhook_configured\": true, \"trial\": {\"profile\": \"cs2\", \"state\": \"active\", \"scans_left\": 3, \"ends_at\": 1759499200}, \"closed\": false}\n",[25,894,895],{"__ignoreMap":232},[236,896,897,899,901,903,905,907,910,912,914,917,919,921,923,926,928,930,932,935,937,939,942,944,947,949,952,954,957,959,961,964,966,969,971,973,976,978,980,982,984,986,989,991,993,995,997,999,1001,1003,1006,1008,1010,1012,1015,1017,1019,1022,1024,1026,1029,1031,1033,1036,1039,1041,1044,1046,1048],{"class":238,"line":239},[236,898,243],{"class":242},[236,900,246],{"class":242},[236,902,85],{"class":249},[236,904,246],{"class":242},[236,906,225],{"class":242},[236,908,909],{"class":440}," 42",[236,911,266],{"class":242},[236,913,257],{"class":242},[236,915,916],{"class":249},"role",[236,918,246],{"class":242},[236,920,225],{"class":242},[236,922,257],{"class":242},[236,924,925],{"class":260},"staff",[236,927,246],{"class":242},[236,929,266],{"class":242},[236,931,257],{"class":242},[236,933,934],{"class":249},"profiles",[236,936,246],{"class":242},[236,938,225],{"class":242},[236,940,941],{"class":242}," [",[236,943,246],{"class":242},[236,945,946],{"class":260},"rust",[236,948,246],{"class":242},[236,950,951],{"class":242},"],",[236,953,257],{"class":242},[236,955,956],{"class":249},"webhook_configured",[236,958,246],{"class":242},[236,960,225],{"class":242},[236,962,963],{"class":242}," true,",[236,965,257],{"class":242},[236,967,968],{"class":249},"trial",[236,970,246],{"class":242},[236,972,225],{"class":242},[236,974,975],{"class":242}," {",[236,977,246],{"class":242},[236,979,184],{"class":619},[236,981,246],{"class":242},[236,983,225],{"class":242},[236,985,257],{"class":242},[236,987,988],{"class":260},"cs2",[236,990,246],{"class":242},[236,992,266],{"class":242},[236,994,257],{"class":242},[236,996,271],{"class":619},[236,998,246],{"class":242},[236,1000,225],{"class":242},[236,1002,257],{"class":242},[236,1004,1005],{"class":260},"active",[236,1007,246],{"class":242},[236,1009,266],{"class":242},[236,1011,257],{"class":242},[236,1013,1014],{"class":619},"scans_left",[236,1016,246],{"class":242},[236,1018,225],{"class":242},[236,1020,1021],{"class":440}," 3",[236,1023,266],{"class":242},[236,1025,257],{"class":242},[236,1027,1028],{"class":619},"ends_at",[236,1030,246],{"class":242},[236,1032,225],{"class":242},[236,1034,1035],{"class":440}," 1759499200",[236,1037,1038],{"class":242},"},",[236,1040,257],{"class":242},[236,1042,1043],{"class":249},"closed",[236,1045,246],{"class":242},[236,1047,225],{"class":242},[236,1049,1050],{"class":242}," false}\n",[15,1052,1053],{},"It adds nothing to what your key can already do. It only tells you what that is, which is how you check a key works. Do not probe with a case id you made up.",[15,1055,1056,152,1058,203,1061,210,1063,1066,1067,1069,1070,1072,1073,1075,1076,203,1078,210,1081,1084,1085,1087],{},[25,1057,916],{},[25,1059,1060],{},"owner",[25,1062,925],{},[25,1064,1065],{},"read-only",", and those names are a contract like the states are. ",[25,1068,934],{}," is every game you may open a case for right now, however you came to hold it. ",[25,1071,956],{}," is whether decided appeals are already posted to your Discord webhook. It is the fact, never the address, so your own bot can tell whether posting an outcome would say it twice. ",[25,1074,968],{}," is null unless a trial is running, and otherwise names the game, one of ",[25,1077,1005],{},[25,1079,1080],{},"scans-used",[25,1082,1083],{},"ended",", how many scans are left, and when it ends. Scans left is absent once a trial has ended, because out of time and out of scans are different facts. ",[25,1086,1043],{}," turns true once a trial ended more than thirty days ago with no plan held. Then this is the one route that still answers.",[10,1089,1091,1092],{"id":1090},"the-discord-bots-own-route","The Discord bot's own route ",[1093,1094],"soon",{},[15,1096,1097,1098,1102],{},"The ",[110,1099,1101],{"href":1100},"\u002Fintegrations\u002F#discord-bot","Discord bot"," is coming; this route is not open to players or staff yet.",[15,1104,1105,1107],{},[25,1106,100],{}," is not a route your key opens. It is how our own bot deployment is handed a community's key.",[15,1109,1110,1111,1113,1114,1117,1118,1121],{},"An owner mints a handoff on their ",[110,1112,113],{"href":112},". It lasts ten minutes. It is spent once. This server kept only its digest. The deployment sends it as ",[25,1115,1116],{},"{\"handoff\": \"...\"}",". The bearer token is the client secret its Discord application was registered with. The answer is ",[25,1119,1120],{},"{\"tenant\": 42, \"api_key\": \"...\"}",", once. The key and the spend are one record, because binding is what mints the key. A key recorded without its handoff spent could be minted again. A handoff spent without its key is somebody locked out of the bot they just installed.",[15,1123,1124],{},"The guild being installed into is not in the request, and never will be. The deployment reads it from the token response it exchanged with its own client secret. This server reads it from nowhere at all. The handoff already names the community, and its owner is who minted it.",[15,1126,1127,1128,1131,1132,1135],{},"A community whose bot already holds a key is refused. The sentence is ",[25,1129,1130],{},"409 This community's bot already holds a key. Retire that key in the dashboard before binding another."," and the code is ",[25,1133,1134],{},"already-bound",". The handoff is not spent. A second key would be live with nobody holding it, and only the owner's retire button can end the first.",[10,1137,1139],{"id":1138},"when-it-refuses","When it refuses",[15,1141,1142,1143,1146,1147,1150],{},"A refusal is one plain sentence as ",[25,1144,1145],{},"text\u002Fplain",", never JSON. The sentence is for whoever reads your log, and it may be reworded the day it reads better. What your program branches on is the status and one header, ",[25,1148,1149],{},"x-glimmer-refusal",", which carries a short code that is a contract and is never reworded. Two refusals that share a status have different codes.",[19,1152,1153,1170,1183,1202,1211,1220,1228,1237,1248,1256,1265,1274],{},[22,1154,1155,1158,1159,1162,1163,1166,1167,118],{},[25,1156,1157],{},"401 Send the API key as a bearer token."," No ",[25,1160,1161],{},"Authorization"," header, no ",[25,1164,1165],{},"Bearer "," in front of it, or a token that is not 64 hexadecimal characters. Code ",[25,1168,1169],{},"bearer-required",[22,1171,1172,1175,1176,1179,1180,118],{},[25,1173,1174],{},"403 Request not permitted."," The key is unknown or revoked. The person it acts for is no longer staff. The install is not yours, the case is not yours, or your plan does not cover that game. A well-formed case id that belongs to somebody else answers this, not ",[25,1177,1178],{},"404",". Code ",[25,1181,1182],{},"denied",[22,1184,1185,1187,1188,210,1191,1194,1195,189,1198,1201],{},[25,1186,127],{},", in the trial's own words, when a trial is why a game is not covered. Either ",[25,1189,1190],{},"The trial's scans are used up. A plan continues where it left off, with every case kept.",[25,1192,1193],{},"The trial ended on its last day. A plan continues where it left off, with every case kept."," Codes ",[25,1196,1197],{},"trial-scans-used",[25,1199,1200],{},"trial-ended",", which is how to tell those two apart without reading the sentence.",[22,1203,1204,1207,1208,118],{},[25,1205,1206],{},"403 Every scan your plan includes this month is sent. Each one comes back thirty days after it went out, or a bigger plan lifts the limit."," Opening a case when your plan's scans a month are all sent: Standard includes 300 and Premium 1,500, counted over the last thirty days. Code ",[25,1209,1210],{},"plan-scans-used",[22,1212,1213,1216,1217,118],{},[25,1214,1215],{},"403 The trial ended more than thirty days ago. Its cases are kept, and readable again under a plan."," Your community's trial ended over thirty days ago and it holds no plan. Reading, listing, withdrawing, re-handing and the machine list all answer this. Opening a case answers the trial's own refusal above. Code ",[25,1218,1219],{},"tenant-closed",[22,1221,1222,1225,1226,118],{},[25,1223,1224],{},"403 Your plan does not include the API, so this key opens a case, reads a case back and says who it is, and nothing more. A plan with the API opens the rest."," Your community's plan does not include the API, and this route is not one of the three every plan reaches. Code ",[25,1227,117],{},[22,1229,1230,1233,1234,118],{},[25,1231,1232],{},"400 Request is invalid."," A profile, a category or a case id of the wrong shape, or a request the record itself refuses. Code ",[25,1235,1236],{},"invalid",[22,1238,1239,1241,1242,1244,1245,1247],{},[25,1240,345],{}," Your ",[25,1243,321],{}," opened a case for another player, game or machine, and a key stands for one create. Code ",[25,1246,349],{},", and a fresh key is the whole remedy.",[22,1249,1250,1253,1254,118],{},[25,1251,1252],{},"429 Try again later."," Too many wrong keys from one place lately. Until the five minutes pass, that place is refused whatever key it sends. Keys that work are never counted. Code ",[25,1255,147],{},[22,1257,1258,1261,1262,1264],{},[25,1259,1260],{},"429 Try again shortly."," This server is reading for somebody else and has no slot for you this second. Nothing is wrong with your key. Code ",[25,1263,155],{},", and sending the same request again is the whole remedy.",[22,1266,1267,1270,1271,118],{},[25,1268,1269],{},"408 Request deadline exceeded."," The request outlived the server's own deadline. Code ",[25,1272,1273],{},"deadline",[22,1275,1276,1279,1280,118],{},[25,1277,1278],{},"503 The case service could not complete this request."," The service could not finish. Nothing was opened, and the same request can go again. Code ",[25,1281,1282],{},"unavailable",[15,1284,1285,1286,1289],{},"A body that is not the JSON above, or one over 32 KiB, is refused before the route runs: a ",[25,1287,1288],{},"4xx"," and no JSON.",[15,1291,1292,1293,1295],{},"One more, and it catches a reader testing against a server of their own. The server expects to sit behind its TLS proxy. A request that did not arrive through that proxy is refused with the same ",[25,1294,1174],{}," a bad key gets. Every request to the real address comes through that proxy. Against a bare server of your own, that 403 is the proxy talking, not your key.",[10,1297,1299],{"id":1298},"what-this-will-not-do","What this will not do",[15,1301,1302],{},"Some things a client would reasonably ask for are refused here on purpose. Saying so is fairer than leaving you waiting for them.",[19,1304,1305,1312,1315,1318,1321,1331],{},[22,1306,1307,1308,1311],{},"No lookup by session code. The code is the scanned player's own credential, and a path segment is still a URL. It is written to access logs, to every proxy in front of them, and to the ",[25,1309,1310],{},"Referer"," of anything the answer links to. Hold the case id you were handed when you opened the case, and look the case up by that.",[22,1313,1314],{},"No appeal link. The capability in the create answer already is the player's login for their appeal. A one-time link would be a second credential in the very place we refuse to carry the first, and one door does not need two keys.",[22,1316,1317],{},"No callback when a report lands. Polling is the contract. A push means this server keeps your endpoint and a signing secret for it forever, to deliver a verdict one poll sooner than you would have read it.",[22,1319,1320],{},"No finding detail. A count and a decision, never the findings. Detail is read in the dashboard, where every read is recorded against a named person; a poll loop is neither a person nor an audit.",[22,1322,1323,1324,1327,1328,1330],{},"No ",[25,1325,1326],{},"downloaded",". Nothing here measures whether the launcher was fetched, and a fact that is not measured is not reported. ",[25,1329,433],{}," is the moment that is real, and it is on the status answer.",[22,1332,1333,1334,1336],{},"No key route. An owner lists and retires keys in the ",[110,1335,113],{"href":112},", and no key does either. A key that could retire keys could retire its siblings. A credential that ends every other credential is a worse thing to lose than the ones it would end.",[10,1338,1340],{"id":1339},"with-curl","With curl",[227,1342,1346],{"className":1343,"code":1344,"language":1345,"meta":232,"style":232},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -X POST https:\u002F\u002Fanticheat.lunarwerx.com\u002Fapi\u002Fv1\u002Fscans \\\n  -H \"Authorization: Bearer $GLIMMER_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"install\": 1000, \"target\": \"player#0420\", \"profile\": \"rust\", \"category\": \"reported\", \"reason\": \"Reported by two players in one round.\"}'\n","bash",[25,1347,1348,1366,1384,1398],{"__ignoreMap":232},[236,1349,1350,1353,1356,1359,1362],{"class":238,"line":239},[236,1351,1352],{"class":619},"curl",[236,1354,1355],{"class":260}," -X",[236,1357,1358],{"class":260}," POST",[236,1360,1361],{"class":260}," https:\u002F\u002Fanticheat.lunarwerx.com\u002Fapi\u002Fv1\u002Fscans",[236,1363,1365],{"class":1364},"sTEyZ"," \\\n",[236,1367,1369,1372,1374,1377,1380,1382],{"class":238,"line":1368},2,[236,1370,1371],{"class":260},"  -H",[236,1373,257],{"class":242},[236,1375,1376],{"class":260},"Authorization: Bearer ",[236,1378,1379],{"class":1364},"$GLIMMER_KEY",[236,1381,246],{"class":242},[236,1383,1365],{"class":1364},[236,1385,1387,1389,1391,1394,1396],{"class":238,"line":1386},3,[236,1388,1371],{"class":260},[236,1390,257],{"class":242},[236,1392,1393],{"class":260},"Content-Type: application\u002Fjson",[236,1395,246],{"class":242},[236,1397,1365],{"class":1364},[236,1399,1401,1404,1407,1410],{"class":238,"line":1400},4,[236,1402,1403],{"class":260},"  -d",[236,1405,1406],{"class":242}," '",[236,1408,1409],{"class":260},"{\"install\": 1000, \"target\": \"player#0420\", \"profile\": \"rust\", \"category\": \"reported\", \"reason\": \"Reported by two players in one round.\"}",[236,1411,1412],{"class":242},"'\n",[15,1414,1415,1416,1418],{},"Then, until ",[25,1417,25],{}," is there:",[227,1420,1422],{"className":1343,"code":1421,"language":1345,"meta":232,"style":232},"curl https:\u002F\u002Fanticheat.lunarwerx.com\u002Fapi\u002Fv1\u002Fscans\u002F$CASE \\\n  -H \"Authorization: Bearer $GLIMMER_KEY\"\n",[25,1423,1424,1434],{"__ignoreMap":232},[236,1425,1426,1428,1431],{"class":238,"line":239},[236,1427,1352],{"class":619},[236,1429,1430],{"class":260}," https:\u002F\u002Fanticheat.lunarwerx.com\u002Fapi\u002Fv1\u002Fscans\u002F",[236,1432,1433],{"class":1364},"$CASE \\\n",[236,1435,1436,1438,1440,1442,1444],{"class":238,"line":1368},[236,1437,1371],{"class":260},[236,1439,257],{"class":242},[236,1441,1376],{"class":260},[236,1443,1379],{"class":1364},[236,1445,1446],{"class":242},"\"\n",[10,1448,1450],{"id":1449},"with-python","With Python",[15,1452,1453],{},"Nothing to install: this is the standard library. It opens a case, prints the capability that goes to the player, and polls until the code exists.",[227,1455,1459],{"className":1456,"code":1457,"language":1458,"meta":232,"style":232},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import json, os, time, urllib.error, urllib.request\n\nBASE = \"https:\u002F\u002Fanticheat.lunarwerx.com\"\nKEY = os.environ[\"GLIMMER_KEY\"]\n\ndef call(method, path, body=None):\n    request = urllib.request.Request(\n        BASE + path,\n        method=method,\n        data=None if body is None else json.dumps(body).encode(),\n        headers={\"Authorization\": \"Bearer \" + KEY, \"Content-Type\": \"application\u002Fjson\"},\n    )\n    try:\n        with urllib.request.urlopen(request, timeout=30) as answer:\n            return json.load(answer)\n    except urllib.error.HTTPError as refused:\n        raise SystemExit(str(refused.code) + \" \" + refused.read().decode().strip())\n\nopened = call(\"POST\", \"\u002Fapi\u002Fv1\u002Fscans\", {\n    \"target\": \"player#0420\",\n    \"profile\": \"rust\",\n    \"category\": \"reported\",\n    \"reason\": \"Reported by two players in one round.\",\n})\nprint(\"case\", opened[\"case\"])\nprint(\"hand the player\", opened[\"accused_capability\"])\n\nwhile True:\n    case = call(\"GET\", \"\u002Fapi\u002Fv1\u002Fscans\u002F\" + opened[\"case\"])\n    if case[\"code\"]:\n        print(\"code\", case[\"code\"], \"state\", case[\"state\"])\n        break\n    time.sleep(5)\n","python",[25,1460,1461,1466,1472,1477,1482,1487,1493,1499,1505,1511,1517,1523,1529,1535,1541,1547,1553,1559,1564,1570,1576,1582,1588,1594,1600,1606,1612,1617,1623,1629,1635,1641,1647],{"__ignoreMap":232},[236,1462,1463],{"class":238,"line":239},[236,1464,1465],{},"import json, os, time, urllib.error, urllib.request\n",[236,1467,1468],{"class":238,"line":1368},[236,1469,1471],{"emptyLinePlaceholder":1470},true,"\n",[236,1473,1474],{"class":238,"line":1386},[236,1475,1476],{},"BASE = \"https:\u002F\u002Fanticheat.lunarwerx.com\"\n",[236,1478,1479],{"class":238,"line":1400},[236,1480,1481],{},"KEY = os.environ[\"GLIMMER_KEY\"]\n",[236,1483,1485],{"class":238,"line":1484},5,[236,1486,1471],{"emptyLinePlaceholder":1470},[236,1488,1490],{"class":238,"line":1489},6,[236,1491,1492],{},"def call(method, path, body=None):\n",[236,1494,1496],{"class":238,"line":1495},7,[236,1497,1498],{},"    request = urllib.request.Request(\n",[236,1500,1502],{"class":238,"line":1501},8,[236,1503,1504],{},"        BASE + path,\n",[236,1506,1508],{"class":238,"line":1507},9,[236,1509,1510],{},"        method=method,\n",[236,1512,1514],{"class":238,"line":1513},10,[236,1515,1516],{},"        data=None if body is None else json.dumps(body).encode(),\n",[236,1518,1520],{"class":238,"line":1519},11,[236,1521,1522],{},"        headers={\"Authorization\": \"Bearer \" + KEY, \"Content-Type\": \"application\u002Fjson\"},\n",[236,1524,1526],{"class":238,"line":1525},12,[236,1527,1528],{},"    )\n",[236,1530,1532],{"class":238,"line":1531},13,[236,1533,1534],{},"    try:\n",[236,1536,1538],{"class":238,"line":1537},14,[236,1539,1540],{},"        with urllib.request.urlopen(request, timeout=30) as answer:\n",[236,1542,1544],{"class":238,"line":1543},15,[236,1545,1546],{},"            return json.load(answer)\n",[236,1548,1550],{"class":238,"line":1549},16,[236,1551,1552],{},"    except urllib.error.HTTPError as refused:\n",[236,1554,1556],{"class":238,"line":1555},17,[236,1557,1558],{},"        raise SystemExit(str(refused.code) + \" \" + refused.read().decode().strip())\n",[236,1560,1562],{"class":238,"line":1561},18,[236,1563,1471],{"emptyLinePlaceholder":1470},[236,1565,1567],{"class":238,"line":1566},19,[236,1568,1569],{},"opened = call(\"POST\", \"\u002Fapi\u002Fv1\u002Fscans\", {\n",[236,1571,1573],{"class":238,"line":1572},20,[236,1574,1575],{},"    \"target\": \"player#0420\",\n",[236,1577,1579],{"class":238,"line":1578},21,[236,1580,1581],{},"    \"profile\": \"rust\",\n",[236,1583,1585],{"class":238,"line":1584},22,[236,1586,1587],{},"    \"category\": \"reported\",\n",[236,1589,1591],{"class":238,"line":1590},23,[236,1592,1593],{},"    \"reason\": \"Reported by two players in one round.\",\n",[236,1595,1597],{"class":238,"line":1596},24,[236,1598,1599],{},"})\n",[236,1601,1603],{"class":238,"line":1602},25,[236,1604,1605],{},"print(\"case\", opened[\"case\"])\n",[236,1607,1609],{"class":238,"line":1608},26,[236,1610,1611],{},"print(\"hand the player\", opened[\"accused_capability\"])\n",[236,1613,1615],{"class":238,"line":1614},27,[236,1616,1471],{"emptyLinePlaceholder":1470},[236,1618,1620],{"class":238,"line":1619},28,[236,1621,1622],{},"while True:\n",[236,1624,1626],{"class":238,"line":1625},29,[236,1627,1628],{},"    case = call(\"GET\", \"\u002Fapi\u002Fv1\u002Fscans\u002F\" + opened[\"case\"])\n",[236,1630,1632],{"class":238,"line":1631},30,[236,1633,1634],{},"    if case[\"code\"]:\n",[236,1636,1638],{"class":238,"line":1637},31,[236,1639,1640],{},"        print(\"code\", case[\"code\"], \"state\", case[\"state\"])\n",[236,1642,1644],{"class":238,"line":1643},32,[236,1645,1646],{},"        break\n",[236,1648,1650],{"class":238,"line":1649},33,[236,1651,1652],{},"    time.sleep(5)\n",[10,1654,1656,1657],{"id":1655},"from-an-assistant","From an assistant ",[1093,1658],{},[15,1660,1661],{},"The AI assistant (MCP) is coming; there is nothing to install yet.",[19,1663,1664],{},[22,1665,1666,1670,1671,1673],{},[110,1667,1669],{"href":1668},"\u002Fintegrations\u002F#ai-assistant-mcp","The MCP server"," is coming. It is one small program on a staff PC. It hands Claude or ChatGPT five of the routes above. It opens a case, reads it back, lists what is still open, lists your machines and says who the key is. It never withdraws a case. It has no tool that re-hands a player's key. But asking it to open the same case twice in one hour is a retry, and a retry hands out a fresh key, as above. Its key is minted for it in the ",[110,1672,113],{"href":112}," and lives in the one file it reads.",[1675,1676,1677],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":232,"searchDepth":1368,"depth":1368,"links":1679},[1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695],{"id":12,"depth":1368,"text":13},{"id":104,"depth":1368,"text":105},{"id":159,"depth":1368,"text":160},{"id":314,"depth":1368,"text":315},{"id":361,"depth":1368,"text":362},{"id":586,"depth":1368,"text":587},{"id":711,"depth":1368,"text":712},{"id":795,"depth":1368,"text":796},{"id":823,"depth":1368,"text":824},{"id":882,"depth":1368,"text":883},{"id":1090,"depth":1368,"text":1091},{"id":1138,"depth":1368,"text":1139},{"id":1298,"depth":1368,"text":1299},{"id":1339,"depth":1368,"text":1340},{"id":1449,"depth":1368,"text":1450},{"id":1655,"depth":1368,"text":1656},"Your own tools open a case and read it back. One key. Your games.","md",{},"\u002Fapi",{"title":5,"description":1696},"api","9-Xi2XrHY3lplvkx18ARimKQzv6fpxoWzk105nmWxao",1792069827619]