SDKs & Integrations

Three packages — one for plain Python, one for CrewAI, one for LangChain. All are fail-closed by default and use the same seven canonical permission keys.

Which package should I use?

Situation Package Install
Plain Python — no agent framework openterms-py pip install openterms-py
Building with CrewAI crewai-openterms pip install crewai-openterms
Building with LangChain langchain-openterms pip install langchain-openterms
CrewAI or LangChain + SDK accuracy wrapper + [sdk] extra pip install "crewai-openterms[sdk]"
pip install "langchain-openterms[sdk]"

Use openterms-py directly when you want to call the check, fetch, discover, or receipt functions in plain Python without a framework dependency. Use the framework wrappers when your agent is already built on CrewAI or LangChain — they expose OpenTerms as a native tool or guard that integrates with the framework's tool-calling lifecycle.

Public alpha note: During the public alpha, most domains have not yet published an openterms.json file. All three packages return not_specified (or no_openterms_json) for those domains — this is the expected response, not an error. The fail-closed default blocks on this result unless you explicitly pass fail_closed=False.

Security model

Explicit allow, fail-closed defaults

Every package shares the same decision model: execution is blocked unless the check returns an explicit allow. This is called fail-closed behavior. All ambiguous, absent, or unresolvable results map to a non-allow state that blocks by default.

The four possible check outcomes:

Result Meaning Default behavior
allow Permission explicitly granted in openterms.json Proceed
deny Permission explicitly denied in openterms.json Block (always)
not_specified Key absent, value is null, or file unreachable Block (fail-closed)
no_openterms_json Domain hasn't published openterms.json — expected during public alpha Block (fail-closed)

Permissive opt-in

If you explicitly want to proceed when not_specified or no_openterms_json is returned, pass fail_closed=False to the relevant tool or guard. This is an explicit choice — the caller accepts responsibility for proceeding without permission. Never leave fail_closed=False as a silent default in production code.

Guarded tool execution

The CrewAI and LangChain wrappers both provide a guarded-wrapper pattern: a tool that wraps any downstream tool and runs the OpenTerms check as a gate before the wrapped tool executes. The wrapped tool never runs unless the check returns allow.

Canonical permission keys

All three packages document and check only these seven keys. Non-canonical keys may be passed to the API but are treated as not_specified by the SDK unless the domain's openterms.json explicitly contains them.

Key What it covers
read_contentReading or displaying publicly accessible content
scrape_dataAutomated bulk scraping or crawling
api_accessProgrammatic API access
create_accountCreating user accounts programmatically
make_purchasesCompleting automated purchases or transactions
post_contentPublishing or submitting content
allow_trainingUsing site content to train ML models

openterms-py

v0.4.0 · Python 3.9+
pip install openterms-py

crewai-openterms

v0.4.0 · Python 3.10+
pip install crewai-openterms

langchain-openterms

v0.4.0 · Python 3.9+
pip install langchain-openterms

openterms-py — Core Python SDK

The core SDK for plain Python. No framework dependency. Use this when you want to call check(), fetch(), discover(), or receipt() directly in your code.

Install:

shell
pip install openterms-py

# Optional: receipts support
pip install "openterms-py[receipts]"

# Optional: async support (httpx)
pip install "openterms-py[async]"

Minimal example — permission gate before an HTTP call:

Python — fail-closed permission gate
import openterms
import requests

def fetch_if_permitted(domain: str, url: str) -> dict | None:
    result = openterms.check(domain, "api_access")

    if not result:  # False when decision is "deny" or "not_specified"
        print(f"Blocked: api_access is '{result.decision}' for {domain}")
        return None

    return requests.get(url, timeout=10).json()


# result.decision → "allow" | "deny" | "not_specified"
# bool(result)    → True only when decision is "allow"

Receipts (v0.4.0+)

v0.4.0 adds opt-in receipt generation. A receipt is a local signed record of a permission-check result — nothing is sent to any server.

Receipt opt-in only. Receipt generation requires passing receipt=True explicitly. The default check() behavior is unchanged — no receipts are generated unless you opt in. Receipts are local signed records. They do not prove: website-owner approval, registry correctness, or permission outside the recorded check result.

Python — receipt opt-in
import openterms

# Default check — no receipt generated (behavior unchanged from v0.3.x)
result = openterms.check("example.com", "read_content")

# Opt-in to receipt generation
result = openterms.check("example.com", "read_content", receipt=True)
receipt = result.signed_receipt  # local signed record of the check result

# Install receipts extra for this feature:
# pip install "openterms-py[receipts]"

Links: PyPI · GitHub

crewai-openterms — CrewAI integration

Permission-aware tools for CrewAI agents. Three tools ship in this package:

  • OpenTermsCheckTool — agent calls this to look up permissions for a domain before deciding whether to proceed
  • OpenTermsGuardTool — returns a go or no-go decision; agent uses this as a pre-action gate
  • OpenTermsGuardedTool — wraps any downstream CrewAI BaseTool; the wrapped tool executes only if the check returns allow

Install:

shell
pip install crewai-openterms

# With openterms-py SDK (recommended — more accurate checks):
pip install "crewai-openterms[sdk]"

Minimal example — OpenTermsCheckTool in a CrewAI agent:

crewai_check_example.py
from crewai import Agent, Task, Crew
from crewai_openterms import OpenTermsCheckTool

check_tool = OpenTermsCheckTool()

agent = Agent(
    role="Web Research Agent",
    goal="Check site permissions before acting",
    backstory="Respects publisher terms before taking action.",
    tools=[check_tool],
)

task = Task(
    description="Check whether scrape_data is permitted for example.com",
    expected_output="Permission result from OpenTerms",
    agent=agent,
)

crew = Crew(agents=[agent], tasks=[task])
result = crew.kickoff(
    inputs={"domain": "example.com", "action": "scrape_data"}
)

Minimal example — OpenTermsGuardedTool wrapping a downstream tool:

crewai_guarded_example.py
from crewai.tools import BaseTool
from crewai_openterms import OpenTermsGuardedTool


class FetchPageTool(BaseTool):
    name: str = "fetch_page"
    description: str = "Fetch a page after permission is granted."

    def _run(self, url: str, **kwargs):
        return f"Fetched {url}"


fetch_page = FetchPageTool()

# Wrapped tool: FetchPageTool only executes when OpenTerms returns allow
guarded_fetch = OpenTermsGuardedTool(
    wrapped_tool=fetch_page,
    action="read_content",
    fail_closed=True,  # default — block on not_specified / no_openterms_json
)

result = guarded_fetch._run(url="https://example.com/page")

Links: PyPI · GitHub

langchain-openterms — LangChain integration

Permission-aware tools for LangChain agents. Three integration patterns:

  • OpenTermsGuard — wraps any LangChain tool; blocks the wrapped tool unless the check returns allow
  • OpenTermsChecker — standalone tool an agent can call to check permissions explicitly
  • OpenTermsCallbackHandler — passive observer; logs permission checks without blocking (monitoring only — does not enforce permissions)

Install:

shell
pip install langchain-openterms

# With openterms-py SDK (recommended — requires openterms-py>=0.4.0):
pip install "langchain-openterms[sdk]"

Minimal example — OpenTermsGuard wrapping a LangChain tool:

langchain_guard_example.py
from langchain_openterms import OpenTermsGuard

# Replace BraveSearch with any LangChain tool that takes a URL
# from langchain_community.tools import BraveSearch
# search = BraveSearch.from_api_key(api_key="...", search_kwargs={"count": 3})

# Wrap the tool — fail-closed by default
guarded_search = OpenTermsGuard(
    tool=search,
    action="read_content",
    # fail_closed=True is the default — omit it or set explicitly
)

result = guarded_search.invoke("https://example.com/pricing")

if "blocked" in result.lower():
    print("Cannot proceed:", result)
else:
    print("Allowed:", result)

Minimal example — OpenTermsChecker as a standalone agent tool:

langchain_checker_example.py
import json
from langchain_openterms import OpenTermsChecker

checker = OpenTermsChecker()

# Agent invokes: "example.com scrape_data"
result_json = checker.invoke("example.com scrape_data")
parsed = json.loads(result_json)

# Gate strictly on allowed=True
if parsed["check"]["allowed"] is True:
    print("Permitted — proceed")
else:
    # blocked: denied, not_specified, no_openterms_json, conditional
    print("Blocked:", parsed["check"]["reason"])

Links: PyPI · GitHub

Fail-closed reference table

The table below applies to all three packages. The default behavior column assumes fail_closed=True (the default). Permissive opt-in requires fail_closed=False and is the caller's explicit responsibility.

Result state Covers Default behavior fail_closed=False
allow Explicit true in openterms.json Proceed Proceed
deny Explicit false in openterms.json Block Block
not_specified Key absent or value is null Block (fail-closed) Pass through
no_openterms_json Domain hasn't published file — normal during alpha Block (fail-closed) Pass through
conditional allowed: true/false with conditions string Block or escalate Block or escalate
low confidence Confidence score below threshold Block or escalate Block or escalate

Registry records disclaimer

Registry records, not rulings. Permission values returned by these packages are registry records derived from machine-readable openterms.json files. They reflect what a domain's published file says — not a review of the domain's full terms of service.

Treat them as one structured input to agent decision logic, not as a substitute for legal review in regulated contexts. When in doubt, escalate to a human.

Additional caveats to be aware of:

  • Registry entries may be machine-generated and have not all been human-validated. Check the validation_status field before relying on entries in high-stakes contexts.
  • Entries can become stale when a domain updates its terms of service. openterms.json files include an expires field; treat expired entries as provisional.
  • During the public alpha, most domains have not published an openterms.json file. A no_openterms_json result means the domain has not adopted the standard — it does not mean the action is permitted.