[{"data":1,"prerenderedAt":636},["ShallowReactive",2],{"navigation_docs":3,"-docs-integration-guides-php":114,"-docs-integration-guides-php-surround":631},[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":58,"body":116,"description":625,"extension":626,"links":627,"meta":628,"navigation":167,"path":59,"seo":629,"stem":60,"__hash__":630},"docs\u002Fdocs\u002F10.integration-guides\u002F3.php.md",{"type":117,"value":118,"toc":620},"minimark",[119,123,128,140,144,493,497,605,616],[120,121,122],"p",{},"This guide needs PHP 8.1 or later and the cURL extension, which ships with most PHP installations.",[124,125,127],"h2",{"id":126},"set-the-key","Set the key",[120,129,130,131,135,136,139],{},"Put the key in the environment of your server, never in the code. For PHP-FPM, set it in the pool's ",[132,133,134],"code",{},"env[OPENSHIELD_API_KEY]"," or in your framework's secret store, and read it with ",[132,137,138],{},"getenv()",".",[124,141,143],{"id":142},"the-helper","The helper",[145,146,152],"pre",{"className":147,"code":148,"filename":149,"language":150,"meta":151,"style":151},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C?php\n\ndeclare(strict_types=1);\n\nconst OPENMOD_BASE = 'https:\u002F\u002Fapi.openshield.dev';\n\nfinal class ModerationError extends RuntimeException\n{\n    public function __construct(public readonly int $status, public readonly string $type, string $message, public readonly ?int $retryAfter = null)\n    {\n        parent::__construct($message, $status);\n    }\n}\n\nfunction openmod_send(string $path, string $body, array $headers = []): array\n{\n    $key = getenv('OPENSHIELD_API_KEY');\n    if ($key === false || $key === '') {\n        throw new RuntimeException('OPENSHIELD_API_KEY is not set');\n    }\n\n    $curl = curl_init(OPENMOD_BASE . $path);\n    curl_setopt_array($curl, [\n        CURLOPT_POST => true,\n        CURLOPT_POSTFIELDS => $body,\n        CURLOPT_HTTPHEADER => array_merge(['Authorization: Bearer ' . $key], $headers),\n        CURLOPT_RETURNTRANSFER => true,\n        CURLOPT_HEADER => false,\n        CURLOPT_TIMEOUT => 10,\n    ]);\n    $raw = curl_exec($curl);\n    $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);\n\n    if ($raw === false) {\n        throw new RuntimeException('openshield could not be reached: ' . curl_error($curl));\n    }\n\n    $answer = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);\n    if ($status !== 200) {\n        $error = $answer['error'] ?? [];\n        throw new ModerationError($status, $error['type'] ?? 'unknown', $error['message'] ?? 'The check failed');\n    }\n\n    return [\n        'action' => $answer['summary']['action'],\n        'reasons' => array_merge($answer['summary']['reject_reason'], $answer['summary']['review_reason']),\n        'request_id' => $answer['request_id'],\n    ];\n}\n\nfunction openmod_check_text(string $text, ?string $ref = null): array\n{\n    return openmod_send('\u002Fv1\u002Ftext', json_encode(['text' => $text, 'ref' => $ref], JSON_THROW_ON_ERROR), ['Content-Type: application\u002Fjson']);\n}\n\nfunction openmod_check_image(string $bytes, ?string $ref = null): array\n{\n    return openmod_send('\u002Fv1\u002Fimage' . ($ref === null ? '' : '?ref=' . rawurlencode($ref)), $bytes);\n}\n","moderation.php","php","",[132,153,154,162,169,175,180,186,191,197,203,209,215,221,227,233,238,244,249,255,261,267,272,277,283,289,295,301,307,313,319,325,331,337,343,348,354,360,365,370,376,382,388,394,399,404,410,416,422,428,434,439,444,450,455,461,466,471,477,482,488],{"__ignoreMap":151},[155,156,159],"span",{"class":157,"line":158},"line",1,[155,160,161],{},"\u003C?php\n",[155,163,165],{"class":157,"line":164},2,[155,166,168],{"emptyLinePlaceholder":167},true,"\n",[155,170,172],{"class":157,"line":171},3,[155,173,174],{},"declare(strict_types=1);\n",[155,176,178],{"class":157,"line":177},4,[155,179,168],{"emptyLinePlaceholder":167},[155,181,183],{"class":157,"line":182},5,[155,184,185],{},"const OPENMOD_BASE = 'https:\u002F\u002Fapi.openshield.dev';\n",[155,187,189],{"class":157,"line":188},6,[155,190,168],{"emptyLinePlaceholder":167},[155,192,194],{"class":157,"line":193},7,[155,195,196],{},"final class ModerationError extends RuntimeException\n",[155,198,200],{"class":157,"line":199},8,[155,201,202],{},"{\n",[155,204,206],{"class":157,"line":205},9,[155,207,208],{},"    public function __construct(public readonly int $status, public readonly string $type, string $message, public readonly ?int $retryAfter = null)\n",[155,210,212],{"class":157,"line":211},10,[155,213,214],{},"    {\n",[155,216,218],{"class":157,"line":217},11,[155,219,220],{},"        parent::__construct($message, $status);\n",[155,222,224],{"class":157,"line":223},12,[155,225,226],{},"    }\n",[155,228,230],{"class":157,"line":229},13,[155,231,232],{},"}\n",[155,234,236],{"class":157,"line":235},14,[155,237,168],{"emptyLinePlaceholder":167},[155,239,241],{"class":157,"line":240},15,[155,242,243],{},"function openmod_send(string $path, string $body, array $headers = []): array\n",[155,245,247],{"class":157,"line":246},16,[155,248,202],{},[155,250,252],{"class":157,"line":251},17,[155,253,254],{},"    $key = getenv('OPENSHIELD_API_KEY');\n",[155,256,258],{"class":157,"line":257},18,[155,259,260],{},"    if ($key === false || $key === '') {\n",[155,262,264],{"class":157,"line":263},19,[155,265,266],{},"        throw new RuntimeException('OPENSHIELD_API_KEY is not set');\n",[155,268,270],{"class":157,"line":269},20,[155,271,226],{},[155,273,275],{"class":157,"line":274},21,[155,276,168],{"emptyLinePlaceholder":167},[155,278,280],{"class":157,"line":279},22,[155,281,282],{},"    $curl = curl_init(OPENMOD_BASE . $path);\n",[155,284,286],{"class":157,"line":285},23,[155,287,288],{},"    curl_setopt_array($curl, [\n",[155,290,292],{"class":157,"line":291},24,[155,293,294],{},"        CURLOPT_POST => true,\n",[155,296,298],{"class":157,"line":297},25,[155,299,300],{},"        CURLOPT_POSTFIELDS => $body,\n",[155,302,304],{"class":157,"line":303},26,[155,305,306],{},"        CURLOPT_HTTPHEADER => array_merge(['Authorization: Bearer ' . $key], $headers),\n",[155,308,310],{"class":157,"line":309},27,[155,311,312],{},"        CURLOPT_RETURNTRANSFER => true,\n",[155,314,316],{"class":157,"line":315},28,[155,317,318],{},"        CURLOPT_HEADER => false,\n",[155,320,322],{"class":157,"line":321},29,[155,323,324],{},"        CURLOPT_TIMEOUT => 10,\n",[155,326,328],{"class":157,"line":327},30,[155,329,330],{},"    ]);\n",[155,332,334],{"class":157,"line":333},31,[155,335,336],{},"    $raw = curl_exec($curl);\n",[155,338,340],{"class":157,"line":339},32,[155,341,342],{},"    $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);\n",[155,344,346],{"class":157,"line":345},33,[155,347,168],{"emptyLinePlaceholder":167},[155,349,351],{"class":157,"line":350},34,[155,352,353],{},"    if ($raw === false) {\n",[155,355,357],{"class":157,"line":356},35,[155,358,359],{},"        throw new RuntimeException('openshield could not be reached: ' . curl_error($curl));\n",[155,361,363],{"class":157,"line":362},36,[155,364,226],{},[155,366,368],{"class":157,"line":367},37,[155,369,168],{"emptyLinePlaceholder":167},[155,371,373],{"class":157,"line":372},38,[155,374,375],{},"    $answer = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);\n",[155,377,379],{"class":157,"line":378},39,[155,380,381],{},"    if ($status !== 200) {\n",[155,383,385],{"class":157,"line":384},40,[155,386,387],{},"        $error = $answer['error'] ?? [];\n",[155,389,391],{"class":157,"line":390},41,[155,392,393],{},"        throw new ModerationError($status, $error['type'] ?? 'unknown', $error['message'] ?? 'The check failed');\n",[155,395,397],{"class":157,"line":396},42,[155,398,226],{},[155,400,402],{"class":157,"line":401},43,[155,403,168],{"emptyLinePlaceholder":167},[155,405,407],{"class":157,"line":406},44,[155,408,409],{},"    return [\n",[155,411,413],{"class":157,"line":412},45,[155,414,415],{},"        'action' => $answer['summary']['action'],\n",[155,417,419],{"class":157,"line":418},46,[155,420,421],{},"        'reasons' => array_merge($answer['summary']['reject_reason'], $answer['summary']['review_reason']),\n",[155,423,425],{"class":157,"line":424},47,[155,426,427],{},"        'request_id' => $answer['request_id'],\n",[155,429,431],{"class":157,"line":430},48,[155,432,433],{},"    ];\n",[155,435,437],{"class":157,"line":436},49,[155,438,232],{},[155,440,442],{"class":157,"line":441},50,[155,443,168],{"emptyLinePlaceholder":167},[155,445,447],{"class":157,"line":446},51,[155,448,449],{},"function openmod_check_text(string $text, ?string $ref = null): array\n",[155,451,453],{"class":157,"line":452},52,[155,454,202],{},[155,456,458],{"class":157,"line":457},53,[155,459,460],{},"    return openmod_send('\u002Fv1\u002Ftext', json_encode(['text' => $text, 'ref' => $ref], JSON_THROW_ON_ERROR), ['Content-Type: application\u002Fjson']);\n",[155,462,464],{"class":157,"line":463},54,[155,465,232],{},[155,467,469],{"class":157,"line":468},55,[155,470,168],{"emptyLinePlaceholder":167},[155,472,474],{"class":157,"line":473},56,[155,475,476],{},"function openmod_check_image(string $bytes, ?string $ref = null): array\n",[155,478,480],{"class":157,"line":479},57,[155,481,202],{},[155,483,485],{"class":157,"line":484},58,[155,486,487],{},"    return openmod_send('\u002Fv1\u002Fimage' . ($ref === null ? '' : '?ref=' . rawurlencode($ref)), $bytes);\n",[155,489,491],{"class":157,"line":490},59,[155,492,232],{},[124,494,496],{"id":495},"use-it-and-fail-closed","Use it, and fail closed",[145,498,500],{"className":147,"code":499,"language":150,"meta":151,"style":151},"\u003C?php\n\nrequire 'moderation.php';\n\nfunction moderate_comment(string $text, string $commentId): string\n{\n    try {\n        $decision = openmod_check_text($text, $commentId);\n    } catch (ModerationError $error) {\n        \u002F\u002F The content cannot be accepted as it is: refuse it.\n        return in_array($error->status, [400, 413, 415], true) ? 'refuse' : 'hold';\n    } catch (Throwable) {\n        \u002F\u002F A timeout or a network error: keep it hidden until someone looks at it.\n        return 'hold';\n    }\n\n    return match ($decision['action']) {\n        'accept' => 'publish',\n        'review' => 'hold',\n        default => 'refuse',\n    };\n}\n",[132,501,502,506,510,515,519,524,528,533,538,543,548,553,558,563,568,572,576,581,586,591,596,601],{"__ignoreMap":151},[155,503,504],{"class":157,"line":158},[155,505,161],{},[155,507,508],{"class":157,"line":164},[155,509,168],{"emptyLinePlaceholder":167},[155,511,512],{"class":157,"line":171},[155,513,514],{},"require 'moderation.php';\n",[155,516,517],{"class":157,"line":177},[155,518,168],{"emptyLinePlaceholder":167},[155,520,521],{"class":157,"line":182},[155,522,523],{},"function moderate_comment(string $text, string $commentId): string\n",[155,525,526],{"class":157,"line":188},[155,527,202],{},[155,529,530],{"class":157,"line":193},[155,531,532],{},"    try {\n",[155,534,535],{"class":157,"line":199},[155,536,537],{},"        $decision = openmod_check_text($text, $commentId);\n",[155,539,540],{"class":157,"line":205},[155,541,542],{},"    } catch (ModerationError $error) {\n",[155,544,545],{"class":157,"line":211},[155,546,547],{},"        \u002F\u002F The content cannot be accepted as it is: refuse it.\n",[155,549,550],{"class":157,"line":217},[155,551,552],{},"        return in_array($error->status, [400, 413, 415], true) ? 'refuse' : 'hold';\n",[155,554,555],{"class":157,"line":223},[155,556,557],{},"    } catch (Throwable) {\n",[155,559,560],{"class":157,"line":229},[155,561,562],{},"        \u002F\u002F A timeout or a network error: keep it hidden until someone looks at it.\n",[155,564,565],{"class":157,"line":235},[155,566,567],{},"        return 'hold';\n",[155,569,570],{"class":157,"line":240},[155,571,226],{},[155,573,574],{"class":157,"line":246},[155,575,168],{"emptyLinePlaceholder":167},[155,577,578],{"class":157,"line":251},[155,579,580],{},"    return match ($decision['action']) {\n",[155,582,583],{"class":157,"line":257},[155,584,585],{},"        'accept' => 'publish',\n",[155,587,588],{"class":157,"line":263},[155,589,590],{},"        'review' => 'hold',\n",[155,592,593],{"class":157,"line":269},[155,594,595],{},"        default => 'refuse',\n",[155,597,598],{"class":157,"line":274},[155,599,600],{},"    };\n",[155,602,603],{"class":157,"line":279},[155,604,232],{},[120,606,607,608,611,612,615],{},"For rejected content, show your user a message chosen from the ",[132,609,610],{},"id"," of the first reason, in your own words and your own languages. ",[613,614,87],"a",{"href":88}," says more.",[617,618,619],"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);}",{"title":151,"searchDepth":164,"depth":164,"links":621},[622,623,624],{"id":126,"depth":164,"text":127},{"id":142,"depth":164,"text":143},{"id":495,"depth":164,"text":496},"A moderation helper for PHP 8.1 or later with cURL, and no dependency.","md",null,{},{"title":58,"description":625},"x2JKicju6esGbl7y22ajdtvEJmMdDojvxltEp_Z8QQQ",[632,634],{"title":54,"path":55,"stem":56,"description":633,"children":-1},"A moderation module for Python 3 with the requests library.",{"title":62,"path":63,"stem":64,"description":635,"children":-1},"A moderation package for Go with net\u002Fhttp and the standard library only.",1792938463411]