OpenTerms Specification
Machine-readable legal terms for the agentic web. Define what AI agents can and can't do on your service — in a format they can actually parse.
OpenTerms is a JSON-based protocol that lets services declare their terms of service, permissions, rate limits, and compliance requirements in a structured format. Think of it as robots.txt for AI agent behavior.
New in v0.2.0: JSON-LD support (@context), policy identifiers (policy_id), ORS verification integration, HIPAA compliance fields, data residency, permission scoping/frequency, and an extensions namespace.
New in v0.3.0: discovery object — machine-readable pointers to MCP servers and OpenAPI specs. Plus formalized extensions.com.openterms.meta namespace for generator and provenance metadata.
Quick Start
Create an openterms.json file and host it at the root of your domain:
{
"$schema": "https://openterms.com/schema/openterms.schema.json",
"openterms_version": "0.2.0",
"service": {
"name": "Your Service",
"domain": "yourservice.com",
"tos_url": "https://yourservice.com/terms"
},
"permissions": {
"read_content": true,
"create_account": false,
"make_purchases": false,
"scrape_data": false,
"api_access": true,
"browser_automation": false
},
"requires_consent": true,
"jurisdiction": "US",
"contact": "legal@yourservice.com",
"last_updated": "2025-06-01"
}
That's it. AI agents fetch https://yourservice.com/openterms.json before taking any action, just like crawlers check robots.txt.
Pro tip: Add the $schema field to get auto-completion and inline validation in VS Code, JetBrains, and other editors that support JSON Schema.
How It Works
- Services publish an
openterms.jsonat their domain root (or any discoverable URL) - Agents query the file before taking actions — permissions are structured data, not legalese
- Compliance is automatic — every agent action has a legal basis on record, with optional cryptographic receipts via ORS
Core Fields
| Field | Type | Status | Description |
|---|---|---|---|
openterms_version |
string |
Required | Spec version (e.g. "0.2.0"). Semver format. |
service |
string | object |
Required | Service info. Shorthand: "acme.com". Full: object with name, domain, tos_url, privacy_url, description, logo_url. |
permissions |
object |
Required | What agents can do. See Permissions section. |
$schema |
string (URI) |
Optional | Self-referencing schema URI. Enables editor auto-completion. |
@context |
string | object |
New | JSON-LD context for semantic web / linked data interoperability. |
policy_id |
string |
New | Globally unique identifier for this terms document. Used in ORS receipts and audit trails. |
requires_consent |
boolean |
Optional | Must the agent obtain explicit consent before acting? |
jurisdiction |
string | string[] |
Optional | ISO 3166-1/2 jurisdiction code(s). E.g. "US-DE", ["US-CA", "EU"]. |
contact |
string | object |
Optional | Legal contact. Shorthand: email string. Full: object with email, name, url. |
last_updated |
string (date) |
Optional | ISO 8601 date when terms were last modified. |
expires |
string (date) |
Optional | Date these terms expire. Agents should re-fetch after this date. |
discovery |
object |
v0.3.0 | Machine-readable pointers to MCP servers and API specs. See Discovery section. |
extensions |
object |
Optional | Namespace for custom or industry-specific fields. Use reverse-domain keys. See Extensions. |
Permissions
The permissions object defines what AI agents can do. Each value is either:
true— allowed unconditionallyfalse— denied- Conditional object — allowed with conditions
Standard Permissions
| Permission | Description |
|---|---|
read_content | Read publicly available content |
create_account | Create user accounts |
make_purchases | Make purchases or financial transactions |
scrape_data | Scrape or bulk-download data |
post_content | Post, publish, or submit content |
modify_data | Modify existing data or settings |
delete_data | Delete data |
automated_messaging | Send messages to users |
api_access | Access the service's API |
browser_automation | Use browser automation tools |
execute_code | Execute code on the platform New |
access_user_data | Access personal or user-specific data New |
Custom permissions are also allowed — the schema accepts any additional string keys with permission values.
Conditional Permission Object
{
"make_purchases": {
"allowed": true,
"conditions": "Max $500/day. Agent must be linked to verified human.",
"requires_auth": true,
"max_frequency": "50/day",
"scope": "authenticated"
}
}
| Field | Type | Description |
|---|---|---|
allowed | boolean | Required Whether the permission is granted. |
conditions | string | Human-readable conditions or restrictions. |
requires_auth | boolean | Whether this permission requires authentication. |
max_frequency | string | Rate limit for this specific action. E.g. "10/hour", "100/day". New |
scope | string | What data subset this applies to. E.g. "public", "authenticated", "premium". New |
Rate Limits
{
"rate_limits": {
"requests_per_minute": 60,
"requests_per_hour": 1000,
"requests_per_day": 10000,
"concurrent_sessions": 5
}
}
All fields are optional integers. concurrent_sessions is new in v0.2.0 and limits how many simultaneous agent connections are allowed.
Data Handling
{
"data_handling": {
"stores_agent_data": true,
"shares_with_third_parties": false,
"retention_days": 90,
"gdpr_compliant": true,
"ccpa_compliant": true,
"hipaa_compliant": false,
"data_residency": ["US", "EU"]
}
}
| Field | Type | Description |
|---|---|---|
stores_agent_data | boolean | Stores data about agent interactions? |
shares_with_third_parties | boolean | Shares agent data with third parties? |
retention_days | integer | Days data is retained. 0 = no retention. |
gdpr_compliant | boolean | GDPR compliant? |
ccpa_compliant | boolean | CCPA compliant? |
hipaa_compliant | boolean | HIPAA compliant? New |
data_residency | string | string[] | Where data is stored. ISO codes. New |
Authentication
{
"authentication": {
"required": true,
"methods": ["api_key", "oauth2"],
"registration_url": "https://acme.com/developers",
"docs_url": "https://docs.acme.com/auth"
}
}
Supported methods: api_key, oauth2, bearer_token, basic_auth, mTLS New, none.
The docs_url field is new in v0.2.0 — link directly to your auth documentation for faster agent onboarding.
Verification
New in v0.2.0. Enables ORS (Open Receipt Specification) integration — cryptographic receipts proving an agent acknowledged your terms before acting.
{
"verification": {
"jwks_url": "https://acme.com/.well-known/jwks.json",
"signing_algorithm": "Ed25519",
"policy_hash": "a1b2c3d4e5f6..."
}
}
| Field | Type | Description |
|---|---|---|
jwks_url | string (URI) | URL to your JWKS endpoint for verifying signed receipts. |
signing_algorithm | string | One of: Ed25519, RS256, ES256. |
policy_hash | string | SHA-256 hash of the canonical terms document. 64 hex chars. |
How ORS + OpenTerms work together: OpenTerms defines what agents can do. ORS provides cryptographic proof they acknowledged those terms. The policy_id field links your terms to ORS receipts.
Extensions
The extensions object is a namespace for custom or industry-specific fields. Use reverse-domain notation to avoid conflicts:
{
"extensions": {
"health.hipaa.baa_required": true,
"health.hipaa.audit_log_url": "https://acme.com/api/audit",
"com.acme.internal_tier": "enterprise",
"org.fintech.pci_dss_level": 1
}
}
Extensions are free-form — any JSON value is accepted. This keeps the core schema stable while allowing domain-specific needs.
com.openterms.meta (v0.3.1)
extensions.com.openterms.meta is the first official OpenTerms namespace. It records provenance — how and where this file was created. This namespace is placed inside extensions rather than at root level because the root schema uses additionalProperties: false to ensure forward compatibility and strict validation.
Why not a top-level field? The root schema is intentionally closed. New root fields require a spec version bump and breaking changes. The extensions namespace lets tools add structured metadata without modifying the core spec contract.
{
"extensions": {
"com.openterms.meta": {
"source": "self",
"generator": "openterms.com/v0.3.0"
}
}
}
| Field | Type | Description |
|---|---|---|
source | string | Origin of this file. Typically "self" (written by the domain owner) or "openterms.com" (auto-generated). |
generator | string | Tool or service that generated this file, in reverse-domain/version format. E.g. "openterms.com/v0.3.0". |
generated_at | string (datetime) | ISO 8601 timestamp of when this file was generated. E.g. "2025-06-01T12:00:00Z". |
The validator displays a note when com.openterms.meta is present, indicating the file was auto-generated and showing the generator version.
Discovery (v0.3.0)
The discovery object positions openterms.json as both the legal permissions layer and the technical discovery entry point for a domain's agent-facing infrastructure. It provides machine-readable signposts to existing technical resources — MCP servers, OpenAPI specs — that an agent can connect to directly.
Discovery does not describe what those servers do. It points to them. OpenTerms doesn't duplicate what MCP manifests or OpenAPI specs already define. It simply says: "these endpoints exist and are permitted."
| Field | Type | Description |
|---|---|---|
mcp_servers |
array |
List of MCP (Model Context Protocol) server endpoints. Each entry has url, transport, and optional description. |
api_specs |
array |
List of API specification documents. Each entry has url, type, and optional description. |
MCP Server entry fields
| Field | Required | Values | Description |
|---|---|---|---|
url | Required | string (URI) | URL of the MCP server endpoint. |
transport | Required | "sse" | "stdio" | "streamable-http" | Transport protocol used by this MCP server. |
description | Optional | string | Human-readable summary of what this server provides. |
API Spec entry fields
| Field | Required | Values | Description |
|---|---|---|---|
url | Required | string (URI) | URL to the API specification document. |
type | Required | "openapi_3" | "swagger_2" | "graphql_schema" | The specification format. |
description | Optional | string | Human-readable description of this API spec. |
Complete v0.3.0 Example
A full openterms.json showing both permissions and discovery populated:
{
"$schema": "https://openterms.com/schema/openterms.schema.json",
"openterms_version": "0.3.0",
"service": "acme-corp.com",
"permissions": {
"read_content": true,
"scrape_data": false,
"api_access": {
"allowed": true,
"requires_auth": true,
"max_frequency": "1000/hour"
}
},
"discovery": {
"mcp_servers": [
{
"url": "https://acme-corp.com/mcp/sse",
"transport": "sse",
"description": "Provides tools for checking order status and inventory."
}
],
"api_specs": [
{
"url": "https://api.acme-corp.com/v1/openapi.json",
"type": "openapi_3",
"description": "Full REST API for catalog and user management."
}
]
}
}
Discovery is a signpost, not a description layer. MCP servers already have manifests. OpenAPI specs already describe endpoints. The discovery field simply makes those resources findable via a single, standardized location — no duplicated documentation required.
Examples
Complete, validated examples for common use cases:
| Use Case | File | Key Features |
|---|---|---|
| SaaS API | saas-api.json |
Full API with OAuth, rate limits, conditional purchases, sandboxed code execution |
| E-Commerce | ecommerce.json |
Purchase limits, product scraping with conditions, multi-jurisdiction |
| Social Platform | social-platform.json |
AI disclosure requirements, DM opt-in, frequency limits per permission |
| Open/Public API | open-api.json |
Minimal restrictions, high rate limits, no auth required |
| Healthcare (HIPAA) | healthcare.json |
HIPAA fields, ORS verification, BAA requirement, extensions namespace, mTLS auth |
Load any example directly in the Validator to explore it interactively.
Adoption Guide
Step 1: Create your openterms.json
Start with the Quick Start template. Add permissions that match your service's terms of service. Be explicit — false is better than omitting a permission.
Step 2: Host it
Place the file at https://yourdomain.com/openterms.json — the standard discovery path. Alternatively, reference it from your existing robots.txt:
# AI Agent Terms OpenTerms: https://yourdomain.com/openterms.json
Step 3: Validate
Use the interactive validator or the programmatic API:
curl -X POST https://openterms.com/api/validate \
-H "Content-Type: application/json" \
-d '{"content": <your openterms.json>}'
Step 4: Keep it updated
Update last_updated whenever you change terms. Set expires to force agents to re-fetch periodically.
Integrations
Add OpenTerms checks to your agent framework in minutes. Copy, paste, done.
LangChain Tool
Use the check_permissions tool as a guard before any agent action:
from langchain.tools import tool import openterms # Check if an AI agent is allowed to perform an action on a domain. @tool def check_permissions(domain: str, action: str) -> str: result = openterms.check(domain, action) if result.allowed: return f("Allowed: {} on {}".format(action, domain)) return f("Denied: {}".format(result.decision))
CrewAI Agent Guard
Wrap tasks with a policy check before execution. Returns a compliance receipt:
from crewai import Agent, Task import openterms def guarded_task(domain, action, task_fn): result = openterms.check(domain, action) if not result.allowed: return f("Blocked by policy: {}".format(result.decision)) receipt = openterms.receipt(domain, action, result.decision) output = task_fn() return {"output": output, "compliance_receipt": receipt.to_dict()}
Direct API (Any Language)
Query the OpenTerms Registry API from any environment with a single curl call:
$ curl https://api.openterms.com/registry/github-com/status { "exists": true, "source": "claimed", "domain": "github.com" }
See the full SDK at openterms-py.
Bulk Download
Download the entire registry as a single ZIP file — 500+ openterms.json entries, organized by validation status. Ideal for bootstrapping local policy enforcement, training datasets, or offline analysis.
⬇ Download the full dataset
openterms.com/registry/download
— live ZIP, generated fresh from the registry on every request.
ZIP Structure
openterms-registry-seed/ ├── validated/ │ ├── github-com.json │ ├── stripe-com.json │ └── ... (all validated entries) ├── unvalidated/ │ ├── example-com.json │ └── ... (all unvalidated entries) ├── flagged/ │ └── ... (entries with data quality issues) ├── index.json ← manifest: domain, category, validation_status, confidence └── README.md ← schema version, generated timestamp, usage
index.json Schema
The index.json manifest lists every entry with metadata, making it easy to filter locally without parsing each file:
{
"generated_at": "2026-04-16T14:00:00.000Z",
"schema_version": "0.3.1",
"total": 511,
"counts": { "validated": 53, "unvalidated": 458, "flagged": 0 },
"entries": [
{
"domain": "github.com",
"filename": "github-com.json",
"category": "Developer Tools",
"validation_status": "validated",
"confidence": 0.9
}
]
}
Use Cases
- Local policy enforcement — embed the dataset in your agent runtime for zero-latency permission checks
- Offline environments — air-gapped or latency-sensitive deployments that can't call the live API
- Training data — structured ToS signals for fine-tuning AI models
- Compliance audits — snapshot the registry state at a point in time
ORS Integration
The Open Receipt Specification (ORS) provides cryptographic receipts proving AI agents acknowledged specific policies before taking actions.
OpenTerms + ORS together create a complete policy lifecycle:
- OpenTerms declares what's allowed (the policy)
- ORS proves an agent read and acknowledged that policy (the receipt)
- The
policy_idlinks the two together
To enable ORS, add the verification and policy_id fields to your openterms.json. See the Healthcare example for a complete implementation.
Prove Your Agent Followed the Rules. Cryptographically.
The Problem
Standard application logs are not sufficient evidence when a website owner claims an agent violated their terms. Logs can be altered. They don't prove what the terms said at the exact moment the agent acted.
How ORS Solves It
An ORS receipt contains five components:
- Domain — the
openterms.jsonURL that was checked - Action — the specific permission requested (e.g.,
scrape_data,api_access) - Decision —
allow,deny, ornot_specified - Timestamp — ISO 8601, high precision
- SHA-256 Hash — cryptographic hash of the
openterms.jsoncontent at time of check
If a website changes its terms after an agent acted, the receipt proves the action was permitted at the time. The hash proves the openterms.json content has not been altered.
The legal equivalent of a screenshot, but cryptographically verifiable.
Implementation
import openterms
result = openterms.check("example.com", "api_access")
receipt = openterms.receipt("example.com", "api_access", result.decision)
# receipt.to_dict() returns:
# {
# "domain": "example.com",
# "action": "api_access",
# "decision": "allow",
# "timestamp": "2026-04-12T14:32:01Z",
# "openterms_hash": "e3b0c44..."
# }
# Store in your audit log
import json
with open("compliance.jsonl", "a") as f:
f.write(json.dumps(receipt.to_dict()) + "\n")
Regulatory context: ORS is designed to support compliance with emerging AI legislation including the EU AI Act. Articles 12 and 13 require automatic logging and transparency for high-risk AI systems. ORS receipts provide a tamper-evident audit trail aligned with these obligations. This is not legal advice — consult qualified counsel for compliance decisions.
Read the full ORS specification →
CI/CD Validation
Validate your openterms.json in CI/CD pipelines using the API endpoint:
- name: Validate openterms.json
run: |
RESULT=$(curl -s -X POST https://openterms.com/api/validate \
-H "Content-Type: application/json" \
-d "{\"content\": $(cat openterms.json)}")
echo "$RESULT" | jq .
VALID=$(echo "$RESULT" | jq -r '.valid')
if [ "$VALID" != "true" ]; then
echo "openterms.json validation failed!"
exit 1
fi
// package.json
{
"scripts": {
"validate:terms": "curl -sf -X POST https://openterms.com/api/validate -H 'Content-Type: application/json' -d '{\"content\":'$(cat openterms.json)'}' | jq -e '.valid'"
}
}