[{"data":1,"prerenderedAt":582},["ShallowReactive",2],{"navigation_docs":3,"-docs-integration-guides-python":114,"-docs-integration-guides-python-surround":577},[4,8,12,16,20,24,28,32,36,40,44,82,86,90,94,98,102,106,110],{"title":5,"path":6,"stem":7},"Overview","\u002Fdocs","docs\u002Findex",{"title":9,"path":10,"stem":11},"Quickstart","\u002Fdocs\u002Fquickstart","docs\u002F01.quickstart",{"title":13,"path":14,"stem":15},"Authentication and keys","\u002Fdocs\u002Fauthentication","docs\u002F02.authentication",{"title":17,"path":18,"stem":19},"Images","\u002Fdocs\u002Fimages","docs\u002F03.images",{"title":21,"path":22,"stem":23},"Texts","\u002Fdocs\u002Ftexts","docs\u002F04.texts",{"title":25,"path":26,"stem":27},"Migrating from SightEngine","\u002Fdocs\u002Fmigrating-from-sightengine","docs\u002F05.migrating-from-sightengine",{"title":29,"path":30,"stem":31},"Workflows","\u002Fdocs\u002Fworkflows","docs\u002F06.workflows",{"title":33,"path":34,"stem":35},"Responses and errors","\u002Fdocs\u002Fresponses-and-errors","docs\u002F07.responses-and-errors",{"title":37,"path":38,"stem":39},"Limits and quotas","\u002Fdocs\u002Flimits-and-quotas","docs\u002F08.limits-and-quotas",{"title":41,"path":42,"stem":43},"The journal and ref","\u002Fdocs\u002Fjournal-and-ref","docs\u002F09.journal-and-ref",{"title":45,"path":46,"stem":47,"children":48,"page":81},"Integration guides","\u002Fdocs\u002Fintegration-guides","docs\u002F10.integration-guides",[49,53,57,61,65,69,73,77],{"title":50,"path":51,"stem":52},"Node.js and TypeScript","\u002Fdocs\u002Fintegration-guides\u002Fnodejs","docs\u002F10.integration-guides\u002F1.nodejs",{"title":54,"path":55,"stem":56},"Python","\u002Fdocs\u002Fintegration-guides\u002Fpython","docs\u002F10.integration-guides\u002F2.python",{"title":58,"path":59,"stem":60},"PHP","\u002Fdocs\u002Fintegration-guides\u002Fphp","docs\u002F10.integration-guides\u002F3.php",{"title":62,"path":63,"stem":64},"Go","\u002Fdocs\u002Fintegration-guides\u002Fgo","docs\u002F10.integration-guides\u002F4.go",{"title":66,"path":67,"stem":68},"Supabase Edge Functions","\u002Fdocs\u002Fintegration-guides\u002Fsupabase-edge-functions","docs\u002F10.integration-guides\u002F5.supabase-edge-functions",{"title":70,"path":71,"stem":72},"Cloudflare Workers","\u002Fdocs\u002Fintegration-guides\u002Fcloudflare-workers","docs\u002F10.integration-guides\u002F6.cloudflare-workers",{"title":74,"path":75,"stem":76},"Nuxt and Next","\u002Fdocs\u002Fintegration-guides\u002Fnuxt-and-next","docs\u002F10.integration-guides\u002F7.nuxt-and-next",{"title":78,"path":79,"stem":80},"Moderate an AI agent","\u002Fdocs\u002Fintegration-guides\u002Fmoderate-ai-agents","docs\u002F10.integration-guides\u002F8.moderate-ai-agents",false,{"title":83,"path":84,"stem":85},"AI coding agents","\u002Fdocs\u002Fai-agents","docs\u002F11.ai-agents",{"title":87,"path":88,"stem":89},"Best practices","\u002Fdocs\u002Fbest-practices","docs\u002F12.best-practices",{"title":91,"path":92,"stem":93},"Privacy and data","\u002Fdocs\u002Fprivacy-and-data","docs\u002F13.privacy-and-data",{"title":95,"path":96,"stem":97},"API reference","\u002Fdocs\u002Fapi-reference","docs\u002F14.api-reference",{"title":99,"path":100,"stem":101},"Fraud prevention","\u002Fdocs\u002Ffraud-prevention","docs\u002F16.fraud-prevention",{"title":103,"path":104,"stem":105},"Device token and challenges","\u002Fdocs\u002Fdevice-and-challenges","docs\u002F17.device-and-challenges",{"title":107,"path":108,"stem":109},"MCP server","\u002Fdocs\u002Fmcp-server","docs\u002F18.mcp-server",{"title":111,"path":112,"stem":113},"Changelog","\u002Fdocs\u002Fchangelog","docs\u002F99.changelog",{"id":115,"title":54,"body":116,"description":571,"extension":572,"links":573,"meta":574,"navigation":202,"path":55,"seo":575,"stem":56,"__hash__":576},"docs\u002Fdocs\u002F10.integration-guides\u002F2.python.md",{"type":117,"value":118,"toc":566},"minimark",[119,136,141,144,180,184,435,439,552,562],[120,121,122,123,130,131,135],"p",{},"This guide uses ",[124,125,129],"a",{"href":126,"rel":127},"https:\u002F\u002Frequests.readthedocs.io",[128],"nofollow","requests",". Install it with ",[132,133,134],"code",{},"pip install requests",".",[137,138,140],"h2",{"id":139},"set-the-key","Set the key",[120,142,143],{},"Put the key in the environment of your server, never in the code:",[145,146,151],"pre",{"className":147,"code":148,"language":149,"meta":150,"style":150},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export OPENSHIELD_API_KEY=\"om_live_...\"\n","bash","",[132,152,153],{"__ignoreMap":150},[154,155,158,162,166,170,173,177],"span",{"class":156,"line":157},"line",1,[154,159,161],{"class":160},"spNyl","export",[154,163,165],{"class":164},"sTEyZ"," OPENSHIELD_API_KEY",[154,167,169],{"class":168},"sMK4o","=",[154,171,172],{"class":168},"\"",[154,174,176],{"class":175},"sfazB","om_live_...",[154,178,179],{"class":168},"\"\n",[137,181,183],{"id":182},"the-module","The module",[145,185,190],{"className":186,"code":187,"filename":188,"language":189,"meta":150,"style":150},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import os\n\nimport requests\n\nBASE = \"https:\u002F\u002Fapi.openshield.dev\"\nSESSION = requests.Session()\n\n\nclass ModerationError(Exception):\n    def __init__(self, status, error_type, message, retry_after=None):\n        super().__init__(message)\n        self.status = status\n        self.type = error_type\n        self.retry_after = retry_after\n\n\ndef _send(path, **kwargs):\n    headers = {\"Authorization\": f\"Bearer {os.environ['OPENSHIELD_API_KEY']}\"}\n    response = SESSION.post(BASE + path, headers=headers, timeout=10, **kwargs)\n    body = response.json()\n    if not response.ok:\n        error = body.get(\"error\", {})\n        raise ModerationError(\n            response.status_code,\n            error.get(\"type\", \"unknown\"),\n            error.get(\"message\", response.reason),\n            response.headers.get(\"Retry-After\"),\n        )\n    summary = body[\"summary\"]\n    return {\n        \"action\": summary[\"action\"],\n        \"reasons\": summary[\"reject_reason\"] + summary[\"review_reason\"],\n        \"request_id\": body[\"request_id\"],\n    }\n\n\ndef check_text(text, ref=None):\n    return _send(\"\u002Fv1\u002Ftext\", json={\"text\": text, \"ref\": ref})\n\n\ndef check_image(data, ref=None):\n    return _send(\"\u002Fv1\u002Fimage\", params={\"ref\": ref} if ref else None, data=data)\n","moderation.py","python",[132,191,192,197,204,210,215,221,227,232,237,243,249,255,261,267,273,278,283,289,295,301,307,313,319,325,331,337,343,349,355,361,367,373,379,385,391,396,401,407,413,418,423,429],{"__ignoreMap":150},[154,193,194],{"class":156,"line":157},[154,195,196],{},"import os\n",[154,198,200],{"class":156,"line":199},2,[154,201,203],{"emptyLinePlaceholder":202},true,"\n",[154,205,207],{"class":156,"line":206},3,[154,208,209],{},"import requests\n",[154,211,213],{"class":156,"line":212},4,[154,214,203],{"emptyLinePlaceholder":202},[154,216,218],{"class":156,"line":217},5,[154,219,220],{},"BASE = \"https:\u002F\u002Fapi.openshield.dev\"\n",[154,222,224],{"class":156,"line":223},6,[154,225,226],{},"SESSION = requests.Session()\n",[154,228,230],{"class":156,"line":229},7,[154,231,203],{"emptyLinePlaceholder":202},[154,233,235],{"class":156,"line":234},8,[154,236,203],{"emptyLinePlaceholder":202},[154,238,240],{"class":156,"line":239},9,[154,241,242],{},"class ModerationError(Exception):\n",[154,244,246],{"class":156,"line":245},10,[154,247,248],{},"    def __init__(self, status, error_type, message, retry_after=None):\n",[154,250,252],{"class":156,"line":251},11,[154,253,254],{},"        super().__init__(message)\n",[154,256,258],{"class":156,"line":257},12,[154,259,260],{},"        self.status = status\n",[154,262,264],{"class":156,"line":263},13,[154,265,266],{},"        self.type = error_type\n",[154,268,270],{"class":156,"line":269},14,[154,271,272],{},"        self.retry_after = retry_after\n",[154,274,276],{"class":156,"line":275},15,[154,277,203],{"emptyLinePlaceholder":202},[154,279,281],{"class":156,"line":280},16,[154,282,203],{"emptyLinePlaceholder":202},[154,284,286],{"class":156,"line":285},17,[154,287,288],{},"def _send(path, **kwargs):\n",[154,290,292],{"class":156,"line":291},18,[154,293,294],{},"    headers = {\"Authorization\": f\"Bearer {os.environ['OPENSHIELD_API_KEY']}\"}\n",[154,296,298],{"class":156,"line":297},19,[154,299,300],{},"    response = SESSION.post(BASE + path, headers=headers, timeout=10, **kwargs)\n",[154,302,304],{"class":156,"line":303},20,[154,305,306],{},"    body = response.json()\n",[154,308,310],{"class":156,"line":309},21,[154,311,312],{},"    if not response.ok:\n",[154,314,316],{"class":156,"line":315},22,[154,317,318],{},"        error = body.get(\"error\", {})\n",[154,320,322],{"class":156,"line":321},23,[154,323,324],{},"        raise ModerationError(\n",[154,326,328],{"class":156,"line":327},24,[154,329,330],{},"            response.status_code,\n",[154,332,334],{"class":156,"line":333},25,[154,335,336],{},"            error.get(\"type\", \"unknown\"),\n",[154,338,340],{"class":156,"line":339},26,[154,341,342],{},"            error.get(\"message\", response.reason),\n",[154,344,346],{"class":156,"line":345},27,[154,347,348],{},"            response.headers.get(\"Retry-After\"),\n",[154,350,352],{"class":156,"line":351},28,[154,353,354],{},"        )\n",[154,356,358],{"class":156,"line":357},29,[154,359,360],{},"    summary = body[\"summary\"]\n",[154,362,364],{"class":156,"line":363},30,[154,365,366],{},"    return {\n",[154,368,370],{"class":156,"line":369},31,[154,371,372],{},"        \"action\": summary[\"action\"],\n",[154,374,376],{"class":156,"line":375},32,[154,377,378],{},"        \"reasons\": summary[\"reject_reason\"] + summary[\"review_reason\"],\n",[154,380,382],{"class":156,"line":381},33,[154,383,384],{},"        \"request_id\": body[\"request_id\"],\n",[154,386,388],{"class":156,"line":387},34,[154,389,390],{},"    }\n",[154,392,394],{"class":156,"line":393},35,[154,395,203],{"emptyLinePlaceholder":202},[154,397,399],{"class":156,"line":398},36,[154,400,203],{"emptyLinePlaceholder":202},[154,402,404],{"class":156,"line":403},37,[154,405,406],{},"def check_text(text, ref=None):\n",[154,408,410],{"class":156,"line":409},38,[154,411,412],{},"    return _send(\"\u002Fv1\u002Ftext\", json={\"text\": text, \"ref\": ref})\n",[154,414,416],{"class":156,"line":415},39,[154,417,203],{"emptyLinePlaceholder":202},[154,419,421],{"class":156,"line":420},40,[154,422,203],{"emptyLinePlaceholder":202},[154,424,426],{"class":156,"line":425},41,[154,427,428],{},"def check_image(data, ref=None):\n",[154,430,432],{"class":156,"line":431},42,[154,433,434],{},"    return _send(\"\u002Fv1\u002Fimage\", params={\"ref\": ref} if ref else None, data=data)\n",[137,436,438],{"id":437},"use-it-and-fail-closed","Use it, and fail closed",[145,440,442],{"className":186,"code":441,"language":189,"meta":150,"style":150},"import requests\n\nfrom moderation import ModerationError, check_text\n\n\ndef moderate_comment(text, comment_id):\n    \"\"\"Returns \"publish\", \"hold\" or \"refuse\".\"\"\"\n    try:\n        decision = check_text(text, comment_id)\n    except ModerationError as error:\n        # The content cannot be accepted as it is: refuse it.\n        if error.status in (400, 413, 415):\n            return \"refuse\"\n        return \"hold\"\n    except requests.RequestException:\n        # A timeout or a network error: keep it hidden until someone looks at it.\n        return \"hold\"\n\n    if decision[\"action\"] == \"accept\":\n        return \"publish\"\n    if decision[\"action\"] == \"review\":\n        return \"hold\"\n    return \"refuse\"\n",[132,443,444,448,452,457,461,465,470,475,480,485,490,495,500,505,510,515,520,524,528,533,538,543,547],{"__ignoreMap":150},[154,445,446],{"class":156,"line":157},[154,447,209],{},[154,449,450],{"class":156,"line":199},[154,451,203],{"emptyLinePlaceholder":202},[154,453,454],{"class":156,"line":206},[154,455,456],{},"from moderation import ModerationError, check_text\n",[154,458,459],{"class":156,"line":212},[154,460,203],{"emptyLinePlaceholder":202},[154,462,463],{"class":156,"line":217},[154,464,203],{"emptyLinePlaceholder":202},[154,466,467],{"class":156,"line":223},[154,468,469],{},"def moderate_comment(text, comment_id):\n",[154,471,472],{"class":156,"line":229},[154,473,474],{},"    \"\"\"Returns \"publish\", \"hold\" or \"refuse\".\"\"\"\n",[154,476,477],{"class":156,"line":234},[154,478,479],{},"    try:\n",[154,481,482],{"class":156,"line":239},[154,483,484],{},"        decision = check_text(text, comment_id)\n",[154,486,487],{"class":156,"line":245},[154,488,489],{},"    except ModerationError as error:\n",[154,491,492],{"class":156,"line":251},[154,493,494],{},"        # The content cannot be accepted as it is: refuse it.\n",[154,496,497],{"class":156,"line":257},[154,498,499],{},"        if error.status in (400, 413, 415):\n",[154,501,502],{"class":156,"line":263},[154,503,504],{},"            return \"refuse\"\n",[154,506,507],{"class":156,"line":269},[154,508,509],{},"        return \"hold\"\n",[154,511,512],{"class":156,"line":275},[154,513,514],{},"    except requests.RequestException:\n",[154,516,517],{"class":156,"line":280},[154,518,519],{},"        # A timeout or a network error: keep it hidden until someone looks at it.\n",[154,521,522],{"class":156,"line":285},[154,523,509],{},[154,525,526],{"class":156,"line":291},[154,527,203],{"emptyLinePlaceholder":202},[154,529,530],{"class":156,"line":297},[154,531,532],{},"    if decision[\"action\"] == \"accept\":\n",[154,534,535],{"class":156,"line":303},[154,536,537],{},"        return \"publish\"\n",[154,539,540],{"class":156,"line":309},[154,541,542],{},"    if decision[\"action\"] == \"review\":\n",[154,544,545],{"class":156,"line":315},[154,546,509],{},[154,548,549],{"class":156,"line":321},[154,550,551],{},"    return \"refuse\"\n",[120,553,554,555,558,559,561],{},"For rejected content, show your user a message chosen from the ",[132,556,557],{},"id"," of the first reason, in your own words and your own languages. ",[124,560,87],{"href":88}," says more.",[563,564,565],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .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":150,"searchDepth":199,"depth":199,"links":567},[568,569,570],{"id":139,"depth":199,"text":140},{"id":182,"depth":199,"text":183},{"id":437,"depth":199,"text":438},"A moderation module for Python 3 with the requests library.","md",null,{},{"title":54,"description":571},"SBOClortPX5DqZZoYijviqGI0hNCPt-MutTEJMoDtSk",[578,580],{"title":50,"path":51,"stem":52,"description":579,"children":-1},"A small moderation module for Node.js 18 or later, with timeouts and a decision that fails closed.",{"title":58,"path":59,"stem":60,"description":581,"children":-1},"A moderation helper for PHP 8.1 or later with cURL, and no dependency.",1792938463369]