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
- Your user sends a message. Your application sends it to
POST /v1/checks/input. - Only if the input check lets it through, send the message to your AI model.
- Send the model's answer to
POST /v1/checks/output. - 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.
- To try it: pick a demo user in the check playground and select Get demo API key, or call
POST /v1/demo-users/demo1/keywith the body{}. Demo users are shared: anyone who picks the same one uses the same settings and the same 2,000 checks a month. Do not send real personal data through a demo user. - For your application: customer keys are issued with your GuardOath account. A key is shown once and only a fingerprint of it is stored, so keep it safe. Keep it on your server; never put a customer key in a web page or mobile app.
Send a check
Base address: https://guardoath.com. Every request with a body is JSON over HTTPS and needs these headers:
| Header | Value |
|---|---|
X-GuardOath-Key | Your API key. |
Content-Type | application/json (UTF-8). |
x-amz-content-sha256 | The 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
| Field | Required | What it is |
|---|---|---|
application_id | yes | Your tenant id (1–100 characters). |
content | yes | The message or answer to check, 1–32,000 characters. For JSON content, send the JSON as a string. |
content_format | no | text (default) or json. JSON checks (schema, field comparisons, decision records, citations, tool calls, ledger, timestamps, graph) need json. |
policy_version | no | The profile to run for this request only. Leave it out to use your default profile. See Profiles. |
request_context | no | Up to 8,000 characters of context. Accepted, but not inspected by the current checks. |
language | no | en, 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
}
| Field | Meaning |
|---|---|
effective_action | What to do now: allow, flag, redact or block. In observe mode it is always allow. |
recommended_action | What the checks found, whatever the mode. In observe mode, this shows what enforce mode would do. |
evaluation_status | complete, 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. |
findings | What was found: rule_id, category, severity and a plain-language explanation. Findings never repeat the text that matched. |
checker_results | One entry per check that ran, with status (passed, flagged, error, skipped, unsupported) and, on errors, an error_code. |
redaction | For 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, stage | The 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
| Method | Path | Key needed | What it does |
|---|---|---|---|
GET | /v1/demo-users | none | The demo users you can pick (demo1 to demo10). |
POST | /v1/demo-users/{tenant_id}/key | none (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/current | yes | Your 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/input | yes | Check the user's message before it goes to your AI model. Counts as one check. |
POST | /v1/checks/output | yes | Check the AI's answer before it reaches your user. Counts as one check. |
POST | /v1/tenants/current/checks | yes | Change your own checks, settings or default profile (see below). Not counted as a check. |
POST | /v1/tenants/current/reset | yes, demo users only | Put a demo user's checks, settings and default profile back to the starting values. |
GET | /healthz | none | Returns {"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.
| Profile | What it checks | Checks | Mode | Who can use it |
|---|---|---|---|---|
tenant | Checks 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 record | Your record's mode (demo users: enforce) | Everyone |
conversation-v1 | Conversation: 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_pii | observe | Everyone |
pii-v1 | Personal data: email, card, SSN Email, card and U.S. SSN rules plus the 90 privacy pattern types. | pii | observe | Everyone |
pii-extended-v1 | Personal data: labeled fields too Adds labeled phone, birth date, medical record, health plan and bank account fields. | pii | observe | Everyone |
redaction-v1 | Personal data: redact Replaces found identifiers and runs the checks again on the cleaned text. | pii | observe | Everyone |
privacy-encoded-v1 | Personal data: inside encodings Personal data in plain text and inside one labeled Base64/hex layer. | pii, encoded_pii | observe | Everyone |
privacy-encoded-redact-v1 | Personal data: inside encodings, redact Redacts plain identifiers and blocks encoded ones. | pii, encoded_pii | observe | Everyone |
secrets-v1 | Secrets: credential patterns Selected provider keys, JWTs, private keys and passwords in URLs. | secrets | observe | Everyone |
secrets-extended-v1 | Secrets: extended Adds quoted password assignments, labeled encoded secrets and confidential phrases. | secrets | observe | Everyone |
text-security-v1 | Hidden text and encodings Invisible and direction-changing characters, and labeled Base64/hex text. | text_security | observe | Everyone |
structure-v1 | JSON structure (schema) JSON Schema: required fields, types, formats and limits. | schema | observe | Everyone |
business-v1 | Business rules: dates and refunds JSON Schema plus start date before end date, and refund no larger than purchase. | schema, cross_field | observe | Everyone |
business-number-le-v1 | Business rules: number comparison One numeric comparison: left value no larger than right value. | cross_field | observe | Everyone |
completeness-v1 | Required sections Requires “Summary:” in the message, and “Summary:” and “Next steps:” in the answer. | completeness | observe | Everyone |
content-safety-v1 | Content safety (outside provider) Everyday checks plus an outside content-safety provider. Not configured on this local server. | pii, secrets, text_security, content_safety | observe | Customers, once the outside provider is set up (not yet on guardoath.com) |
bias-matched-string-v1 | Matched decisions: text outcomes Compares decision records that differ only by group; outcomes are text. | matched_outcomes | observe | Everyone |
bias-matched-boolean-v1 | Matched decisions: yes/no outcomes Same comparison with yes/no outcomes. | matched_outcomes | observe | Everyone |
bias-matched-number-v1 | Matched decisions: number outcomes Same comparison with numeric outcomes. | matched_outcomes | observe | Everyone |
evidence-v1 | Citation evidence Citations and quotations checked word for word against a source catalog. | evidence | observe | Everyone |
tool-calls-v1 | Proposed tool calls Allowed tool names and argument schemas. | tool_calls | observe | Everyone |
ledger-v1 | Ledger totals Debits and credits add up exactly. | ledger | observe | Everyone |
timestamps-v1 | Timestamp order Timestamps are in order. | timestamps | observe | Everyone |
graph-v1 | Graph structure No cycles, self-loops, duplicate edges or unknown nodes. | graph | observe | Everyone |
integrity-v1 | Content integrity (SHA-256) Content matches an approved fingerprint. | integrity | observe | Everyone |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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"}}
- You can change: every
check_<id>field, the check settings,default_profile,mode(enforceorobserve),timeout_ms(1–5,000) andfailure_action(blockorflag). - At least one check must stay on. A change that is refused says why (HTTP 422) and changes nothing.
- If the record changed since you read it, you get HTTP 409: read the revision again and resend.
- Changes apply from the next check and are not counted as checks. Demo users can go back to the starting values with
POST /v1/tenants/current/reset(body{}).
Errors and limits
Errors return JSON with a detail message, except those from the network edge. Only checks that return HTTP 200 are counted.
| HTTP | When | What to do |
|---|---|---|
400 | Malformed JSON, ambiguous headers, or JSON nested deeper than 32 levels / more than 1,000 items in one list or object | Fix the request. |
401 | "Invalid application credential": missing or wrong key | Check 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 use | Use a profile listed by GET /v1/applications/current, or leave policy_version out. |
408 | The 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. |
413 | Request body over 262,144 bytes | Send less content per check. |
415 | Not application/json, not UTF-8, or compressed | Send 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 reason | Fix 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 ran | Wait for the next month, or the Retry-After seconds; a busy 429 can be sent again after a short wait. |
503 | Capacity is temporarily used up | Try again shortly. |
Your data
- Your tenant record keeps your settings and one usage count per month. The content you send for checking is not saved in your record.
- Demo users are shared by everyone who picks them: use synthetic data only.
- Findings describe what was found without repeating the matched text.