[{"data":1,"prerenderedAt":851},["ShallowReactive",2],{"navigation":3,"\u002Fintegrations\u002Fapi-keys":189,"\u002Fintegrations\u002Fapi-keys-surround":846},[4,34,60,116,137,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":33},"Getting Started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,18,23,28],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-house",{"title":5,"path":16,"stem":17,"icon":6},"\u002Fgetting-started\u002Fgetting-started","1.getting-started\u002F2.getting-started",{"title":19,"path":20,"stem":21,"icon":22},"Choosing a Preset","\u002Fgetting-started\u002Fchoosing-a-preset","1.getting-started\u002F3.choosing-a-preset","i-lucide-git-branch",{"title":24,"path":25,"stem":26,"icon":27},"Deployment","\u002Fgetting-started\u002Fdeployment","1.getting-started\u002F4.deployment","i-lucide-cloud",{"title":29,"path":30,"stem":31,"icon":32},"Background Maintenance","\u002Fgetting-started\u002Fbackground-maintenance","1.getting-started\u002F5.background-maintenance","i-lucide-timer-reset",false,{"title":35,"icon":36,"path":37,"stem":38,"children":39,"page":33},"Build","i-lucide-wrench","\u002Fbuild","2.build",[40,45,50,55],{"title":41,"path":42,"stem":43,"icon":44},"Routers & Prefixes","\u002Fbuild\u002Frouters-and-prefixes","2.build\u002F1.routers-and-prefixes","i-lucide-route",{"title":46,"path":47,"stem":48,"icon":49},"Configuration","\u002Fbuild\u002Fconfiguration","2.build\u002F2.configuration","i-lucide-settings",{"title":51,"path":52,"stem":53,"icon":54},"Authorization Dependencies","\u002Fbuild\u002Fauthorization-dependencies","2.build\u002F3.authorization-dependencies","i-lucide-shield-check",{"title":56,"path":57,"stem":58,"icon":59},"Command Line","\u002Fbuild\u002Fcli","2.build\u002F4.cli","i-lucide-terminal",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":33},"Auth","i-lucide-lock","\u002Fauth","3.auth",[66,71,76,81,86,91,96,101,106,111],{"title":67,"path":68,"stem":69,"icon":70},"OAuth & Social Login","\u002Fauth\u002Foauth-and-social-login","3.auth\u002F1.oauth-and-social-login","i-lucide-log-in",{"title":72,"path":73,"stem":74,"icon":75},"Multi-Frontend Support","\u002Fauth\u002Fmulti-frontend","3.auth\u002F10.multi-frontend","i-lucide-layout-grid",{"title":77,"path":78,"stem":79,"icon":80},"Sessions & Audit","\u002Fauth\u002Fsessions-and-audit","3.auth\u002F2.sessions-and-audit","i-lucide-monitor-smartphone",{"title":82,"path":83,"stem":84,"icon":85},"Passwordless & Messaging","\u002Fauth\u002Fpasswordless-and-messaging","3.auth\u002F3.passwordless-and-messaging","i-lucide-mail",{"title":87,"path":88,"stem":89,"icon":90},"JWT Tokens","\u002Fauth\u002Fjwt-tokens","3.auth\u002F4.jwt-tokens","i-lucide-key-round",{"title":92,"path":93,"stem":94,"icon":95},"User Management API","\u002Fauth\u002Fuser-management-api","3.auth\u002F5.user-management-api","i-lucide-users",{"title":97,"path":98,"stem":99,"icon":100},"User Invitations","\u002Fauth\u002Fuser-invitations","3.auth\u002F6.user-invitations","i-lucide-send",{"title":102,"path":103,"stem":104,"icon":105},"Roles & Permissions","\u002Fauth\u002Froles-and-permissions","3.auth\u002F7.roles-and-permissions","i-lucide-shield",{"title":107,"path":108,"stem":109,"icon":110},"ABAC","\u002Fauth\u002Fabac","3.auth\u002F8.abac","i-lucide-filter",{"title":112,"path":113,"stem":114,"icon":115},"User Status","\u002Fauth\u002Fuser-status","3.auth\u002F9.user-status","i-lucide-user-cog",{"title":117,"icon":118,"path":119,"stem":120,"children":121,"page":33},"Enterprise","i-lucide-building-2","\u002Fenterprise","4.enterprise",[122,127,132],{"title":123,"path":124,"stem":125,"icon":126},"Core Authorization Concepts","\u002Fenterprise\u002Fcore-authorization-concepts","4.enterprise\u002F1.core-authorization-concepts","i-lucide-network",{"title":128,"path":129,"stem":130,"icon":131},"Entities","\u002Fenterprise\u002Fentities","4.enterprise\u002F2.entities","i-lucide-folder-tree",{"title":133,"path":134,"stem":135,"icon":136},"Entity Memberships","\u002Fenterprise\u002Fentity-memberships","4.enterprise\u002F3.entity-memberships","i-lucide-user-plus",{"title":138,"icon":139,"path":140,"stem":141,"children":142,"page":33},"Integrations","i-lucide-plug","\u002Fintegrations","5.integrations",[143,148],{"title":144,"path":145,"stem":146,"icon":147},"API Keys","\u002Fintegrations\u002Fapi-keys","5.integrations\u002F1.api-keys","i-lucide-key",{"title":149,"path":150,"stem":151,"icon":152},"OutlabsAuth UI","\u002Fintegrations\u002Foutlabsauth-ui","5.integrations\u002F2.outlabsauth-ui","i-lucide-layout-dashboard",{"title":154,"icon":155,"path":156,"stem":157,"children":158,"page":33},"Reference","i-lucide-book-marked","\u002Freference","6.reference",[159,164,169,174,179,184],{"title":160,"path":161,"stem":162,"icon":163},"Data Models","\u002Freference\u002Fdata-models","6.reference\u002F1.data-models","i-lucide-database",{"title":165,"path":166,"stem":167,"icon":168},"Activity Tracking","\u002Freference\u002Factivity-tracking","6.reference\u002F2.activity-tracking","i-lucide-activity",{"title":170,"path":171,"stem":172,"icon":173},"Testing","\u002Freference\u002Ftesting","6.reference\u002F3.testing","i-lucide-flask-conical",{"title":175,"path":176,"stem":177,"icon":178},"Observability","\u002Freference\u002Fobservability","6.reference\u002F4.observability","i-lucide-eye",{"title":180,"path":181,"stem":182,"icon":183},"Metrics Reference","\u002Freference\u002Fmetrics-reference","6.reference\u002F5.metrics-reference","i-lucide-chart-bar",{"title":185,"path":186,"stem":187,"icon":188},"Log Events Reference","\u002Freference\u002Flog-events-reference","6.reference\u002F6.log-events-reference","i-lucide-scroll-text",{"id":190,"title":144,"body":191,"description":839,"extension":840,"links":841,"meta":842,"navigation":843,"path":145,"seo":844,"stem":146,"__hash__":845},"docs\u002F5.integrations\u002F1.api-keys.md",{"type":192,"value":193,"toc":824},"minimark",[194,201,204,257,283,349,355,358,363,372,375,382,388,404,407,414,417,448,458,460,464,508,530,533,601,603,607,656,666,668,672,715,734,739,743,746,757,760,798,802,820],[195,196,197],"note",{},[198,199,200],"p",{},"Issue and check API keys from your FastAPI host.",[198,202,203],{},"Two key kinds, three routers to mount, one runtime helper for your own routes.",[205,206,207,223],"table",{},[208,209,210],"thead",{},[211,212,213,217,220],"tr",{},[214,215,216],"th",{},"Kind",[214,218,219],{},"Owner",[214,221,222],{},"Typical use",[224,225,226,241],"tbody",{},[211,227,228,235,238],{},[229,230,231],"td",{},[232,233,234],"code",{},"personal",[229,236,237],{},"A user",[229,239,240],{},"Self-service keys that act as that user",[211,242,243,248,254],{},[229,244,245],{},[232,246,247],{},"system_integration",[229,249,250,251],{},"An ",[232,252,253],{},"IntegrationPrincipal",[229,255,256],{},"Non-human \u002F service integrations (admin-managed)",[198,258,259,263,264,267,268,271,274,275,278,279,282],{},[260,261,262],"strong",{},"Do:"," mount the routers below and call ",[232,265,266],{},"auth.authorize_api_key(...)",".",[269,270],"br",{},[260,272,273],{},"Don’t:"," read ",[232,276,277],{},"api_keys"," tables directly or call ",[232,280,281],{},"api_key_service.verify_api_key","\nfrom host code.",[284,285,290],"pre",{"className":286,"code":287,"language":288,"meta":289,"style":289},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from outlabs_auth.routers import (\n    get_api_keys_router,\n    get_api_key_admin_router,\n    get_integration_principals_router,\n)\n\napp.include_router(get_api_keys_router(auth, prefix=\"\u002Fv1\u002Fapi-keys\"))\napp.include_router(get_integration_principals_router(auth, prefix=\"\u002Fv1\u002Fadmin\"))\napp.include_router(get_api_key_admin_router(auth, prefix=\"\u002Fv1\u002Fadmin\u002Fentities\"))\n","python","",[232,291,292,300,306,312,318,324,331,337,343],{"__ignoreMap":289},[293,294,297],"span",{"class":295,"line":296},"line",1,[293,298,299],{},"from outlabs_auth.routers import (\n",[293,301,303],{"class":295,"line":302},2,[293,304,305],{},"    get_api_keys_router,\n",[293,307,309],{"class":295,"line":308},3,[293,310,311],{},"    get_api_key_admin_router,\n",[293,313,315],{"class":295,"line":314},4,[293,316,317],{},"    get_integration_principals_router,\n",[293,319,321],{"class":295,"line":320},5,[293,322,323],{},")\n",[293,325,327],{"class":295,"line":326},6,[293,328,330],{"emptyLinePlaceholder":329},true,"\n",[293,332,334],{"class":295,"line":333},7,[293,335,336],{},"app.include_router(get_api_keys_router(auth, prefix=\"\u002Fv1\u002Fapi-keys\"))\n",[293,338,340],{"class":295,"line":339},8,[293,341,342],{},"app.include_router(get_integration_principals_router(auth, prefix=\"\u002Fv1\u002Fadmin\"))\n",[293,344,346],{"class":295,"line":345},9,[293,347,348],{},"app.include_router(get_api_key_admin_router(auth, prefix=\"\u002Fv1\u002Fadmin\u002Fentities\"))\n",[198,350,351,352,267],{},"Admin listing of another user’s personal keys also lives on the users router —\n",[353,354,92],"a",{"href":93},[356,357],"hr",{},[359,360,362],"h2",{"id":361},"routers","Routers",[364,365,367,368,371],"h3",{"id":366},"personal-v1api-keys","Personal (",[232,369,370],{},"\u002Fv1\u002Fapi-keys",")",[198,373,374],{},"Self-service CRUD + rotate for the authenticated user’s own keys.",[364,376,378,379,371],{"id":377},"integration-principals-v1admin","Integration principals (",[232,380,381],{},"\u002Fv1\u002Fadmin",[198,383,384,385,387],{},"Enterprise-oriented admin for ",[232,386,247],{}," keys:",[389,390,391,398],"ul",{},[392,393,394,395],"li",{},"Entity-scoped: ",[232,396,397],{},"\u002Fentities\u002F{entity_id}\u002Fintegration-principals[\u002F{id}\u002Fapi-keys…]",[392,399,400,401],{},"Platform-global (superuser): ",[232,402,403],{},"\u002Fsystem\u002Fintegration-principals[…]",[198,405,406],{},"Includes create \u002F patch \u002F delete principal, list\u002Fcreate\u002Frotate\u002Frevoke keys.",[364,408,410,411,371],{"id":409},"entity-inventory-v1adminentities","Entity inventory (",[232,412,413],{},"\u002Fv1\u002Fadmin\u002Fentities",[198,415,416],{},"Incident response across keys anchored to an entity:",[389,418,419,438,443],{},[392,420,421,424,425,428,429,428,432,428,435,371],{},[232,422,423],{},"GET \u002F{entity_id}\u002Fapi-keys"," (filters: ",[232,426,427],{},"owner_id",", ",[232,430,431],{},"status",[232,433,434],{},"key_kind",[232,436,437],{},"search",[392,439,440],{},[232,441,442],{},"GET \u002F{entity_id}\u002Fapi-keys\u002F{key_id}",[392,444,445],{},[232,446,447],{},"DELETE \u002F{entity_id}\u002Fapi-keys\u002F{key_id}",[198,449,450,451,454,455,267],{},"Responses include derived ",[232,452,453],{},"is_currently_effective"," \u002F ",[232,456,457],{},"ineffective_reasons",[356,459],{},[359,461,463],{"id":462},"runtime-authorization","Runtime authorization",[284,465,467],{"className":286,"code":466,"language":288,"meta":289,"style":289},"auth_result = await auth.authorize_api_key(\n    session,\n    api_key_string,  # e.g. from X-API-Key\n    required_scope=\"contacts:read\",\n    entity_id=entity_id,\n    ip_address=client_ip,\n)\n# None → deny; else dict with source=\"api_key\", scopes, owner metadata, …\n",[232,468,469,474,479,484,489,494,499,503],{"__ignoreMap":289},[293,470,471],{"class":295,"line":296},[293,472,473],{},"auth_result = await auth.authorize_api_key(\n",[293,475,476],{"class":295,"line":302},[293,477,478],{},"    session,\n",[293,480,481],{"class":295,"line":308},[293,482,483],{},"    api_key_string,  # e.g. from X-API-Key\n",[293,485,486],{"class":295,"line":314},[293,487,488],{},"    required_scope=\"contacts:read\",\n",[293,490,491],{"class":295,"line":320},[293,492,493],{},"    entity_id=entity_id,\n",[293,495,496],{"class":295,"line":326},[293,497,498],{},"    ip_address=client_ip,\n",[293,500,501],{"class":295,"line":333},[293,502,323],{},[293,504,505],{"class":295,"line":339},[293,506,507],{},"# None → deny; else dict with source=\"api_key\", scopes, owner metadata, …\n",[198,509,510,513,514,454,517,267,520,522,525,526,529],{},[260,511,512],{},"Personal keys"," resolve a ",[232,515,516],{},"user",[232,518,519],{},"user_id",[269,521],{},[260,523,524],{},"System integration keys"," resolve an ",[232,527,528],{},"integration_principal"," (no user).",[198,531,532],{},"Host pattern sketch:",[284,534,536],{"className":286,"code":535,"language":288,"meta":289,"style":289},"@router.get(\"\u002Fcontacts\u002F{entity_id}\")\nasync def list_contacts(entity_id: UUID, request: Request, session=Depends(auth.uow)):\n    result = await auth.authorize_api_key(\n        session,\n        request.headers.get(\"X-API-Key\"),\n        required_scope=\"contacts:read\",\n        entity_id=entity_id,\n        ip_address=request.client.host if request.client else None,\n    )\n    if result is None:\n        raise HTTPException(status_code=401, detail=\"Invalid API key\")\n    ...\n",[232,537,538,543,548,553,558,563,568,573,578,583,589,595],{"__ignoreMap":289},[293,539,540],{"class":295,"line":296},[293,541,542],{},"@router.get(\"\u002Fcontacts\u002F{entity_id}\")\n",[293,544,545],{"class":295,"line":302},[293,546,547],{},"async def list_contacts(entity_id: UUID, request: Request, session=Depends(auth.uow)):\n",[293,549,550],{"class":295,"line":308},[293,551,552],{},"    result = await auth.authorize_api_key(\n",[293,554,555],{"class":295,"line":314},[293,556,557],{},"        session,\n",[293,559,560],{"class":295,"line":320},[293,561,562],{},"        request.headers.get(\"X-API-Key\"),\n",[293,564,565],{"class":295,"line":326},[293,566,567],{},"        required_scope=\"contacts:read\",\n",[293,569,570],{"class":295,"line":333},[293,571,572],{},"        entity_id=entity_id,\n",[293,574,575],{"class":295,"line":339},[293,576,577],{},"        ip_address=request.client.host if request.client else None,\n",[293,579,580],{"class":295,"line":345},[293,581,582],{},"    )\n",[293,584,586],{"class":295,"line":585},10,[293,587,588],{},"    if result is None:\n",[293,590,592],{"class":295,"line":591},11,[293,593,594],{},"        raise HTTPException(status_code=401, detail=\"Invalid API key\")\n",[293,596,598],{"class":295,"line":597},12,[293,599,600],{},"    ...\n",[356,602],{},[359,604,606],{"id":605},"simple-vs-enterprise","Simple vs Enterprise",[205,608,609,621],{},[208,610,611],{},[211,612,613,615,618],{},[214,614],{},[214,616,617],{},"SimpleRBAC",[214,619,620],{},"EnterpriseRBAC",[224,622,623,632,646],{},[211,624,625,627,630],{},[229,626,512],{},[229,628,629],{},"Yes",[229,631,629],{},[211,633,634,637,644],{},[229,635,636],{},"Integration principals \u002F system keys",[229,638,639,640,643],{},"No (use JWT ",[260,641,642],{},"service tokens"," for internal automation)",[229,645,629],{},[211,647,648,651,654],{},[229,649,650],{},"Entity inventory admin router",[229,652,653],{},"Optional \u002F limited",[229,655,629],{},[198,657,658,659,662,663,665],{},"Enterprise runtime also folds in owner\u002Fprincipal activity, allowlists, entity\nscope, and stored scopes. ",[260,660,661],{},"Principal-backed keys are RBAC-only"," — if ABAC is\non and the required permission has conditions, those keys are denied\n(",[353,664,107],{"href":108},").",[356,667],{},[359,669,671],{"id":670},"credential-chooser","Credential chooser",[205,673,674,684],{},[208,675,676],{},[211,677,678,681],{},[214,679,680],{},"Need",[214,682,683],{},"Use",[224,685,686,696,705],{},[211,687,688,691],{},[229,689,690],{},"Human-owned automation",[229,692,693,695],{},[232,694,234],{}," API key",[211,697,698,701],{},[229,699,700],{},"Durable non-human key with inventory \u002F rotate \u002F IP allowlist",[229,702,703],{},[232,704,247],{},[211,706,707,710],{},[229,708,709],{},"Internal platform service without DB-managed key lifecycle",[229,711,712,713],{},"JWT ",[260,714,642],{},[198,716,717,718,267,726,728,729,267],{},"Example personas and mounts: ",[353,719,723],{"href":720,"rel":721},"https:\u002F\u002Fgithub.com\u002Foutlabsio\u002FoutlabsAuth\u002Ftree\u002Fmain\u002Fexamples\u002Fenterprise_rbac\u002F",[722],"nofollow",[232,724,725],{},"examples\u002Fenterprise_rbac",[269,727],{},"\nDesign epic (maintainer): ",[353,730,731],{"href":145},[232,732,733],{},"docs\u002FAPI_KEY_SCOPE_AND_GRANT_POLICY_EPIC.md",[198,735,736,737,267],{},"Auth-layer metrics\u002Flogs cover validation, denials, rate limits, and lifecycle —\nsee ",[353,738,175],{"href":176},[359,740,742],{"id":741},"distributed-rate-limits","Distributed rate limits",[198,744,745],{},"Configured API-key minute, hour and day limits are Redis-backed fixed windows.\nThe expiry is anchored to the first request in a window; later requests increment\nthe counter without extending it. OutlabsAuth also repairs a counter that Redis\nreports without an expiry, so legacy state or an expiry-boundary recreation cannot\nleave a key permanently rate-limited.",[198,747,748,749,752,753,756],{},"Treat Redis as required infrastructure when any API-key quota is configured. The\ndefault ",[232,750,751],{},"api_key_rate_limit_failure_mode=\"fail_closed\""," rejects authorization if\nthe distributed counter cannot be enforced. Monitor persistent 429s together with\nthe counter TTL: ",[232,754,755],{},"TTL=-1"," is invalid for a configured rate-limit window, while a\npositive TTL is the expected fixed-window state.",[198,758,759],{},"Library contributors should exercise these semantics against a real server:",[284,761,766],{"className":762,"code":763,"filename":764,"language":765,"meta":289,"style":289},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","TEST_REDIS_REQUIRED=1 uv run pytest -m redis\n","Terminal","bash",[232,767,768],{"__ignoreMap":289},[293,769,770,774,778,782,786,789,792,795],{"class":295,"line":296},[293,771,773],{"class":772},"sTEyZ","TEST_REDIS_REQUIRED",[293,775,777],{"class":776},"sMK4o","=",[293,779,781],{"class":780},"sfazB","1",[293,783,785],{"class":784},"sBMFI"," uv",[293,787,788],{"class":780}," run",[293,790,791],{"class":780}," pytest",[293,793,794],{"class":780}," -m",[293,796,797],{"class":780}," redis\n",[359,799,801],{"id":800},"related","Related",[389,803,804,808,812,816],{},[392,805,806],{},[353,807,41],{"href":42},[392,809,810],{},[353,811,92],{"href":93},[392,813,814],{},[353,815,107],{"href":108},[392,817,818],{},[353,819,149],{"href":150},[821,822,823],"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":289,"searchDepth":296,"depth":302,"links":825},[826,834,835,836,837,838],{"id":361,"depth":302,"text":362,"children":827},[828,830,832],{"id":366,"depth":308,"text":829},"Personal (\u002Fv1\u002Fapi-keys)",{"id":377,"depth":308,"text":831},"Integration principals (\u002Fv1\u002Fadmin)",{"id":409,"depth":308,"text":833},"Entity inventory (\u002Fv1\u002Fadmin\u002Fentities)",{"id":462,"depth":302,"text":463},{"id":605,"depth":302,"text":606},{"id":670,"depth":302,"text":671},{"id":741,"depth":302,"text":742},{"id":800,"depth":302,"text":801},"Personal and system integration keys for host apps.","md",null,{},{"icon":147},{"title":144,"description":839},"iX6XVEGmHiVpcT7IfsIWRuRjqTtRHoyjBnion755FLM",[847,849],{"title":133,"path":134,"stem":135,"description":848,"icon":136,"children":-1},"Enterprise membership lifecycle.",{"title":149,"path":150,"stem":151,"description":850,"icon":152,"children":-1},"Optional sidecar admin console — point it at any FastAPI host running OutlabsAuth and manage the full surface.",1787472744721]