[{"data":1,"prerenderedAt":1164},["ShallowReactive",2],{"/articles/protecting-apis":3},{"id":4,"title":5,"body":6,"date":1148,"description":1149,"extension":1150,"head":1151,"image":19,"meta":1152,"navigation":139,"ogImage":1151,"path":1154,"readingTime":1155,"robots":1151,"schemaOrg":1151,"seo":1156,"sitemap":1157,"stem":1158,"tags":1159,"__hash__":1163},"articles/articles/protecting-apis.md","Protecting Your API Endpoints with Bot Detector",{"type":7,"value":8,"toc":1135},"minimark",[9,13,20,31,34,37,42,51,60,64,67,70,406,410,417,422,425,434,437,512,515,706,709,713,716,726,924,928,931,934,1013,1016,1020,1023,1060,1063,1067,1081,1097,1100,1104,1111,1117,1121,1124,1131],[10,11,12],"p",{},"APIs receive a wide range of automated traffic. Some of that traffic is useful and intentional. Other traffic probes for credentials, scrapes content at scale, or tries to find weaknesses.",[10,14,15],{},[16,17],"img",{"alt":18,"src":19},"preview","/articles/api.jpg",[10,21,22,23,30],{},"Protecting an endpoint requires combining multiple independent signals. A single rule rarely stops a determined attacker. ",[24,25,29],"a",{"href":26,"rel":27},"https://docs.riavzon.com/docs/bot-detection",[28],"nofollow","Bot Detector"," brings together reputation, fingerprinting, and behavioral analysis in a single pipeline.",[10,32,33],{},"Each signal has limits. IP reputation flags known bad ranges but attackers rotate addresses. User agent checks catch simple tools but not sophisticated ones. Timing and session signals reveal scripted behavior that other checks miss.",[10,35,36],{},"By combining signals you make evasion costly. An attacker must spoof multiple unrelated properties at once to avoid detection. That increases complexity and reduces the attack surface.",[38,39,41],"h2",{"id":40},"how-it-fits","How it fits",[10,43,44,45,50],{},"Bot Detector is an Express middleware that runs a two phase pipeline of ",[24,46,49],{"href":47,"rel":48},"https://docs.riavzon.com/docs/bot-detection/checkers",[28],"checkers",". The cheap phase runs synchronous, in-memory checks first and stops early when the score crosses the ban threshold. The heavy phase runs async checks only when needed.",[10,52,53,54,59],{},"The detector loads binary databases compiled by ",[24,55,58],{"href":56,"rel":57},"https://docs.riavzon.com/docs/shield-base",[28],"Shield Base"," at startup. Those files provide fast IP, ASN, Tor, proxy, and user-agent pattern lookups. A canary cookie ties each browser or client session to a persistent server record used by behavioral checks.",[38,61,63],{"id":62},"integrate-quickly-with-express","Integrate quickly with Express",[10,65,66],{},"Mount the middleware early in the request chain so it can short-circuit abusive requests before they reach business logic. The example below shows a minimal Express setup and a protected login route.",[10,68,69],{},"Initialize the detector once at startup, then mount the middleware and protect sensitive routes.",[71,72,78],"pre",{"className":73,"code":74,"filename":75,"language":76,"meta":77,"style":77},"language-ts shiki shiki-themes github-light github-dark github-dark","import express from 'express';\nimport cookieParser from 'cookie-parser';\nimport { defineConfiguration, detectBots } from '@riavzon/bot-detector';\n\nconst app = express();\napp.use(cookieParser());\n\nawait defineConfiguration({\n  store: { main: { driver: 'sqlite', name: './bot-detector.db' } },\n  banScore: 100,\n  checkers: {\n    enableBehaviorRateCheck: { enable: true },\n    enableUaAndHeaderChecks: { enable: true }\n  }\n});\n\napp.use(detectBots());\n\napp.post('/auth/login', detectBots(), async (req, res) => {\n  if (req.botDetection?.banned) return res.status(403).json({ ok: false, reason: 'BOT_DETECTED' });\n  // continue with normal login flow\n  res.json({ ok: true });\n});\n","server.ts","ts","",[79,80,81,104,119,134,141,161,179,184,196,214,226,232,244,255,261,267,272,286,291,336,380,387,401],"code",{"__ignoreMap":77},[82,83,86,90,94,97,101],"span",{"class":84,"line":85},"line",1,[82,87,89],{"class":88},"so5gQ","import",[82,91,93],{"class":92},"slsVL"," express ",[82,95,96],{"class":88},"from",[82,98,100],{"class":99},"sfrk1"," 'express'",[82,102,103],{"class":92},";\n",[82,105,107,109,112,114,117],{"class":84,"line":106},2,[82,108,89],{"class":88},[82,110,111],{"class":92}," cookieParser ",[82,113,96],{"class":88},[82,115,116],{"class":99}," 'cookie-parser'",[82,118,103],{"class":92},[82,120,122,124,127,129,132],{"class":84,"line":121},3,[82,123,89],{"class":88},[82,125,126],{"class":92}," { defineConfiguration, detectBots } ",[82,128,96],{"class":88},[82,130,131],{"class":99}," '@riavzon/bot-detector'",[82,133,103],{"class":92},[82,135,137],{"class":84,"line":136},4,[82,138,140],{"emptyLinePlaceholder":139},true,"\n",[82,142,144,147,151,154,158],{"class":84,"line":143},5,[82,145,146],{"class":88},"const",[82,148,150],{"class":149},"suiK_"," app",[82,152,153],{"class":88}," =",[82,155,157],{"class":156},"shcOC"," express",[82,159,160],{"class":92},"();\n",[82,162,164,167,170,173,176],{"class":84,"line":163},6,[82,165,166],{"class":92},"app.",[82,168,169],{"class":156},"use",[82,171,172],{"class":92},"(",[82,174,175],{"class":156},"cookieParser",[82,177,178],{"class":92},"());\n",[82,180,182],{"class":84,"line":181},7,[82,183,140],{"emptyLinePlaceholder":139},[82,185,187,190,193],{"class":84,"line":186},8,[82,188,189],{"class":88},"await",[82,191,192],{"class":156}," defineConfiguration",[82,194,195],{"class":92},"({\n",[82,197,199,202,205,208,211],{"class":84,"line":198},9,[82,200,201],{"class":92},"  store: { main: { driver: ",[82,203,204],{"class":99},"'sqlite'",[82,206,207],{"class":92},", name: ",[82,209,210],{"class":99},"'./bot-detector.db'",[82,212,213],{"class":92}," } },\n",[82,215,217,220,223],{"class":84,"line":216},10,[82,218,219],{"class":92},"  banScore: ",[82,221,222],{"class":149},"100",[82,224,225],{"class":92},",\n",[82,227,229],{"class":84,"line":228},11,[82,230,231],{"class":92},"  checkers: {\n",[82,233,235,238,241],{"class":84,"line":234},12,[82,236,237],{"class":92},"    enableBehaviorRateCheck: { enable: ",[82,239,240],{"class":149},"true",[82,242,243],{"class":92}," },\n",[82,245,247,250,252],{"class":84,"line":246},13,[82,248,249],{"class":92},"    enableUaAndHeaderChecks: { enable: ",[82,251,240],{"class":149},[82,253,254],{"class":92}," }\n",[82,256,258],{"class":84,"line":257},14,[82,259,260],{"class":92},"  }\n",[82,262,264],{"class":84,"line":263},15,[82,265,266],{"class":92},"});\n",[82,268,270],{"class":84,"line":269},16,[82,271,140],{"emptyLinePlaceholder":139},[82,273,275,277,279,281,284],{"class":84,"line":274},17,[82,276,166],{"class":92},[82,278,169],{"class":156},[82,280,172],{"class":92},[82,282,283],{"class":156},"detectBots",[82,285,178],{"class":92},[82,287,289],{"class":84,"line":288},18,[82,290,140],{"emptyLinePlaceholder":139},[82,292,294,296,299,301,304,307,309,312,315,318,322,324,327,330,333],{"class":84,"line":293},19,[82,295,166],{"class":92},[82,297,298],{"class":156},"post",[82,300,172],{"class":92},[82,302,303],{"class":99},"'/auth/login'",[82,305,306],{"class":92},", ",[82,308,283],{"class":156},[82,310,311],{"class":92},"(), ",[82,313,314],{"class":88},"async",[82,316,317],{"class":92}," (",[82,319,321],{"class":320},"sQHwn","req",[82,323,306],{"class":92},[82,325,326],{"class":320},"res",[82,328,329],{"class":92},") ",[82,331,332],{"class":88},"=>",[82,334,335],{"class":92}," {\n",[82,337,339,342,345,348,351,354,356,359,362,365,368,371,374,377],{"class":84,"line":338},20,[82,340,341],{"class":88},"  if",[82,343,344],{"class":92}," (req.botDetection?.banned) ",[82,346,347],{"class":88},"return",[82,349,350],{"class":92}," res.",[82,352,353],{"class":156},"status",[82,355,172],{"class":92},[82,357,358],{"class":149},"403",[82,360,361],{"class":92},").",[82,363,364],{"class":156},"json",[82,366,367],{"class":92},"({ ok: ",[82,369,370],{"class":149},"false",[82,372,373],{"class":92},", reason: ",[82,375,376],{"class":99},"'BOT_DETECTED'",[82,378,379],{"class":92}," });\n",[82,381,383],{"class":84,"line":382},21,[82,384,386],{"class":385},"sCsY4","  // continue with normal login flow\n",[82,388,390,393,395,397,399],{"class":84,"line":389},22,[82,391,392],{"class":92},"  res.",[82,394,364],{"class":156},[82,396,367],{"class":92},[82,398,240],{"class":149},[82,400,379],{"class":92},[82,402,404],{"class":84,"line":403},23,[82,405,266],{"class":92},[38,407,409],{"id":408},"selective-enforcement","Selective enforcement",[10,411,412,413,416],{},"Protect only the endpoints that matter. Use ",[79,414,415],{},"detectBots()"," as a route-level middleware for high value paths. This keeps false positive surface small and preserves throughput for public endpoints.",[418,419,421],"h3",{"id":420},"tuning-for-api-traffic","Tuning for API traffic",[10,423,424],{},"APIs often serve legitimate automated clients. That fact changes how you tune the detector. The two common approaches are explicit client identification and adaptive penalties.",[10,426,427,428,433],{},"Use API keys or client certificates to identify trusted automation. When a request carries a valid API key, mark it as a trusted client in the ",[24,429,432],{"href":430,"rel":431},"https://docs.riavzon.com/docs/bot-detection/guides/custom",[28],"custom context"," and either lower penalties or skip specific checks. When you cannot trust the client, apply the full pipeline.",[10,435,436],{},"Explanation: build a small custom context that flags API clients based on the presence of a known key.",[71,438,440],{"className":73,"code":439,"filename":75,"language":76,"meta":77,"style":77},"app.use(\n  detectBots\u003C{ isApiClient?: boolean }>((req) => ({\n    isApiClient: Boolean(req.get('x-api-key'))\n  }))\n);\n",[79,441,442,451,480,502,507],{"__ignoreMap":77},[82,443,444,446,448],{"class":84,"line":85},[82,445,166],{"class":92},[82,447,169],{"class":156},[82,449,450],{"class":92},"(\n",[82,452,453,456,459,462,465,468,471,473,475,477],{"class":84,"line":106},[82,454,455],{"class":156},"  detectBots",[82,457,458],{"class":92},"\u003C{ ",[82,460,461],{"class":320},"isApiClient",[82,463,464],{"class":88},"?:",[82,466,467],{"class":149}," boolean",[82,469,470],{"class":92}," }>((",[82,472,321],{"class":320},[82,474,329],{"class":92},[82,476,332],{"class":88},[82,478,479],{"class":92}," ({\n",[82,481,482,485,488,491,494,496,499],{"class":84,"line":121},[82,483,484],{"class":92},"    isApiClient: ",[82,486,487],{"class":156},"Boolean",[82,489,490],{"class":92},"(req.",[82,492,493],{"class":156},"get",[82,495,172],{"class":92},[82,497,498],{"class":99},"'x-api-key'",[82,500,501],{"class":92},"))\n",[82,503,504],{"class":84,"line":136},[82,505,506],{"class":92},"  }))\n",[82,508,509],{"class":84,"line":143},[82,510,511],{"class":92},");\n",[10,513,514],{},"Then implement a cheap-phase checker that treats flagged clients differently.",[71,516,519],{"className":73,"code":517,"filename":518,"language":76,"meta":77,"style":77},"import { CheckerRegistry } from '@riavzon/bot-detector';\nimport type { IBotChecker, ValidationContext } from '@riavzon/bot-detector';\n\nclass ApiClientChecker implements IBotChecker\u003C'API_CLIENT'> {\n  name = 'api-client-checker';\n  phase = 'cheap' as const;\n  isEnabled() { return true; }\n\n  run(ctx: ValidationContext) {\n    if (ctx.custom?.isApiClient) return { score: 0, reasons: [] };\n    return { score: 0, reasons: [] };\n  }\n}\n\nCheckerRegistry.register(new ApiClientChecker());\n","checkers/api-client-checker.ts",[79,520,521,534,550,554,577,589,607,623,627,646,665,676,680,685,689],{"__ignoreMap":77},[82,522,523,525,528,530,532],{"class":84,"line":85},[82,524,89],{"class":88},[82,526,527],{"class":92}," { CheckerRegistry } ",[82,529,96],{"class":88},[82,531,131],{"class":99},[82,533,103],{"class":92},[82,535,536,538,541,544,546,548],{"class":84,"line":106},[82,537,89],{"class":88},[82,539,540],{"class":88}," type",[82,542,543],{"class":92}," { IBotChecker, ValidationContext } ",[82,545,96],{"class":88},[82,547,131],{"class":99},[82,549,103],{"class":92},[82,551,552],{"class":84,"line":121},[82,553,140],{"emptyLinePlaceholder":139},[82,555,556,559,562,565,568,571,574],{"class":84,"line":136},[82,557,558],{"class":88},"class",[82,560,561],{"class":156}," ApiClientChecker",[82,563,564],{"class":88}," implements",[82,566,567],{"class":156}," IBotChecker",[82,569,570],{"class":92},"\u003C",[82,572,573],{"class":99},"'API_CLIENT'",[82,575,576],{"class":92},"> {\n",[82,578,579,582,584,587],{"class":84,"line":143},[82,580,581],{"class":320},"  name",[82,583,153],{"class":88},[82,585,586],{"class":99}," 'api-client-checker'",[82,588,103],{"class":92},[82,590,591,594,596,599,602,605],{"class":84,"line":163},[82,592,593],{"class":320},"  phase",[82,595,153],{"class":88},[82,597,598],{"class":99}," 'cheap'",[82,600,601],{"class":88}," as",[82,603,604],{"class":88}," const",[82,606,103],{"class":92},[82,608,609,612,615,617,620],{"class":84,"line":181},[82,610,611],{"class":156},"  isEnabled",[82,613,614],{"class":92},"() { ",[82,616,347],{"class":88},[82,618,619],{"class":149}," true",[82,621,622],{"class":92},"; }\n",[82,624,625],{"class":84,"line":186},[82,626,140],{"emptyLinePlaceholder":139},[82,628,629,632,634,637,640,643],{"class":84,"line":198},[82,630,631],{"class":156},"  run",[82,633,172],{"class":92},[82,635,636],{"class":320},"ctx",[82,638,639],{"class":88},":",[82,641,642],{"class":156}," ValidationContext",[82,644,645],{"class":92},") {\n",[82,647,648,651,654,656,659,662],{"class":84,"line":216},[82,649,650],{"class":88},"    if",[82,652,653],{"class":92}," (ctx.custom?.isApiClient) ",[82,655,347],{"class":88},[82,657,658],{"class":92}," { score: ",[82,660,661],{"class":149},"0",[82,663,664],{"class":92},", reasons: [] };\n",[82,666,667,670,672,674],{"class":84,"line":228},[82,668,669],{"class":88},"    return",[82,671,658],{"class":92},[82,673,661],{"class":149},[82,675,664],{"class":92},[82,677,678],{"class":84,"line":234},[82,679,260],{"class":92},[82,681,682],{"class":84,"line":246},[82,683,684],{"class":92},"}\n",[82,686,687],{"class":84,"line":257},[82,688,140],{"emptyLinePlaceholder":139},[82,690,691,694,697,699,702,704],{"class":84,"line":263},[82,692,693],{"class":92},"CheckerRegistry.",[82,695,696],{"class":156},"register",[82,698,172],{"class":92},[82,700,701],{"class":88},"new",[82,703,561],{"class":156},[82,705,178],{"class":92},[10,707,708],{},"This pattern keeps the pipeline flexible. Trusted clients retain their throughput while unknown clients face the full set of checks.",[38,710,712],{"id":711},"custom-checkers-for-business-signals","Custom checkers for business signals",[10,714,715],{},"Custom checkers let you encode domain knowledge into the same scoring pipeline. Examples include plan tier limits, internal IP bypasses, or stricter rules for account creation endpoints.",[10,717,718,719,725],{},"Explanation: a checker inspects ",[24,720,722],{"href":430,"rel":721},[28],[79,723,724],{},"ctx.custom"," and returns a score and reason codes. Register it at startup and the pipeline picks it up automatically.",[71,727,730],{"className":73,"code":728,"filename":729,"language":76,"meta":77,"style":77},"import { CheckerRegistry } from '@riavzon/bot-detector';\nimport type { IBotChecker, ValidationContext } from '@riavzon/bot-detector';\n\nclass PlanAbuseChecker implements IBotChecker\u003C'PLAN_ABUSE'> {\n  name = 'plan-abuse';\n  phase = 'cheap' as const;\n  isEnabled() { return true; }\n\n  run(ctx: ValidationContext\u003C{ plan?: string }>) {\n    if (ctx.custom?.plan === 'free' && ctx.geoData?.proxy) {\n      return { score: 20, reasons: ['PLAN_ABUSE'] };\n    }\n    return { score: 0, reasons: [] };\n  }\n}\n\nCheckerRegistry.register(new PlanAbuseChecker());\n","checkers/plan-abuse-checker.ts",[79,731,732,744,758,762,780,791,805,817,821,846,865,883,888,898,902,906,910],{"__ignoreMap":77},[82,733,734,736,738,740,742],{"class":84,"line":85},[82,735,89],{"class":88},[82,737,527],{"class":92},[82,739,96],{"class":88},[82,741,131],{"class":99},[82,743,103],{"class":92},[82,745,746,748,750,752,754,756],{"class":84,"line":106},[82,747,89],{"class":88},[82,749,540],{"class":88},[82,751,543],{"class":92},[82,753,96],{"class":88},[82,755,131],{"class":99},[82,757,103],{"class":92},[82,759,760],{"class":84,"line":121},[82,761,140],{"emptyLinePlaceholder":139},[82,763,764,766,769,771,773,775,778],{"class":84,"line":136},[82,765,558],{"class":88},[82,767,768],{"class":156}," PlanAbuseChecker",[82,770,564],{"class":88},[82,772,567],{"class":156},[82,774,570],{"class":92},[82,776,777],{"class":99},"'PLAN_ABUSE'",[82,779,576],{"class":92},[82,781,782,784,786,789],{"class":84,"line":143},[82,783,581],{"class":320},[82,785,153],{"class":88},[82,787,788],{"class":99}," 'plan-abuse'",[82,790,103],{"class":92},[82,792,793,795,797,799,801,803],{"class":84,"line":163},[82,794,593],{"class":320},[82,796,153],{"class":88},[82,798,598],{"class":99},[82,800,601],{"class":88},[82,802,604],{"class":88},[82,804,103],{"class":92},[82,806,807,809,811,813,815],{"class":84,"line":181},[82,808,611],{"class":156},[82,810,614],{"class":92},[82,812,347],{"class":88},[82,814,619],{"class":149},[82,816,622],{"class":92},[82,818,819],{"class":84,"line":186},[82,820,140],{"emptyLinePlaceholder":139},[82,822,823,825,827,829,831,833,835,838,840,843],{"class":84,"line":198},[82,824,631],{"class":156},[82,826,172],{"class":92},[82,828,636],{"class":320},[82,830,639],{"class":88},[82,832,642],{"class":156},[82,834,458],{"class":92},[82,836,837],{"class":320},"plan",[82,839,464],{"class":88},[82,841,842],{"class":149}," string",[82,844,845],{"class":92}," }>) {\n",[82,847,848,850,853,856,859,862],{"class":84,"line":216},[82,849,650],{"class":88},[82,851,852],{"class":92}," (ctx.custom?.plan ",[82,854,855],{"class":88},"===",[82,857,858],{"class":99}," 'free'",[82,860,861],{"class":88}," &&",[82,863,864],{"class":92}," ctx.geoData?.proxy) {\n",[82,866,867,870,872,875,878,880],{"class":84,"line":228},[82,868,869],{"class":88},"      return",[82,871,658],{"class":92},[82,873,874],{"class":149},"20",[82,876,877],{"class":92},", reasons: [",[82,879,777],{"class":99},[82,881,882],{"class":92},"] };\n",[82,884,885],{"class":84,"line":234},[82,886,887],{"class":92},"    }\n",[82,889,890,892,894,896],{"class":84,"line":246},[82,891,669],{"class":88},[82,893,658],{"class":92},[82,895,661],{"class":149},[82,897,664],{"class":92},[82,899,900],{"class":84,"line":257},[82,901,260],{"class":92},[82,903,904],{"class":84,"line":263},[82,905,684],{"class":92},[82,907,908],{"class":84,"line":269},[82,909,140],{"emptyLinePlaceholder":139},[82,911,912,914,916,918,920,922],{"class":84,"line":274},[82,913,693],{"class":92},[82,915,696],{"class":156},[82,917,172],{"class":92},[82,919,701],{"class":88},[82,921,768],{"class":156},[82,923,178],{"class":92},[38,925,927],{"id":926},"testing-and-validation","Testing and validation",[10,929,930],{},"Test in three layers: unit test checkers, integration test the middleware, and run traffic simulations that mirror expected attacks. Start conservative and raise penalties after you observe real traffic.",[10,932,933],{},"a simple script can generate repeated login attempts to validate that cheap-phase short-circuits obvious attacks.",[71,935,940],{"className":936,"code":937,"filename":938,"language":939,"meta":77,"style":77},"language-bash shiki shiki-themes github-light github-dark github-dark","# 50 quick login attempts\nfor i in {1..50}; do\n  curl -s -X POST https://api.example.com/auth/login \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\"username\":\"test\",\"password\":\"wrong\"}'\ndone\n","Terminal","bash",[79,941,942,947,970,990,1000,1008],{"__ignoreMap":77},[82,943,944],{"class":84,"line":85},[82,945,946],{"class":385},"# 50 quick login attempts\n",[82,948,949,952,955,958,961,964,967],{"class":84,"line":106},[82,950,951],{"class":88},"for",[82,953,954],{"class":92}," i ",[82,956,957],{"class":88},"in",[82,959,960],{"class":92}," {",[82,962,963],{"class":156},"1..50}",[82,965,966],{"class":92},"; ",[82,968,969],{"class":88},"do\n",[82,971,972,975,978,981,984,987],{"class":84,"line":121},[82,973,974],{"class":156},"  curl",[82,976,977],{"class":149}," -s",[82,979,980],{"class":149}," -X",[82,982,983],{"class":99}," POST",[82,985,986],{"class":99}," https://api.example.com/auth/login",[82,988,989],{"class":149}," \\\n",[82,991,992,995,998],{"class":84,"line":136},[82,993,994],{"class":149},"    -H",[82,996,997],{"class":99}," \"Content-Type: application/json\"",[82,999,989],{"class":149},[82,1001,1002,1005],{"class":84,"line":143},[82,1003,1004],{"class":149},"    -d",[82,1006,1007],{"class":99}," '{\"username\":\"test\",\"password\":\"wrong\"}'\n",[82,1009,1010],{"class":84,"line":163},[82,1011,1012],{"class":88},"done\n",[10,1014,1015],{},"Observe the detector logs and metrics during the run. Confirm that banned requests return 403 and that the application handlers never execute for those requests.",[38,1017,1019],{"id":1018},"observability-and-metrics","Observability and metrics",[10,1021,1022],{},"Track these signals per endpoint and per reason code. They guide safe tuning and reveal blind spots.",[1024,1025,1026,1034,1040,1046],"ul",{},[1027,1028,1029,1033],"li",{},[1030,1031,1032],"strong",{},"Ban rate",": count of banned requests by endpoint.",[1027,1035,1036,1039],{},[1030,1037,1038],{},"Decision score distribution",": histogram of scores at decision time.",[1027,1041,1042,1045],{},[1030,1043,1044],{},"Cheap vs heavy phase time",": time spent in each phase.",[1027,1047,1048,1051,1052,1055,1056,1059],{},[1030,1049,1050],{},"Generation counts",": entries written to ",[79,1053,1054],{},"banned.mmdb"," and ",[79,1057,1058],{},"highRisk.mmdb",".",[10,1061,1062],{},"Use your existing monitoring stack to alert on sudden increases in banned counts or generation volume. Those are reliable indicators of an active campaign.",[38,1064,1066],{"id":1065},"operations-and-generation","Operations and generation",[10,1068,1069,1070,1075,1076,1055,1078,1080],{},"Periodically run ",[24,1071,1074],{"href":1072,"rel":1073},"https://docs.riavzon.com/docs/bot-detection/guides/generate",[28],"generation"," to compile ",[79,1077,1054],{},[79,1079,1058],{}," from recent history. The detector hot reloads updated files automatically.",[71,1082,1084],{"className":936,"code":1083,"filename":938,"language":939,"meta":77,"style":77},"npx @riavzon/bot-detector generate\n",[79,1085,1086],{"__ignoreMap":77},[82,1087,1088,1091,1094],{"class":84,"line":85},[82,1089,1090],{"class":156},"npx",[82,1092,1093],{"class":99}," @riavzon/bot-detector",[82,1095,1096],{"class":99}," generate\n",[10,1098,1099],{},"Start with nightly generation for moderate traffic. Move to hourly generation only if you see frequent repeat offenders that need immediate blocking.",[38,1101,1103],{"id":1102},"progressive-enforcement-policy","Progressive enforcement policy",[10,1105,1106,1107,1110],{},"Roll out enforcement in three steps. Detect, observe, then apply mitigation. Begin by logging decisions without blocking. Next, add ",[79,1108,1109],{},"highRisk"," compilations to bias the cheap phase. Finally, enforce bans for repeat offenders.",[1112,1113,1114],"note",{},[10,1115,1116],{},"Start conservative when you deploy detection to avoid impacting legitimate users. Use metrics to move from observation to enforcement.",[38,1118,1120],{"id":1119},"summary","Summary",[10,1122,1123],{},"Protecting API endpoints requires signals that are hard to spoof together. Bot Detector combines compiled reputation data, fingerprint checks, and behavioral analytics into a two phase pipeline. Use API keys and custom checkers to handle legitimate automation. Tune by observing live traffic and increase enforcement in measured steps.",[10,1125,1126,1127,1059],{},"Learn more about the module in its ",[24,1128,1130],{"href":26,"rel":1129},[28],"docs",[1132,1133,1134],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}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);}",{"title":77,"searchDepth":106,"depth":106,"links":1136},[1137,1138,1139,1142,1143,1144,1145,1146,1147],{"id":40,"depth":106,"text":41},{"id":62,"depth":106,"text":63},{"id":408,"depth":106,"text":409,"children":1140},[1141],{"id":420,"depth":121,"text":421},{"id":711,"depth":106,"text":712},{"id":926,"depth":106,"text":927},{"id":1018,"depth":106,"text":1019},{"id":1065,"depth":106,"text":1066},{"id":1102,"depth":106,"text":1103},{"id":1119,"depth":106,"text":1120},"2026-04-16","A practical, in-depth guide to protecting API endpoints using Bot Detector's layered pipeline, session binding, and custom checkers.","md",null,{"author":1153},"Sergio","/articles/protecting-apis","20 min read",{"title":5,"description":1149},{"loc":1154},"articles/protecting-apis",[1160,1161,1162],"Security","Bot Detection","API","lOSO7A4wmhrRrPVo0EQTBAKOOaAa50-U3Id81-Wk1o8",1776293248231]