|
| 1 | +# APoP Python SDK |
| 2 | + |
| 3 | +**Agent Policy Protocol (APoP) SDK for Python** — the authorization & consent layer for the agentic web. |
| 4 | + |
| 5 | +> Like `robots.txt`, but for AI agents — with fine-grained access control, identity verification, rate limiting, and cross-protocol interoperability. |
| 6 | +
|
| 7 | +[](https://pypi.org/project/apop/) |
| 8 | +[](https://www.python.org/downloads/) |
| 9 | +[](https://opensource.org/licenses/Apache-2.0) |
| 10 | + |
| 11 | +## Installation |
| 12 | + |
| 13 | +```bash |
| 14 | +pip install apop |
| 15 | +``` |
| 16 | + |
| 17 | +With framework extras: |
| 18 | + |
| 19 | +```bash |
| 20 | +pip install apop[fastapi] # FastAPI / Starlette |
| 21 | +pip install apop[flask] # Flask |
| 22 | +pip install apop[django] # Django |
| 23 | +pip install apop[all] # All frameworks |
| 24 | +``` |
| 25 | + |
| 26 | +## Quick Start |
| 27 | + |
| 28 | +### 1. Create your policy (`agent-policy.json`) |
| 29 | + |
| 30 | +```json |
| 31 | +{ |
| 32 | + "version": "1.0", |
| 33 | + "defaultPolicy": { |
| 34 | + "allow": true, |
| 35 | + "disallow": ["extract", "automated_purchase"], |
| 36 | + "rateLimit": { "requests": 100, "window": "hour" } |
| 37 | + }, |
| 38 | + "pathPolicies": [ |
| 39 | + { "path": "/admin/*", "allow": false }, |
| 40 | + { "path": "/api/**", "allow": true, "requireVerification": true } |
| 41 | + ] |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +### 2. Use with FastAPI |
| 46 | + |
| 47 | +```python |
| 48 | +from fastapi import FastAPI |
| 49 | +from apop.parser import parse_policy_file |
| 50 | +from apop.middleware.fastapi import create_fastapi_middleware, create_discovery_route |
| 51 | +from apop.types import MiddlewareOptions |
| 52 | + |
| 53 | +app = FastAPI() |
| 54 | + |
| 55 | +# Load and validate policy |
| 56 | +result = parse_policy_file("agent-policy.json") |
| 57 | +policy = result.policy |
| 58 | + |
| 59 | +# Add enforcement middleware |
| 60 | +app.add_middleware( |
| 61 | + create_fastapi_middleware(MiddlewareOptions(policy=policy)) |
| 62 | +) |
| 63 | + |
| 64 | +# Serve policy at well-known URL |
| 65 | +app.get("/.well-known/agent-policy.json")(create_discovery_route(policy)) |
| 66 | +``` |
| 67 | + |
| 68 | +### 3. Use with Flask |
| 69 | + |
| 70 | +```python |
| 71 | +from flask import Flask |
| 72 | +from apop.parser import parse_policy_file |
| 73 | +from apop.middleware.flask import create_flask_middleware, create_flask_discovery |
| 74 | +from apop.types import MiddlewareOptions |
| 75 | + |
| 76 | +app = Flask(__name__) |
| 77 | + |
| 78 | +result = parse_policy_file("agent-policy.json") |
| 79 | +policy = result.policy |
| 80 | + |
| 81 | +create_flask_middleware(app, MiddlewareOptions(policy=policy)) |
| 82 | +create_flask_discovery(app, policy) |
| 83 | +``` |
| 84 | + |
| 85 | +### 4. Use with Django |
| 86 | + |
| 87 | +```python |
| 88 | +# settings.py |
| 89 | +MIDDLEWARE = [ |
| 90 | + # ... other middleware ... |
| 91 | + 'apop.middleware.django.APoPMiddleware', |
| 92 | +] |
| 93 | + |
| 94 | +APOP_POLICY_FILE = BASE_DIR / "agent-policy.json" |
| 95 | +``` |
| 96 | + |
| 97 | +### 5. Programmatic Enforcement |
| 98 | + |
| 99 | +```python |
| 100 | +from apop.parser import parse_policy |
| 101 | +from apop.enforcer import enforce |
| 102 | +from apop.types import RequestContext |
| 103 | + |
| 104 | +result = parse_policy('{"version":"1.0","defaultPolicy":{"allow":true}}') |
| 105 | +policy = result.policy |
| 106 | + |
| 107 | +decision = enforce(policy, RequestContext( |
| 108 | + path="/api/data", |
| 109 | + agent_name="TestBot/1.0", |
| 110 | + agent_intent="read", |
| 111 | + agent_id="did:web:testbot.com", |
| 112 | + agent_signature="sig123", |
| 113 | +)) |
| 114 | + |
| 115 | +print(decision.status) # "allowed" |
| 116 | +print(decision.http_status) # 200 |
| 117 | +print(decision.headers) # {"Agent-Policy-Status": "allowed", ...} |
| 118 | +``` |
| 119 | + |
| 120 | +### 6. Policy Discovery |
| 121 | + |
| 122 | +```python |
| 123 | +import asyncio |
| 124 | +from apop.discovery import discover_policy |
| 125 | + |
| 126 | +async def main(): |
| 127 | + result = await discover_policy("example.com") |
| 128 | + if result.policy: |
| 129 | + print(f"Found policy via {result.method}") |
| 130 | + print(f"Version: {result.policy.version}") |
| 131 | + else: |
| 132 | + print(f"No policy found: {result.error}") |
| 133 | + |
| 134 | +asyncio.run(main()) |
| 135 | +``` |
| 136 | + |
| 137 | +## API Reference |
| 138 | + |
| 139 | +### Core Modules |
| 140 | + |
| 141 | +| Module | Description | |
| 142 | +| ---------------- | -------------------------------------------------------- | |
| 143 | +| `apop.parser` | Parse & validate `agent-policy.json` against JSON Schema | |
| 144 | +| `apop.enforcer` | Evaluate policy against request context | |
| 145 | +| `apop.matcher` | Glob-style path matching (`/*`, `/**`) | |
| 146 | +| `apop.headers` | Parse agent request headers, build response headers | |
| 147 | +| `apop.discovery` | 4-method discovery chain (well-known, header, meta, DNS) | |
| 148 | +| `apop.types` | Dataclass types for all APoP entities | |
| 149 | + |
| 150 | +### Middleware Adapters |
| 151 | + |
| 152 | +| Module | Framework | |
| 153 | +| ------------------------- | ------------------- | |
| 154 | +| `apop.middleware.fastapi` | FastAPI / Starlette | |
| 155 | +| `apop.middleware.flask` | Flask | |
| 156 | +| `apop.middleware.django` | Django | |
| 157 | + |
| 158 | +### Key Functions |
| 159 | + |
| 160 | +```python |
| 161 | +# Parser |
| 162 | +parse_policy(json_str: str) -> ParseResult |
| 163 | +validate_policy(data: Any) -> ParseResult |
| 164 | +parse_policy_file(path: str) -> ParseResult |
| 165 | +get_schema() -> dict |
| 166 | + |
| 167 | +# Enforcer |
| 168 | +enforce(policy: AgentPolicy, ctx: RequestContext) -> EnforcementResult |
| 169 | + |
| 170 | +# Matcher |
| 171 | +path_matches(url_path: str, pattern: str) -> bool |
| 172 | +match_path_policy(policy: AgentPolicy, url_path: str) -> PathPolicy | None |
| 173 | +merge_policy(default: PolicyRule, path_rule: PathPolicy | None) -> MergedPolicy |
| 174 | + |
| 175 | +# Headers |
| 176 | +parse_request_headers(headers: dict) -> AgentRequestHeaders |
| 177 | +is_agent(headers: AgentRequestHeaders) -> bool |
| 178 | +parse_intents(header: str | None) -> list[str] |
| 179 | +build_allowed_headers(**kwargs) -> dict[str, str] |
| 180 | +build_denied_headers(**kwargs) -> dict[str, str] |
| 181 | +build_verification_headers(**kwargs) -> dict[str, str] |
| 182 | +build_rate_limited_headers(**kwargs) -> dict[str, str] |
| 183 | + |
| 184 | +# Discovery (async) |
| 185 | +discover_policy(domain: str, options?: DiscoveryOptions) -> DiscoveryResult |
| 186 | +``` |
| 187 | + |
| 188 | +### Types |
| 189 | + |
| 190 | +| Type | Description | |
| 191 | +| --------------------- | -------------------------------------------------------- | |
| 192 | +| `AgentPolicy` | Top-level policy manifest | |
| 193 | +| `PolicyRule` | Default or path-specific rules | |
| 194 | +| `PathPolicy` | Path-specific policy with allow/deny lists | |
| 195 | +| `RateLimit` | Rate limiting config (requests + window) | |
| 196 | +| `Verification` | Identity verification config | |
| 197 | +| `EnforcementResult` | Result of `enforce()` — status, HTTP code, headers, body | |
| 198 | +| `RequestContext` | Incoming request info for enforcement | |
| 199 | +| `AgentRequestHeaders` | Parsed agent headers | |
| 200 | +| `DiscoveryResult` | Result of `discover_policy()` | |
| 201 | + |
| 202 | +### HTTP Status Codes |
| 203 | + |
| 204 | +| Code | Constant | Meaning | |
| 205 | +| ---- | -------------------------------------------- | ---------------------------- | |
| 206 | +| 430 | `APOP_STATUS_CODES["ACTION_NOT_ALLOWED"]` | Action not allowed by policy | |
| 207 | +| 438 | `APOP_STATUS_CODES["RATE_LIMITED"]` | Rate limit exceeded | |
| 208 | +| 439 | `APOP_STATUS_CODES["VERIFICATION_REQUIRED"]` | Agent must verify identity | |
| 209 | + |
| 210 | +### Action Types |
| 211 | + |
| 212 | +```python |
| 213 | +from apop.types import ACTION_TYPES |
| 214 | +# ('read', 'index', 'extract', 'summarize', 'render', |
| 215 | +# 'api_call', 'form_submit', 'automated_purchase', 'tool_invoke', 'all') |
| 216 | +``` |
| 217 | + |
| 218 | +## Known Limitations |
| 219 | + |
| 220 | +> These match the Node.js SDK and are documented for transparency: |
| 221 | +
|
| 222 | +- **Rate limiting is advisory-only**: Headers are set from config, but actual request counting is not implemented. Use a dedicated rate limiter (e.g., `slowapi`, Redis) for production enforcement. |
| 223 | +- **Signature verification is presence-check only**: `require_verification=True` checks that an `Agent-Signature` or `Agent-VC` header exists, but does not perform cryptographic validation. |
| 224 | + |
| 225 | +## Development |
| 226 | + |
| 227 | +```bash |
| 228 | +# Clone the repo |
| 229 | +git clone https://github.com/ArunSuperdom/agent-policy-protocol.git |
| 230 | +cd agent-policy-protocol/sdk/python |
| 231 | + |
| 232 | +# Install dev dependencies |
| 233 | +pip install -e ".[dev]" |
| 234 | + |
| 235 | +# Run tests |
| 236 | +pytest |
| 237 | + |
| 238 | +# Type check |
| 239 | +mypy src/apop |
| 240 | + |
| 241 | +# Lint |
| 242 | +ruff check src/ tests/ |
| 243 | +``` |
| 244 | + |
| 245 | +## License |
| 246 | + |
| 247 | +Apache 2.0 — see [LICENSE](../../LICENSE). |
| 248 | + |
| 249 | +## Links |
| 250 | + |
| 251 | +- [APoP Specification](../../spec/README.md) |
| 252 | +- [JSON Schema](../../spec/schema/agent-policy.schema.json) |
| 253 | +- [Node.js SDK](../node/README.md) |
| 254 | +- [Example Policies](../../examples/) |
| 255 | +- [GitHub Repository](https://github.com/ArunSuperdom/agent-policy-protocol) |
0 commit comments