> For the complete documentation index, see [llms.txt](https://docs.otari.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.otari.ai/api-reference/hooks.md).

# Hooks

## Check Policy

> Evaluate a submitted policy against submitted evidence.\
> \
> Authenticated with either an API key or the master key (the router-level\
> gate), like \`\`POST /api/v1/usage/external-events\`\`: this identifies who\
> sent the request, not whether its evidence is true. \`blocked\` is set when\
> a required gate's outcome is not \`pass\`/\`not\_applicable\` (an unresolved\
> gate never counts as a pass).\
> \
> The actual parse-and-evaluate work is\
> \`\`otari\_agent.domain.check.run\_policy\_check\`\`, shared with \`\`otari\
> hook\`\`'s own local evaluation: this route's own job is authentication,\
> translating that function's tri-state request fields into its own typed\
> ones, and turning \`\`PolicyCheckError\`\` into a 422.

```json
{"openapi":"3.1.0","info":{"title":"otari","version":"0.33.0"},"security":[{"ApiKeyAuth":[]},{"XApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"description":"Enter your API key here (sent as Otari-Key header).","in":"header","name":"Otari-Key","type":"apiKey"},"XApiKeyAuth":{"description":"Anthropic-native clients send credentials here (no Bearer prefix).","in":"header","name":"x-api-key","type":"apiKey"}},"schemas":{"PolicyCheckRequest":{"additionalProperties":false,"description":"A policy body plus the evidence to check it against, both caller-supplied.","properties":{"check_results":{"anyOf":[{"items":{"$ref":"#/components/schemas/CheckVerdictRequest"},"maxItems":1000,"type":"array"},{"type":"null"}],"description":"Verifier verdicts the caller collected for this request's verifier gates.","title":"Check Results"},"command_scope":{"default":"call","description":"What `commands` covers: `call` for the single tool call about to run, `session` for every command the session has run so far.","enum":["call","session"],"title":"Command Scope","type":"string"},"commands":{"anyOf":[{"items":{"type":"string"},"maxItems":10000,"type":"array"},{"type":"null"}],"description":"Shell commands the caller observed run or is about to run.","title":"Commands"},"judge_results":{"anyOf":[{"items":{"$ref":"#/components/schemas/JudgeVerdictRequest"},"maxItems":1000,"type":"array"},{"type":"null"}],"description":"Model verdicts the caller collected for this request's judge gates.","title":"Judge Results"},"path_source":{"anyOf":[{"enum":["pre_tool_use.edit_target","pre_tool_use.read_target","pre_tool_use.command","stop.working_tree","stop.session","stop.verifier"],"type":"string"},{"type":"null"}],"description":"Which moment `paths` was read at, matching the `runs` values a path gate declares. Only three of the six `runs` values are legal here, because only those three are moments a path can be read at: `pre_tool_use.edit_target` for a write tool's own target before it runs, `pre_tool_use.read_target` for a read tool's, and `stop.working_tree` for `git status` once the turn is over. Required whenever `paths` is non-empty, and rejected with a 422 if omitted or set to any other value: either would resolve every path gate `not_applicable`, which loses enforcement without reporting anything. An empty `paths` needs no source.","title":"Path Source"},"paths":{"anyOf":[{"items":{"type":"string"},"maxItems":10000,"type":"array"},{"type":"null"}],"description":"Repo-relative paths this moment of the session puts in scope: what `git status --porcelain` reports, or the single target a tool call is about to write or read. `path_source` says which.","title":"Paths"},"policy_yaml":{"maxLength":262144,"minLength":1,"title":"Policy Yaml","type":"string"}},"required":["policy_yaml"],"title":"PolicyCheckRequest","type":"object"},"CheckVerdictRequest":{"additionalProperties":false,"description":"One verifier gate's verdict, as the caller's own verifier run produced it.\n\nMirrors ``JudgeVerdictRequest`` field-for-field: ``gate_id`` echoes back\nthe gate the policy itself named (same bound, same reason), ``outcome``\nis the caller's own report of the verifier's exit code (0/1/anything\nelse, mapped by the caller to pass/fail/error), and ``detail`` is the\nverifier's captured stdout, capped the same way ``reasoning`` is.","properties":{"detail":{"default":"","maxLength":4096,"title":"Detail","type":"string"},"gate_id":{"maxLength":200,"minLength":1,"title":"Gate Id","type":"string"},"outcome":{"enum":["pass","fail","error"],"title":"Outcome","type":"string"}},"required":["gate_id","outcome"],"title":"CheckVerdictRequest","type":"object"},"JudgeVerdictRequest":{"additionalProperties":false,"description":"One judge gate's verdict, as the caller's own model call produced it.","properties":{"gate_id":{"maxLength":200,"minLength":1,"title":"Gate Id","type":"string"},"outcome":{"enum":["pass","fail","error"],"title":"Outcome","type":"string"},"reasoning":{"default":"","maxLength":4096,"title":"Reasoning","type":"string"}},"required":["gate_id","outcome"],"title":"JudgeVerdictRequest","type":"object"},"PolicyCheckResponse":{"properties":{"blocked":{"title":"Blocked","type":"boolean"},"policy_id":{"title":"Policy Id","type":"string"},"provenance":{"default":"client_reported","title":"Provenance","type":"string"},"results":{"items":{"$ref":"#/components/schemas/GateResultResponse"},"title":"Results","type":"array"},"schema_version":{"title":"Schema Version","type":"string"}},"required":["policy_id","schema_version","results","blocked"],"title":"PolicyCheckResponse","type":"object"},"GateResultResponse":{"properties":{"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"enforcement":{"title":"Enforcement","type":"string"},"gate_id":{"title":"Gate Id","type":"string"},"message":{"title":"Message","type":"string"},"outcome":{"title":"Outcome","type":"string"}},"required":["gate_id","enforcement","outcome","message"],"title":"GateResultResponse","type":"object"},"HTTPValidationError":{"properties":{"code":{"const":"invalid_request","description":"Stable error code, also sent as the Otari-Error-Code header.","title":"Code","type":"string"},"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"title":"Detail","type":"array"}},"title":"HTTPValidationError","type":"object"},"ValidationError":{"properties":{"ctx":{"title":"Context","type":"object"},"input":{"title":"Input"},"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"title":"Location","type":"array"},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}},"required":["loc","msg","type"],"title":"ValidationError","type":"object"}}},"paths":{"/api/v1/hooks/check":{"post":{"description":"Evaluate a submitted policy against submitted evidence.\n\nAuthenticated with either an API key or the master key (the router-level\ngate), like ``POST /api/v1/usage/external-events``: this identifies who\nsent the request, not whether its evidence is true. `blocked` is set when\na required gate's outcome is not `pass`/`not_applicable` (an unresolved\ngate never counts as a pass).\n\nThe actual parse-and-evaluate work is\n``otari_agent.domain.check.run_policy_check``, shared with ``otari\nhook``'s own local evaluation: this route's own job is authentication,\ntranslating that function's tri-state request fields into its own typed\nones, and turning ``PolicyCheckError`` into a 422.","operationId":"hooks-check_policy","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyCheckRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyCheckResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"summary":"Check Policy","tags":["hooks"]}}}}
```

## Record Guardrail Definition

> Keep one version of an agent guardrail, so the gate spans that name it can say what each gate is for.\
> \
> \`otari hook\` sends this once per version, the first time it reports a check\
> against it. The fingerprint is recomputed from the gates, and one that does\
> not match the one sent is refused, so a version cannot be stored under\
> another's name. Sending a version again changes nothing. A workspace keeps at\
> most 1,000 versions, and a new one past that is refused with a 409. It is stored in the\
> API key's workspace, beside that key's traces, and takes the same key a trace\
> export does: a budget-exempt API key, never the master key.

```json
{"openapi":"3.1.0","info":{"title":"otari","version":"0.33.0"},"security":[{"ApiKeyAuth":[]},{"XApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"description":"Enter your API key here (sent as Otari-Key header).","in":"header","name":"Otari-Key","type":"apiKey"},"XApiKeyAuth":{"description":"Anthropic-native clients send credentials here (no Bearer prefix).","in":"header","name":"x-api-key","type":"apiKey"}},"schemas":{"GuardrailDefinitionRequest":{"additionalProperties":false,"properties":{"digest":{"pattern":"^[0-9a-f]{64}$","title":"Digest","type":"string"},"gates":{"items":{"$ref":"#/components/schemas/GateDefinitionRequest"},"maxItems":500,"minItems":1,"title":"Gates","type":"array"}},"required":["digest","gates"],"title":"GuardrailDefinitionRequest","type":"object"},"GateDefinitionRequest":{"additionalProperties":false,"description":"One gate of a guardrail version: what it is, when it runs, and what it says when it fails.","properties":{"enforcement":{"enum":["required","advisory"],"title":"Enforcement","type":"string"},"gate_id":{"maxLength":200,"minLength":1,"title":"Gate Id","type":"string"},"gate_type":{"enum":["command","command_if_changed","judge","path","verifier"],"title":"Gate Type","type":"string"},"message":{"maxLength":4096,"title":"Message","type":"string"},"origin":{"enum":["repo","user"],"title":"Origin","type":"string"},"runs":{"items":{"enum":["pre_tool_use.edit_target","pre_tool_use.read_target","pre_tool_use.command","stop.working_tree","stop.session","stop.verifier"],"type":"string"},"maxItems":16,"title":"Runs","type":"array"}},"required":["gate_id","gate_type","enforcement","origin","runs","message"],"title":"GateDefinitionRequest","type":"object"},"GuardrailDefinitionResponse":{"properties":{"digest":{"title":"Digest","type":"string"}},"required":["digest"],"title":"GuardrailDefinitionResponse","type":"object"},"HTTPValidationError":{"properties":{"code":{"const":"invalid_request","description":"Stable error code, also sent as the Otari-Error-Code header.","title":"Code","type":"string"},"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"title":"Detail","type":"array"}},"title":"HTTPValidationError","type":"object"},"ValidationError":{"properties":{"ctx":{"title":"Context","type":"object"},"input":{"title":"Input"},"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"title":"Location","type":"array"},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}},"required":["loc","msg","type"],"title":"ValidationError","type":"object"}}},"paths":{"/api/v1/hooks/guardrails":{"post":{"description":"Keep one version of an agent guardrail, so the gate spans that name it can say what each gate is for.\n\n`otari hook` sends this once per version, the first time it reports a check\nagainst it. The fingerprint is recomputed from the gates, and one that does\nnot match the one sent is refused, so a version cannot be stored under\nanother's name. Sending a version again changes nothing. A workspace keeps at\nmost 1,000 versions, and a new one past that is refused with a 409. It is stored in the\nAPI key's workspace, beside that key's traces, and takes the same key a trace\nexport does: a budget-exempt API key, never the master key.","operationId":"hooks-record_guardrail_definition","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardrailDefinitionRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardrailDefinitionResponse"}}},"description":"Successful Response"},"409":{"description":"The workspace already keeps the most guardrail versions it may, and this one is new."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"summary":"Record Guardrail Definition","tags":["hooks"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.otari.ai/api-reference/hooks.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
