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_content | Reading or displaying publicly accessible content |
scrape_data | Automated bulk scraping or crawling |
api_access | Programmatic API access |
create_account | Creating user accounts programmatically |
make_purchases | Completing automated purchases or transactions |
post_content | Publishing or submitting content |
allow_training | Using site content to train ML models |
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:
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:
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.
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]"
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 proceedOpenTermsGuardTool— returns a go or no-go decision; agent uses this as a pre-action gateOpenTermsGuardedTool— wraps any downstream CrewAIBaseTool; the wrapped tool executes only if the check returnsallow
Install:
pip install crewai-openterms # With openterms-py SDK (recommended — more accurate checks): pip install "crewai-openterms[sdk]"
Minimal example — OpenTermsCheckTool in a CrewAI agent:
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:
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")
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 returnsallowOpenTermsChecker— standalone tool an agent can call to check permissions explicitlyOpenTermsCallbackHandler— passive observer; logs permission checks without blocking (monitoring only — does not enforce permissions)
Install:
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:
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:
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"])
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_statusfield before relying on entries in high-stakes contexts. - Entries can become stale when a domain updates its terms of service.
openterms.jsonfiles include anexpiresfield; treat expired entries as provisional. - During the public alpha, most domains have not published an
openterms.jsonfile. Ano_openterms_jsonresult means the domain has not adopted the standard — it does not mean the action is permitted.