GuardOathBuilding Confidence Into AI.DEVELOPMENT PREVIEW
Check playgroundAPI reference ↗
GUARDOATH · DEVELOPER GUIDE

Add GuardOath to your application

Check every message before it reaches your AI model, and every answer before it reaches your user. This guide covers keys, requests, results, profiles and every check.

How it fits in

  1. Your user sends a message. Your application sends it to POST /v1/checks/input.
  2. Only if the input check lets it through, send the message to your AI model.
  3. Send the model's answer to POST /v1/checks/output.
  4. Only if the output check lets it through, show the answer to your user.

GuardOath does not call an AI model and does not write answers. It checks what you send and tells you what to do with it. You can try the four steps without writing code in the check playground.

Get an API key

Keys look like gok_<tenant id>_<secret>. Each key belongs to one tenant record, which holds your checks, your settings and your monthly usage. Your tenant id is the part between gok_ and the next _; you send it as application_id.

Send a check

Base address: https://guardoath.com. Every request with a body is JSON over HTTPS and needs these headers:

HeaderValue
X-GuardOath-KeyYour API key.
Content-Typeapplication/json (UTF-8).
x-amz-content-sha256The SHA-256 of the exact body bytes you send, as 64 lower-case hex characters. Without it the request is refused with HTTP 403. Hash the same bytes you send: serialize the JSON once and use that string for both the hash and the body.

Use X-GuardOath-Key, not Authorization: on guardoath.com the Authorization header is replaced before it reaches GuardOath.

Check request fields

FieldRequiredWhat it is
application_idyesYour tenant id (1–100 characters).
contentyesThe message or answer to check, 1–32,000 characters. For JSON content, send the JSON as a string.
content_formatnotext (default) or json. JSON checks (schema, field comparisons, decision records, citations, tool calls, ledger, timestamps, graph) need json.
policy_versionnoThe profile to run for this request only. Leave it out to use your default profile. See Profiles.
request_contextnoUp to 8,000 characters of context. Accepted, but not inspected by the current checks.
languagenoen, the only supported value.

Any other field is refused with HTTP 422.

Python (standard library only)

import hashlib
import json
import urllib.error
import urllib.request

API = "https://guardoath.com"
KEY = "gok_demo1_..."          # your API key; keep it on your server
APPLICATION_ID = "demo1"       # your tenant id: the part of the key after "gok_"

def check(stage, content, profile=None):
    """stage is "input" (the user's message) or "output" (the AI's answer)."""
    request = {"application_id": APPLICATION_ID, "content": content}
    if profile:
        request["policy_version"] = profile   # leave out to use your default profile
    body = json.dumps(request).encode("utf-8")
    call = urllib.request.Request(API + "/v1/checks/" + stage, data=body, method="POST", headers={
        "X-GuardOath-Key": KEY,
        "Content-Type": "application/json",
        "x-amz-content-sha256": hashlib.sha256(body).hexdigest(),  # SHA-256 of these exact bytes
    })
    try:
        with urllib.request.urlopen(call, timeout=20) as response:
            return json.load(response)
    except urllib.error.HTTPError as error:
        raise RuntimeError(f"GuardOath answered HTTP {error.code}: {error.read().decode()}") from None

result = check("input", "Please email jane@example.com about my order.")
print(result["effective_action"], [f["rule_id"] for f in result["findings"]])

JavaScript (Node.js)

import { createHash } from "node:crypto";   // Node.js 18 or later

const API = "https://guardoath.com";
const KEY = process.env.GUARDOATH_KEY;      // keep the key on your server
const APPLICATION_ID = "demo1";

async function check(stage, content, profile) {
  const body = JSON.stringify({ application_id: APPLICATION_ID, content, ...(profile ? { policy_version: profile } : {}) });
  const response = await fetch(`${API}/v1/checks/${stage}`, {
    method: "POST",
    headers: {
      "X-GuardOath-Key": KEY,
      "Content-Type": "application/json",
      "x-amz-content-sha256": createHash("sha256").update(body, "utf8").digest("hex"),
    },
    body,
  });
  if (!response.ok) throw new Error(`GuardOath answered HTTP ${response.status}: ${await response.text()}`);
  return response.json();
}

Read the result

A check returns HTTP 200 with a result like this (shortened):

{
  "check_id": "6f1c...",                 "application_id": "demo1",
  "policy_version": "tenant",            "stage": "input",          "mode": "enforce",
  "evaluation_status": "complete",       "recommended_action": "block",   "effective_action": "block",
  "findings": [{"rule_id": "pii.email_address", "category": "pii", "severity": "high",
                "explanation": "..."}],
  "checker_results": [{"checker_id": "pii", "status": "flagged", "latency_ms": 3.1, ...}, ...],
  "redaction": {"status": "not_requested", "content": null, "explanation": "..."},
  "groups": [...],  "total_latency_ms": 12.4,  "simulated": false
}
FieldMeaning
effective_actionWhat to do now: allow, flag, redact or block. In observe mode it is always allow.
recommended_actionWhat the checks found, whatever the mode. In observe mode, this shows what enforce mode would do.
evaluation_statuscomplete, or incomplete when a required check did not finish (for example it timed out). Incomplete is never a pass: your record's failure_action (block or flag) is applied.
findingsWhat was found: rule_id, category, severity and a plain-language explanation. Findings never repeat the text that matched.
checker_resultsOne entry per check that ran, with status (passed, flagged, error, skipped, unsupported) and, on errors, an error_code.
redactionFor the redact action: status ready (enforce) or preview (observe) with the cleaned content, which has been checked again; withheld when no safe cleaned version exists (recommended_action is then block); not_needed or not_requested otherwise.
mode, policy_version, stageThe mode and profile that ran, and whether this was the input or output check.

Deciding what to do

def next_step(result):
    if result["evaluation_status"] != "complete":
        return "stop"                      # a required check did not finish: not a pass
    action = result["effective_action"]
    if action == "allow":
        return "continue"
    if action == "redact" and result["redaction"]["status"] == "ready":
        return "continue with result['redaction']['content']"
    return "stop" if action == "block" else "hold for review"   # "flag": your application decides

flag means "found something, your application decides": hold the message for review, or log it and continue, as your policy says.

Endpoints

MethodPathKey neededWhat it does
GET/v1/demo-usersnoneThe demo users you can pick (demo1 to demo10).
POST/v1/demo-users/{tenant_id}/keynone (send the JSON body {})That demo user's API key. Everyone who picks the same demo user gets the same key and shares its settings and monthly limit.
GET/v1/applications/currentyesYour record: the profiles you can use (with each check and its action on input and output), your default profile, your check settings, and this month's usage.
POST/v1/checks/inputyesCheck the user's message before it goes to your AI model. Counts as one check.
POST/v1/checks/outputyesCheck the AI's answer before it reaches your user. Counts as one check.
POST/v1/tenants/current/checksyesChange your own checks, settings or default profile (see below). Not counted as a check.
POST/v1/tenants/current/resetyes, demo users onlyPut a demo user's checks, settings and default profile back to the starting values.
GET/healthznoneReturns {"status": "ok", ...} when the service is up.

The API reference lists every field of every endpoint. Its "Try it out" buttons cannot add the body hash, so use the examples above to send requests.

Profiles

A profile is a named set of checks. tenant runs the checks that are on in your own record; the others are ready-made examples. Every request can name a profile in policy_version; without one, your record's default_profile is used (it starts as tenant).

The example profiles run in observe mode: they report findings and a recommended_action, but effective_action is always allow. To block, use tenant with your own checks in enforce mode.

ProfileWhat it checksChecksModeWho can use it
tenantChecks on this tenant record
The checks that are on in “Checks on this record” below, on both stages.
The check fields that are on in your recordYour record's mode (demo users: enforce)Everyone
conversation-v1Conversation: everyday checks
Personal data, encoded personal data, secrets and hidden text, on both the user’s message and the AI’s answer.
pii, secrets, text_security, encoded_piiobserveEveryone
pii-v1Personal data: email, card, SSN
Email, card and U.S. SSN rules plus the 90 privacy pattern types.
piiobserveEveryone
pii-extended-v1Personal data: labeled fields too
Adds labeled phone, birth date, medical record, health plan and bank account fields.
piiobserveEveryone
redaction-v1Personal data: redact
Replaces found identifiers and runs the checks again on the cleaned text.
piiobserveEveryone
privacy-encoded-v1Personal data: inside encodings
Personal data in plain text and inside one labeled Base64/hex layer.
pii, encoded_piiobserveEveryone
privacy-encoded-redact-v1Personal data: inside encodings, redact
Redacts plain identifiers and blocks encoded ones.
pii, encoded_piiobserveEveryone
secrets-v1Secrets: credential patterns
Selected provider keys, JWTs, private keys and passwords in URLs.
secretsobserveEveryone
secrets-extended-v1Secrets: extended
Adds quoted password assignments, labeled encoded secrets and confidential phrases.
secretsobserveEveryone
text-security-v1Hidden text and encodings
Invisible and direction-changing characters, and labeled Base64/hex text.
text_securityobserveEveryone
structure-v1JSON structure (schema)
JSON Schema: required fields, types, formats and limits.
schemaobserveEveryone
business-v1Business rules: dates and refunds
JSON Schema plus start date before end date, and refund no larger than purchase.
schema, cross_fieldobserveEveryone
business-number-le-v1Business rules: number comparison
One numeric comparison: left value no larger than right value.
cross_fieldobserveEveryone
completeness-v1Required sections
Requires “Summary:” in the message, and “Summary:” and “Next steps:” in the answer.
completenessobserveEveryone
content-safety-v1Content safety (outside provider)
Everyday checks plus an outside content-safety provider. Not configured on this local server.
pii, secrets, text_security, content_safetyobserveCustomers, once the outside provider is set up (not yet on guardoath.com)
bias-matched-string-v1Matched decisions: text outcomes
Compares decision records that differ only by group; outcomes are text.
matched_outcomesobserveEveryone
bias-matched-boolean-v1Matched decisions: yes/no outcomes
Same comparison with yes/no outcomes.
matched_outcomesobserveEveryone
bias-matched-number-v1Matched decisions: number outcomes
Same comparison with numeric outcomes.
matched_outcomesobserveEveryone
evidence-v1Citation evidence
Citations and quotations checked word for word against a source catalog.
evidenceobserveEveryone
tool-calls-v1Proposed tool calls
Allowed tool names and argument schemas.
tool_callsobserveEveryone
ledger-v1Ledger totals
Debits and credits add up exactly.
ledgerobserveEveryone
timestamps-v1Timestamp order
Timestamps are in order.
timestampsobserveEveryone
graph-v1Graph structure
No cycles, self-loops, duplicate edges or unknown nodes.
graphobserveEveryone
integrity-v1Content integrity (SHA-256)
Content matches an approved fingerprint.
integrityobserveEveryone

Checks

Each check is a field on your record, check_<id>, set to the action taken when it finds something: block, flag, allow (report and let it through) or, for personal data only, redact. An empty field (null) means the check does not run. Some checks need settings, such as a JSON Schema; demo users have them filled in.

Personal data (PII) check_pii

What it checks: Finds personal identifiers: email addresses, card numbers (with a checksum), U.S. Social Security numbers, and 90 more types of bank, tax, health, ID-document, network, phone and postal identifiers for the U.S. and 17 other countries. It can also look for labeled phone, birth date, medical record, health plan and bank account fields.

Why use it: Stop customers’ personal data from being sent to an AI model, or from leaking out in an AI answer. Redact replaces what it finds and checks the result again.

Limits: Does not detect names, places or other free-text personal details.

Input and output · Actions: Block, Flag, Allow, Redact · Options: detect_labeled_sensitive_data, privacy_patterns, disabled_privacy_entities, privacy_min_score

Encoded personal data check_encoded_pii

What it checks: Looks for the same personal identifiers inside one labeled Base64 or hex layer, such as “base64: …”.

Why use it: Someone can encode an email or card number to slip it past a plain-text filter.

Limits: One labeled layer only; unlabeled or nested encodings are not decoded.

Input and output · Actions: Block, Flag, Allow · No settings needed

Secrets and credentials check_secrets

What it checks: Finds credentials from selected providers, JWTs, private keys and passwords inside URLs. Optionally also quoted password assignments, labeled encoded secrets and your own confidential phrases (for example an internal project name).

Why use it: Stop API keys, passwords or confidential names from being pasted into a model or shown in an answer.

Limits: Selected formats only; credentials are never verified with the provider.

Input and output · Actions: Block, Flag, Allow · Options: detect_secret_assignments, inspect_encoded_secrets, confidential_terms

Hidden text and encodings check_text_security

What it checks: Finds selected invisible and control characters, characters that reverse text direction, and labeled Base64/hex text.

Why use it: Hidden characters and encodings are used to smuggle instructions past people reviewing a conversation.

Limits: Not a prompt-injection detector: a plainly worded attack passes.

Input and output · Actions: Block, Flag, Allow · No settings needed

Risky instructions (wording) check_lexical_guards

What it checks: Looks for 121 wording patterns of risky instructions: requests to reveal a system prompt or secrets, to ignore instructions or get around safety, approval and audit steps, destructive or data-exposing commands for shells, databases, Git, Kubernetes and cloud accounts, and settings that turn security controls off.

Why use it: Report a message that asks an AI agent to do something dangerous, or an AI answer that proposes it, so a person can review it before anything runs.

Limits: Matches wording, not intent: harmless text that uses the same words is reported too, and reworded requests pass. Flag suits it better than Block.

Input and output · Actions: Block, Flag, Allow · No settings needed

JSON structure (schema) check_schema

What it checks: Checks that JSON content matches your JSON Schema: required fields, data types, formats such as dates and emails, and size limits.

Why use it: When your application expects structured data, reject a malformed request or AI answer before your code uses it.

Limits: Needs JSON content and a schema on the record. It checks shape, not whether the values are true.

Input and output · Actions: Block, Flag, Allow · Needs: input_schema or output_schema (filled in for demo users)

Field comparisons check_cross_field

What it checks: Compares two fields in the submitted JSON, for example a start date before an end date, or a refund no larger than the purchase.

Why use it: Catch inconsistent values, such as an AI offering a refund bigger than the order.

Limits: Both values come from the submission, so it is a consistency check, not an authoritative limit.

Input and output · Actions: Block, Flag, Allow · Needs: comparisons (filled in for demo users)

Required sections check_completeness

What it checks: Checks that required labeled sections are present and long enough. The demo requires “Summary:” in the message, and “Summary:” and “Next steps:” in the answer.

Why use it: Make sure an AI answer includes the parts your process needs, such as next steps in a support reply.

Limits: Checks that a section exists and its length, not whether its content is good.

Input and output · Actions: Block, Flag, Allow · Needs: required_sections (filled in for demo users)

Matched outcomes (bias) check_matched_outcomes

What it checks: Compares submitted decision records. If two records match except for a group attribute (in the demo, “group”) but got different decisions, it reports a finding.

Why use it: Spot inconsistent decisions between groups, for example in loan or hiring recommendations.

Limits: Needs structured records. It does not judge free-text bias or certify fairness.

Input and output · Actions: Block, Flag, Allow · Needs: matched_outcomes (filled in for demo users)

Citation evidence check_evidence

What it checks: Checks that an answer’s [[source]] citations and quotations match a source catalog on the record, word for word.

Why use it: Catch made-up citations and misquoted policy text before an answer reaches your user.

Limits: Not a fact checker: it does not judge whether a quote supports the claim.

Input and output · Actions: Block, Flag, Allow · Needs: evidence (filled in for demo users)

Proposed tool calls check_tool_calls

What it checks: Checks that tool calls proposed by an AI use allowed tool names and that their arguments match each tool’s schema. The demo allows lookup_order and request_refund.

Why use it: Stop an AI agent from calling a tool it should not use, or passing it bad arguments.

Limits: Nothing is run or authorized; your application still decides whether to make the call.

Input and output · Actions: Block, Flag, Allow · Needs: tool_calls (filled in for demo users)

Ledger totals check_ledger

What it checks: Checks that debit and credit entries add up exactly, in a set currency and number of decimal places (demo: USD, 2 places).

Why use it: Catch arithmetic mistakes in financial summaries an AI produces.

Limits: Exact arithmetic only; no fraud or accounting-rule checks.

Input and output · Actions: Block, Flag, Allow · Needs: ledger (filled in for demo users)

Timestamp order check_timestamps

What it checks: Checks that a list of timestamps is in order, with no earlier time after a later one.

Why use it: Catch out-of-order event logs and timelines.

Limits: No sorting, freshness checks or memory between requests.

Input and output · Actions: Block, Flag, Allow · Needs: timestamps (filled in for demo users)

Graph structure check_graph

What it checks: Checks a declared directed graph for cycles, self-loops, duplicate edges and links to unknown nodes.

Why use it: Validate workflow or dependency graphs before they are used.

Limits: Checks the declared structure only, not whether the relationships are true.

Input and output · Actions: Block, Flag, Allow · Needs: graph (filled in for demo users)

Content integrity (SHA-256) check_integrity

What it checks: Checks that content’s SHA-256 fingerprint matches an approved reference stored on the record.

Why use it: Confirm that a template, policy text or file has not changed by even one character.

Limits: A match shows the bytes are unchanged, not that the content is safe.

Input and output · Actions: Block, Flag, Allow · Needs: integrity (filled in for demo users)

Content safety (outside provider) check_content_safety

What it checks: Sends content to an outside provider (Amazon Bedrock Guardrails) to review hate, insults, sexual content, violence and misconduct.

Why use it: Review harmful content that local pattern checks cannot judge.

Limits: Paid outside service, not available for demo users. Runs only after all local checks pass.

Input and output · Actions: Block, Flag, Allow · No settings needed

Not yet available on guardoath.com (calls a paid outside provider; never available to demo users).

Prompt Shields (outside provider, input only) check_azure_prompt_shields

What it checks: Sends the user’s message to an outside provider (Azure Prompt Shields) to look for prompt-attack attempts. Input only.

Why use it: Add a model-based review of jailbreak attempts on top of the local checks.

Limits: Paid outside service, not available for demo users. Runs only after all local checks pass.

Input only · Actions: Block, Flag, Allow · No settings needed

Not yet available on guardoath.com (calls a paid outside provider; never available to demo users).

Change your checks

Send POST /v1/tenants/current/checks with your record's current revision (from GET /v1/applications/current) and the fields to change:

{"revision": 3, "changes": {"check_pii": "redact", "check_text_security": null, "default_profile": "tenant"}}

Errors and limits

Errors return JSON with a detail message, except those from the network edge. Only checks that return HTTP 200 are counted.

HTTPWhenWhat to do
400Malformed JSON, ambiguous headers, or JSON nested deeper than 32 levels / more than 1,000 items in one list or objectFix the request.
401"Invalid application credential": missing or wrong keyCheck the X-GuardOath-Key header.
403"Credential does not authorize this application": application_id is not your tenant id. A 403 without that message (from the network edge) means the body hash is missing or does not match the body, or too many requests came from your address (more than 300 in 5 minutes)Send your own tenant id; send x-amz-content-sha256 of the exact body; slow down.
404"Policy version not found": a profile you cannot useUse a profile listed by GET /v1/applications/current, or leave policy_version out.
408The request body arrived too slowly (over 5 seconds)Send it again.
409"The tenant record changed; reload it and try again"Read revision again and resend your change.
413Request body over 262,144 bytesSend less content per check.
415Not application/json, not UTF-8, or compressedSend Content-Type: application/json as plain UTF-8.
422"Invalid check request" (a missing or unknown field, content over 32,000 characters, ...) or a change that was refused, with the reasonFix the request; the message says what was wrong for changes.
429"Monthly check limit reached" (demo users: 2,000 checks a month), or "Application check rate limit reached" / "Application check concurrency limit reached" with a Retry-After header. A 429 whose body contains ConcurrentInvocationLimitExceeded means the service was busy and nothing ranWait for the next month, or the Retry-After seconds; a busy 429 can be sent again after a short wait.
503Capacity is temporarily used upTry again shortly.

Your data