v0.3.0 — Current

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:

openterms.json
{
  "$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

  1. Services publish an openterms.json at their domain root (or any discoverable URL)
  2. Agents query the file before taking actions — permissions are structured data, not legalese
  3. 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 unconditionally
  • false — denied
  • Conditional object — allowed with conditions

Standard Permissions

PermissionDescription
read_contentRead publicly available content
create_accountCreate user accounts
make_purchasesMake purchases or financial transactions
scrape_dataScrape or bulk-download data
post_contentPost, publish, or submit content
modify_dataModify existing data or settings
delete_dataDelete data
automated_messagingSend messages to users
api_accessAccess the service's API
browser_automationUse browser automation tools
execute_codeExecute code on the platform New
access_user_dataAccess 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"
  }
}
FieldTypeDescription
allowedbooleanRequired Whether the permission is granted.
conditionsstringHuman-readable conditions or restrictions.
requires_authbooleanWhether this permission requires authentication.
max_frequencystringRate limit for this specific action. E.g. "10/hour", "100/day". New
scopestringWhat 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"]
  }
}
FieldTypeDescription
stores_agent_databooleanStores data about agent interactions?
shares_with_third_partiesbooleanShares agent data with third parties?
retention_daysintegerDays data is retained. 0 = no retention.
gdpr_compliantbooleanGDPR compliant?
ccpa_compliantbooleanCCPA compliant?
hipaa_compliantbooleanHIPAA compliant? New
data_residencystring | 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..."
  }
}
FieldTypeDescription
jwks_urlstring (URI)URL to your JWKS endpoint for verifying signed receipts.
signing_algorithmstringOne of: Ed25519, RS256, ES256.
policy_hashstringSHA-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"
    }
  }
}
FieldTypeDescription
sourcestringOrigin of this file. Typically "self" (written by the domain owner) or "openterms.com" (auto-generated).
generatorstringTool or service that generated this file, in reverse-domain/version format. E.g. "openterms.com/v0.3.0".
generated_atstring (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."

FieldTypeDescription
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

FieldRequiredValuesDescription
urlRequiredstring (URI)URL of the MCP server endpoint.
transportRequired"sse" | "stdio" | "streamable-http"Transport protocol used by this MCP server.
descriptionOptionalstringHuman-readable summary of what this server provides.

API Spec entry fields

FieldRequiredValuesDescription
urlRequiredstring (URI)URL to the API specification document.
typeRequired"openapi_3" | "swagger_2" | "graphql_schema"The specification format.
descriptionOptionalstringHuman-readable description of this API spec.

Complete v0.3.0 Example

A full openterms.json showing both permissions and discovery populated:

openterms.json (v0.3.0)
{
  "$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 CaseFileKey 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:

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:

langchain_agent.py
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:

crewai_agent.py
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:

shell
$ 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.zip
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:

index.json (excerpt)
{
  "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:

  1. OpenTerms declares what's allowed (the policy)
  2. ORS proves an agent read and acknowledged that policy (the receipt)
  3. The policy_id links 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.json URL that was checked
  • Action — the specific permission requested (e.g., scrape_data, api_access)
  • Decisionallow, deny, or not_specified
  • Timestamp — ISO 8601, high precision
  • SHA-256 Hash — cryptographic hash of the openterms.json content 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

Python SDK
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:

GitHub Actions
- 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
npm script
// 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'"
  }
}