agentvisa-fastapi 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentvisa_fastapi/__init__.py +42 -0
- agentvisa_fastapi/client.py +44 -0
- agentvisa_fastapi/config.py +30 -0
- agentvisa_fastapi/dependencies.py +92 -0
- agentvisa_fastapi/middleware.py +47 -0
- agentvisa_fastapi/models.py +71 -0
- agentvisa_fastapi-0.1.0.dist-info/METADATA +184 -0
- agentvisa_fastapi-0.1.0.dist-info/RECORD +11 -0
- agentvisa_fastapi-0.1.0.dist-info/WHEEL +5 -0
- agentvisa_fastapi-0.1.0.dist-info/licenses/LICENSE +21 -0
- agentvisa_fastapi-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agentvisa-fastapi — AgentVisa verification middleware for FastAPI.
|
|
3
|
+
|
|
4
|
+
Quick start:
|
|
5
|
+
|
|
6
|
+
from agentvisa_fastapi import AgentVisaConfig, AgentVisaMiddleware, require_agentvisa
|
|
7
|
+
|
|
8
|
+
config = AgentVisaConfig(
|
|
9
|
+
api_key="your_widget_api_key", # server-side only
|
|
10
|
+
widget_id="your_widget_id", # from your AgentVisa dashboard
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Option A: middleware (enriches all requests, doesn't block)
|
|
14
|
+
app.add_middleware(AgentVisaMiddleware, config=config)
|
|
15
|
+
|
|
16
|
+
# Option B: per-route dependency (blocks unverified agents with 401)
|
|
17
|
+
@app.get("/protected")
|
|
18
|
+
async def handler(av=Depends(require_agentvisa(config))):
|
|
19
|
+
return {"verified": True, "plan": av.plan}
|
|
20
|
+
|
|
21
|
+
# Option C: both (recommended — middleware caches result, dependency enforces)
|
|
22
|
+
app.add_middleware(AgentVisaMiddleware, config=config)
|
|
23
|
+
|
|
24
|
+
@app.get("/protected")
|
|
25
|
+
async def handler(av=Depends(require_agentvisa(config))):
|
|
26
|
+
return {"plan": av.plan, "human_name": av.human_name}
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from .config import AgentVisaConfig
|
|
30
|
+
from .middleware import AgentVisaMiddleware
|
|
31
|
+
from .dependencies import get_agentvisa, require_agentvisa
|
|
32
|
+
from .models import AgentVisaResult
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AgentVisaConfig",
|
|
36
|
+
"AgentVisaMiddleware",
|
|
37
|
+
"get_agentvisa",
|
|
38
|
+
"require_agentvisa",
|
|
39
|
+
"AgentVisaResult",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async HTTP client for POST /v1/verify.
|
|
3
|
+
Uses httpx with a shared client for connection pooling.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import httpx
|
|
7
|
+
from .config import AgentVisaConfig
|
|
8
|
+
from .models import AgentVisaResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def verify_token(
|
|
12
|
+
token: str,
|
|
13
|
+
config: AgentVisaConfig,
|
|
14
|
+
human_assertion: str | None = None,
|
|
15
|
+
) -> AgentVisaResult:
|
|
16
|
+
"""
|
|
17
|
+
Call POST /v1/verify and return a parsed AgentVisaResult.
|
|
18
|
+
Never raises — returns AgentVisaResult.error() on any network/API failure.
|
|
19
|
+
"""
|
|
20
|
+
url = f"{config.api_base}/v1/verify"
|
|
21
|
+
params: dict = {"token": token, "widget_id": config.widget_id}
|
|
22
|
+
if human_assertion is not None:
|
|
23
|
+
params["human_assertion"] = human_assertion
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
async with httpx.AsyncClient(timeout=config.timeout) as client:
|
|
27
|
+
response = await client.post(
|
|
28
|
+
url,
|
|
29
|
+
params=params,
|
|
30
|
+
headers={"X-Widget-Api-Key": config.api_key},
|
|
31
|
+
)
|
|
32
|
+
if response.status_code == 401:
|
|
33
|
+
return AgentVisaResult.error("invalid_api_key")
|
|
34
|
+
if response.status_code == 404:
|
|
35
|
+
return AgentVisaResult.error("invalid_widget")
|
|
36
|
+
if not response.is_success:
|
|
37
|
+
return AgentVisaResult.error(f"api_error_{response.status_code}")
|
|
38
|
+
|
|
39
|
+
return AgentVisaResult.from_api(response.json())
|
|
40
|
+
|
|
41
|
+
except httpx.TimeoutException:
|
|
42
|
+
return AgentVisaResult.error("verification_timeout")
|
|
43
|
+
except Exception:
|
|
44
|
+
return AgentVisaResult.error("verification_error")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentVisa configuration — holds the Widget Holder's credentials.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AgentVisaConfig:
|
|
10
|
+
"""
|
|
11
|
+
Configuration for the AgentVisa middleware and dependencies.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
api_key: Your Widget Holder API key (X-Widget-Api-Key).
|
|
15
|
+
Keep this server-side only — never expose in client code.
|
|
16
|
+
widget_id: Your widget ID (public, from your AgentVisa dashboard).
|
|
17
|
+
api_base: Override the AgentVisa API base URL (default: https://api.agentvisa.ai).
|
|
18
|
+
timeout: HTTP timeout in seconds for /v1/verify calls (default: 5).
|
|
19
|
+
"""
|
|
20
|
+
api_key: str
|
|
21
|
+
widget_id: str
|
|
22
|
+
api_base: str = "https://api.agentvisa.ai"
|
|
23
|
+
timeout: float = 5.0
|
|
24
|
+
|
|
25
|
+
def __post_init__(self):
|
|
26
|
+
if not self.api_key:
|
|
27
|
+
raise ValueError("AgentVisaConfig: api_key is required")
|
|
28
|
+
if not self.widget_id:
|
|
29
|
+
raise ValueError("AgentVisaConfig: widget_id is required")
|
|
30
|
+
self.api_base = self.api_base.rstrip("/")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI dependencies for AgentVisa verification.
|
|
3
|
+
|
|
4
|
+
Two options:
|
|
5
|
+
|
|
6
|
+
1. get_agentvisa(config) — returns the result, never blocks.
|
|
7
|
+
Use when you want to check verification status yourself.
|
|
8
|
+
|
|
9
|
+
2. require_agentvisa(config) — returns the result OR raises HTTP 401.
|
|
10
|
+
Use when the route must be verified to proceed.
|
|
11
|
+
|
|
12
|
+
Both work with or without AgentVisaMiddleware installed.
|
|
13
|
+
If the middleware is present, the cached result is reused (no second API call).
|
|
14
|
+
If not, the dependency calls /v1/verify itself.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
from fastapi import Depends, HTTPException, Request
|
|
18
|
+
from .config import AgentVisaConfig
|
|
19
|
+
from .client import verify_token
|
|
20
|
+
from .models import AgentVisaResult
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_agentvisa(config: AgentVisaConfig):
|
|
24
|
+
"""
|
|
25
|
+
FastAPI dependency — returns AgentVisaResult, never raises.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
@app.get("/my-route")
|
|
29
|
+
async def handler(av: AgentVisaResult = Depends(get_agentvisa(config))):
|
|
30
|
+
if av.valid:
|
|
31
|
+
...
|
|
32
|
+
"""
|
|
33
|
+
async def _dependency(request: Request) -> AgentVisaResult:
|
|
34
|
+
# If middleware already ran, reuse its result
|
|
35
|
+
existing = getattr(request.state, "agentvisa", None)
|
|
36
|
+
if existing is not None:
|
|
37
|
+
return existing
|
|
38
|
+
|
|
39
|
+
token = request.headers.get("X-AgentVisa-Token")
|
|
40
|
+
if not token:
|
|
41
|
+
return AgentVisaResult.not_present()
|
|
42
|
+
|
|
43
|
+
return await verify_token(token, config)
|
|
44
|
+
|
|
45
|
+
return _dependency
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def require_agentvisa(config: AgentVisaConfig):
|
|
49
|
+
"""
|
|
50
|
+
FastAPI dependency — returns AgentVisaResult or raises HTTP 401.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
@app.get("/protected")
|
|
54
|
+
async def handler(av: AgentVisaResult = Depends(require_agentvisa(config))):
|
|
55
|
+
# Only reaches here if av.valid is True
|
|
56
|
+
return {"human_name": av.human_name}
|
|
57
|
+
"""
|
|
58
|
+
async def _dependency(
|
|
59
|
+
result: AgentVisaResult = Depends(get_agentvisa(config)),
|
|
60
|
+
) -> AgentVisaResult:
|
|
61
|
+
if not result.valid:
|
|
62
|
+
reason = result.reason
|
|
63
|
+
|
|
64
|
+
# Tell the agent what it needs to do next
|
|
65
|
+
if reason == "token_not_present":
|
|
66
|
+
raise HTTPException(
|
|
67
|
+
status_code=401,
|
|
68
|
+
detail="AgentVisa token required",
|
|
69
|
+
headers={"X-AgentVisa-Required": config.widget_id},
|
|
70
|
+
)
|
|
71
|
+
if reason == "reverification_required":
|
|
72
|
+
raise HTTPException(
|
|
73
|
+
status_code=401,
|
|
74
|
+
detail="AgentVisa reverification required — check email",
|
|
75
|
+
headers={"X-AgentVisa-Required": config.widget_id},
|
|
76
|
+
)
|
|
77
|
+
if reason in ("expired", "revoked"):
|
|
78
|
+
raise HTTPException(
|
|
79
|
+
status_code=401,
|
|
80
|
+
detail=f"AgentVisa token {reason}",
|
|
81
|
+
headers={"X-AgentVisa-Required": config.widget_id},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
raise HTTPException(
|
|
85
|
+
status_code=401,
|
|
86
|
+
detail=f"AgentVisa verification failed: {reason}",
|
|
87
|
+
headers={"X-AgentVisa-Required": config.widget_id},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
return _dependency
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentVisa Starlette middleware.
|
|
3
|
+
|
|
4
|
+
Reads X-AgentVisa-Token from the request, calls /v1/verify,
|
|
5
|
+
and injects the result into request.state.agentvisa.
|
|
6
|
+
|
|
7
|
+
Does NOT block requests — use the require_agentvisa dependency for that.
|
|
8
|
+
This lets you enrich all requests and make per-route decisions.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
12
|
+
from starlette.requests import Request
|
|
13
|
+
from starlette.responses import Response
|
|
14
|
+
from .config import AgentVisaConfig
|
|
15
|
+
from .client import verify_token
|
|
16
|
+
from .models import AgentVisaResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentVisaMiddleware(BaseHTTPMiddleware):
|
|
20
|
+
"""
|
|
21
|
+
Drop-in Starlette/FastAPI middleware.
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
app.add_middleware(
|
|
25
|
+
AgentVisaMiddleware,
|
|
26
|
+
config=AgentVisaConfig(api_key="...", widget_id="..."),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
After adding, every request will have request.state.agentvisa set
|
|
30
|
+
to an AgentVisaResult. No requests are blocked — use require_agentvisa
|
|
31
|
+
as a dependency on individual routes to enforce verification.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, app, config: AgentVisaConfig):
|
|
35
|
+
super().__init__(app)
|
|
36
|
+
self.config = config
|
|
37
|
+
|
|
38
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
39
|
+
token = request.headers.get("X-AgentVisa-Token")
|
|
40
|
+
|
|
41
|
+
if token:
|
|
42
|
+
result = await verify_token(token, self.config)
|
|
43
|
+
else:
|
|
44
|
+
result = AgentVisaResult.not_present()
|
|
45
|
+
|
|
46
|
+
request.state.agentvisa = result
|
|
47
|
+
return await call_next(request)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentVisa result models — mirroring the /v1/verify response schema.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Literal, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class AgentVisaResult:
|
|
12
|
+
"""
|
|
13
|
+
The parsed response from POST /v1/verify.
|
|
14
|
+
Injected into request.state.agentvisa by the middleware.
|
|
15
|
+
"""
|
|
16
|
+
# Always present
|
|
17
|
+
valid: bool
|
|
18
|
+
reason: str
|
|
19
|
+
plan: str
|
|
20
|
+
widget_id: str
|
|
21
|
+
|
|
22
|
+
# Present on valid tokens
|
|
23
|
+
verified_at: Optional[datetime] = None
|
|
24
|
+
expires_at: Optional[datetime] = None
|
|
25
|
+
|
|
26
|
+
# Pro plan metadata
|
|
27
|
+
human_name: Optional[str] = None
|
|
28
|
+
five_factor: Optional[Literal["y", "n"]] = None
|
|
29
|
+
age_over_18: Optional[Literal["y", "n", "null"]] = None
|
|
30
|
+
age_over_21: Optional[Literal["y", "n", "null"]] = None
|
|
31
|
+
multiple_agents_authorized: Optional[Literal["y", "n"]] = None
|
|
32
|
+
verifications_today: Optional[int] = None
|
|
33
|
+
|
|
34
|
+
# Set when no token was present in the request at all
|
|
35
|
+
skipped: bool = False
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_api(cls, data: dict) -> "AgentVisaResult":
|
|
39
|
+
"""Build from the raw /v1/verify JSON response."""
|
|
40
|
+
def parse_dt(val: Optional[str]) -> Optional[datetime]:
|
|
41
|
+
if not val:
|
|
42
|
+
return None
|
|
43
|
+
try:
|
|
44
|
+
return datetime.fromisoformat(val.replace("Z", "+00:00"))
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
return cls(
|
|
49
|
+
valid=data.get("valid", False),
|
|
50
|
+
reason=data.get("reason", "unknown"),
|
|
51
|
+
plan=data.get("plan", "basic"),
|
|
52
|
+
widget_id=data.get("widget_id", ""),
|
|
53
|
+
verified_at=parse_dt(data.get("verified_at")),
|
|
54
|
+
expires_at=parse_dt(data.get("expires_at")),
|
|
55
|
+
human_name=data.get("human_name"),
|
|
56
|
+
five_factor=data.get("five_factor"),
|
|
57
|
+
age_over_18=data.get("age_over_18"),
|
|
58
|
+
age_over_21=data.get("age_over_21"),
|
|
59
|
+
multiple_agents_authorized=data.get("multiple_agents_authorized"),
|
|
60
|
+
verifications_today=data.get("verifications_today"),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def not_present(cls) -> "AgentVisaResult":
|
|
65
|
+
"""Returned when no X-AgentVisa-Token header was in the request."""
|
|
66
|
+
return cls(valid=False, reason="token_not_present", plan="basic", widget_id="", skipped=True)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def error(cls, reason: str = "verification_error") -> "AgentVisaResult":
|
|
70
|
+
"""Returned when the /v1/verify call itself failed."""
|
|
71
|
+
return cls(valid=False, reason=reason, plan="basic", widget_id="")
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentvisa-fastapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI middleware for AgentVisa AI agent verification
|
|
5
|
+
Author-email: AgentVisa <info@agentvisa.ai>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 AgentVisa
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://agentvisa.ai
|
|
29
|
+
Project-URL: Repository, https://github.com/AgentVisa-ai/agentvisa-fastapi
|
|
30
|
+
Project-URL: Documentation, https://agentvisa.ai/docs
|
|
31
|
+
Project-URL: Bug Tracker, https://github.com/AgentVisa-ai/agentvisa-fastapi/issues
|
|
32
|
+
Keywords: agentvisa,fastapi,middleware,ai-agent,verification
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Framework :: FastAPI
|
|
42
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
43
|
+
Classifier: Topic :: Security
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: fastapi>=0.100.0
|
|
48
|
+
Requires-Dist: httpx>=0.24.0
|
|
49
|
+
Requires-Dist: starlette>=0.27.0
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
52
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
53
|
+
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# agentvisa-fastapi
|
|
57
|
+
|
|
58
|
+
FastAPI middleware for [AgentVisa](https://agentvisa.ai) — verify that incoming AI agents were authorized by a real, 5-factor biometric-verified human before granting access.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install agentvisa-fastapi
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from fastapi import FastAPI, Depends
|
|
70
|
+
from agentvisa_fastapi import AgentVisaConfig, AgentVisaMiddleware, require_agentvisa
|
|
71
|
+
|
|
72
|
+
app = FastAPI()
|
|
73
|
+
|
|
74
|
+
config = AgentVisaConfig(
|
|
75
|
+
api_key=os.environ["AGENTVISA_API_KEY"], # your Widget Holder API key — server-side only
|
|
76
|
+
widget_id=os.environ["AGENTVISA_WIDGET_ID"], # from your AgentVisa dashboard
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Add middleware — enriches every request with request.state.agentvisa
|
|
80
|
+
app.add_middleware(AgentVisaMiddleware, config=config)
|
|
81
|
+
|
|
82
|
+
# Protected route — agents without a valid AgentVisa token get 401
|
|
83
|
+
@app.get("/api/orders")
|
|
84
|
+
async def get_orders(av=Depends(require_agentvisa(config))):
|
|
85
|
+
return {"orders": [...], "verified_human": av.human_name}
|
|
86
|
+
|
|
87
|
+
# Unprotected route — check manually if you want soft enforcement
|
|
88
|
+
@app.get("/api/public")
|
|
89
|
+
async def public(request: Request):
|
|
90
|
+
av = request.state.agentvisa
|
|
91
|
+
if av.valid:
|
|
92
|
+
return {"message": "Hello, verified agent!", "plan": av.plan}
|
|
93
|
+
return {"message": "Hello — consider getting an AgentVisa for full access."}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## How it works
|
|
97
|
+
|
|
98
|
+
When an AI agent hits your site, AgentVisa requires a two-step handshake:
|
|
99
|
+
|
|
100
|
+
1. **Your server returns 401** with `X-AgentVisa-Required: your_widget_id` — this middleware does that automatically via `require_agentvisa`.
|
|
101
|
+
2. **The agent calls AgentVisa** to get a short-lived TemporaryToken scoped to your site.
|
|
102
|
+
3. **The agent retries** with `X-AgentVisa-Token: tmp_...` in the header.
|
|
103
|
+
4. **This middleware calls `/v1/verify`** with your API key and returns the result.
|
|
104
|
+
|
|
105
|
+
The human behind the agent completed 5-factor biometric verification (email, phone, cross-verification, Face ID / Touch ID, and a human assertion) when they got their AgentVisa.
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
AgentVisaConfig(
|
|
111
|
+
api_key="wk_...", # required — your Widget Holder API key
|
|
112
|
+
widget_id="wgt_...", # required — your widget ID
|
|
113
|
+
api_base="https://api.agentvisa.ai", # optional — override for self-hosted
|
|
114
|
+
timeout=5.0, # optional — /v1/verify timeout in seconds
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Security note:** Never expose `api_key` in client-side code, environment variables that get bundled, or logs. Treat it like a database password.
|
|
119
|
+
|
|
120
|
+
## AgentVisaResult fields
|
|
121
|
+
|
|
122
|
+
| Field | Type | Description |
|
|
123
|
+
|-------|------|-------------|
|
|
124
|
+
| `valid` | `bool` | Whether the agent is verified |
|
|
125
|
+
| `reason` | `str` | `"ok"`, `"invalid"`, `"expired"`, `"revoked"`, `"reverification_required"`, `"token_not_present"` |
|
|
126
|
+
| `plan` | `str` | `"basic"` or `"pro"` |
|
|
127
|
+
| `widget_id` | `str` | Your widget ID |
|
|
128
|
+
| `verified_at` | `datetime \| None` | When the token was issued |
|
|
129
|
+
| `expires_at` | `datetime \| None` | When the token expires |
|
|
130
|
+
| `human_name` | `str \| None` | Pro plan only |
|
|
131
|
+
| `five_factor` | `"y" \| "n" \| None` | Pro plan only |
|
|
132
|
+
| `age_over_18` | `"y" \| "n" \| "null" \| None` | Pro plan only |
|
|
133
|
+
| `age_over_21` | `"y" \| "n" \| "null" \| None` | Pro plan only |
|
|
134
|
+
| `multiple_agents_authorized` | `"y" \| "n" \| None` | Pro plan only |
|
|
135
|
+
| `verifications_today` | `int \| None` | Pro plan only |
|
|
136
|
+
| `skipped` | `bool` | True when no token was in the request |
|
|
137
|
+
|
|
138
|
+
## Using without the middleware
|
|
139
|
+
|
|
140
|
+
If you don't want to add middleware, use the dependency standalone — it will call `/v1/verify` directly:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from agentvisa_fastapi import AgentVisaConfig, require_agentvisa
|
|
144
|
+
|
|
145
|
+
config = AgentVisaConfig(api_key="...", widget_id="...")
|
|
146
|
+
|
|
147
|
+
@app.post("/checkout")
|
|
148
|
+
async def checkout(av=Depends(require_agentvisa(config))):
|
|
149
|
+
# verified agents only
|
|
150
|
+
...
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Using get_agentvisa for soft enforcement
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from agentvisa_fastapi import AgentVisaConfig, get_agentvisa, AgentVisaResult
|
|
157
|
+
|
|
158
|
+
config = AgentVisaConfig(api_key="...", widget_id="...")
|
|
159
|
+
|
|
160
|
+
@app.get("/search")
|
|
161
|
+
async def search(av: AgentVisaResult = Depends(get_agentvisa(config))):
|
|
162
|
+
results = perform_search()
|
|
163
|
+
if not av.valid:
|
|
164
|
+
# Return limited results for unverified agents
|
|
165
|
+
return {"results": results[:3], "note": "Get an AgentVisa for full results"}
|
|
166
|
+
return {"results": results}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Reverification
|
|
170
|
+
|
|
171
|
+
When an agent's daily verification limit is hit, `/v1/verify` returns `reason: "reverification_required"`. This middleware propagates that reason in the 401 response so your users know to check their email.
|
|
172
|
+
|
|
173
|
+
The `require_agentvisa` dependency returns:
|
|
174
|
+
```
|
|
175
|
+
HTTP 401
|
|
176
|
+
X-AgentVisa-Required: your_widget_id
|
|
177
|
+
{"detail": "AgentVisa reverification required — check email"}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The AgentVisa MCP server handles this automatically — it calls `POST /v1/holder/reverify` to trigger the re-verification email without the human needing to intervene.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
agentvisa_fastapi/__init__.py,sha256=-IYxB2Q4cjIZeTCLAwbjDlcyoU8uRhdJyx2ttLpNE7c,1323
|
|
2
|
+
agentvisa_fastapi/client.py,sha256=HBv5nmXajTCTn7jj9MKY4ywUsppZy8EHhYJud8C51OI,1540
|
|
3
|
+
agentvisa_fastapi/config.py,sha256=N9g-fD-WGPZzc1V9ljnuSdwFwUTlIfrbQp-AcTCPnGo,1062
|
|
4
|
+
agentvisa_fastapi/dependencies.py,sha256=7mT265uhfloIIREA2uXtp9JHLH2OlKIMAEWwFYZ4htI,3133
|
|
5
|
+
agentvisa_fastapi/middleware.py,sha256=4YcDovZ4g-Li-KR30mOoaGvQxWtI3WQcPypZeeKAuxA,1512
|
|
6
|
+
agentvisa_fastapi/models.py,sha256=8DyFugHdQEIOrX2qnx0XTieAoHN3SllucqG7VOs47mA,2560
|
|
7
|
+
agentvisa_fastapi-0.1.0.dist-info/licenses/LICENSE,sha256=2ZWgZFz10QZvvgGbDGi58YRqYwa7fClyV4ryle_u3fs,1066
|
|
8
|
+
agentvisa_fastapi-0.1.0.dist-info/METADATA,sha256=YB4bSB4HKwllNxWDIdgkJfWSuEgPoJimfUu5KU0vldY,7476
|
|
9
|
+
agentvisa_fastapi-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
agentvisa_fastapi-0.1.0.dist-info/top_level.txt,sha256=QblYKfRccBIqklfxQ7rqeolfg3XL5U3r8-th0-QCj0c,18
|
|
11
|
+
agentvisa_fastapi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentVisa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agentvisa_fastapi
|