nxtID Python SDK — AIT-gated AI agent tool permissions
Project description
nxtID Python SDK
Python SDK for integrating MetaKeep wallet, AuthID biometric IDV, and nxtlinq AIT on-chain permissions into AI agents.
Installation
pip install nxtid-sdk
Only httpx is a hard dependency. If your agent is built on FastAPI and you
want the .mount(app) HTTP-route adapters (AITRouter, EntraAuth,
SessionStore), install the fastapi extra:
pip install "nxtid-sdk[fastapi]"
Not on FastAPI? Skip the extra — every operation is a plain Python method (see Framework-agnostic core below), callable from Flask, Django, a Slack bot, a background worker, a plain script, whatever your agent is built on.
Framework-agnostic core
AITRouter, EntraAuth, and SessionStore are each split into two layers:
- Plain methods —
wallet_connect,verify_start,verify_complete,permissions_request,permissions_admin_granted,permissions_refresh,session_restore,ait_status,guard_tool_call,login,config,create/get/get_or_create/delete— take a session object and plain Python arguments, return a plain dict, and raiseNxtidHTTPError(never a web-framework exception) on failure. No FastAPI import happens anywhere in this layer. .mount(app)— an optional FastAPI adapter built on top of the plain methods: it wires them to FastAPI routes with request-body validation and convertsNxtidHTTPErrortofastapi.HTTPException. FastAPI/pydantic are imported lazily inside.mount()/._build_router()— never at module import time — so using the plain methods works even if FastAPI isn't installed at all.
If your agent uses FastAPI, call .mount(app) and never think about this
again — see Typical Agent Integration Pattern below. If it doesn't,
call the plain methods directly from whatever request handler your
framework gives you:
from nxtid_sdk import NxtidSDK, AITRouter, EntraAuth, NxtidHTTPError
sdk = NxtidSDK.from_env()
ait_router = AITRouter(sdk, get_session=sessions.get, create_session=sessions.create)
entra = EntraAuth.from_env(get_session=get_or_create_session,
on_login=ait_router.auto_grant_by_role)
# e.g. inside a Flask view, a Slack slash-command handler, a plain script...
async def handle_wallet_connect(session_id: str, email: str) -> dict:
session = sessions.get(session_id)
if session is None:
return {"error": "Session not found or expired."}, 404
try:
return await ait_router.wallet_connect(session, email=email)
except NxtidHTTPError as exc:
return {"error": exc.detail}, exc.status_code # map to your framework's error shape
async def handle_entra_login(session_id: str, name: str, email: str, roles: list) -> dict:
session = get_or_create_session(session_id)
return await entra.login(session, name=name, email=email, roles=roles)
# Sessions: sessions.create()/get()/get_or_create()/delete() are plain dict
# operations, no HTTP involved — call them from anywhere.
Quick Start
from nxtid_sdk import NxtidSDK
sdk = NxtidSDK(
api_key="your-api-key",
api_secret="your-api-secret",
env="production", # "production" (default) or "staging"
service_id="your-service-id", # required for log_chat
)
# 1. Register your agent's tools on startup
await sdk.register_tools([
{"name": "get_account_balance", "description": "Read-only finance access"},
{"name": "transfer_funds", "description": "Move money between accounts"},
])
# 2. Connect wallet by email
wallet = await sdk.connect_wallet(email="user@example.com")
# → WalletResult(wallet_address="0x...", email="user@example.com")
# 3. Start AuthID identity verification
verify = await sdk.start_verification(wallet.wallet_address)
# → VerifyStartResult(flow="login"|"onboarding"|"choose_document", widget_url="...", ...)
# 4. After user completes the widget
result = await sdk.complete_verification(
wallet.wallet_address,
operation_id=verify.operation_id, # for onboarding flow
transaction_id=verify.transaction_id, # for login flow
)
# → VerifyCompleteResult(is_verified=True)
# 5. Check admin-granted permissions
perms = await sdk.get_permissions(wallet.wallet_address)
if perms.has_permission("transfer_funds"):
...
Environments
| env | AIT Service | Dashboard |
|---|---|---|
production |
https://ait-service.nxtlinq.ai |
https://dashboard.nxtlinq.ai |
staging |
https://staging-ait-service.nxtlinq.ai |
https://dashboard-staging.nxtlinq.ai |
API Reference
NxtidSDK
NxtidSDK(
api_key: str,
api_secret: str,
env: str = "production",
service_id: Optional[str] = None,
)
Credentials (api_key, api_secret, service_id) are obtained from nxtID Dashboard → Services → Register Third-party Agent.
NxtidSDK.from_env() → NxtidSDK | None
Builds an instance from env vars instead of wiring them by hand — returns None if not configured. Pass it straight to AITRouter either way; no if needed — AITRouter is None-safe and mounts unconditionally (see below):
sdk = NxtidSDK.from_env()
AITRouter(sdk, get_session=sessions.get, create_session=sessions.create).mount(app)
Reads NXTLINQ_SERVICE_API_KEY, NXTLINQ_SERVICE_API_SECRET, NXTLINQ_SERVICE_ID (optional), NXTLINQ_ENV ("production" default or "staging").
connect_wallet(email) → WalletResult
Gets or creates a MetaKeep non-custodial wallet for the given email address.
wallet = await sdk.connect_wallet(email="user@example.com")
wallet.wallet_address # "0xABCDEF..."
wallet.email # "user@example.com"
start_verification(wallet_address, id_type=None) → VerifyStartResult
Starts an AuthID identity verification session.
flow |
Condition |
|---|---|
"login" |
Returning user (face scan only) |
"onboarding" |
New user with id_type provided |
"choose_document" |
New user without id_type (user selects type) |
id_type is the AuthID document type code (e.g. "40" for passport).
result = await sdk.start_verification(wallet.wallet_address, id_type="40")
result.flow # "onboarding"
result.widget_url # URL to open in browser/iframe
result.operation_id # pass to complete_verification (onboarding)
result.transaction_id # pass to complete_verification (login)
complete_verification(wallet_address, operation_id=None, transaction_id=None) → VerifyCompleteResult
Finalises the verification session after the user completes the widget. Polls AuthID for up to 60 s.
done = await sdk.complete_verification(
wallet.wallet_address,
operation_id=result.operation_id,
)
done.is_verified # True | False
get_permissions(wallet_address) → PermissionsResult
Queries the AIT on-chain record for this wallet to determine which tools the admin has granted.
perms = await sdk.get_permissions(wallet.wallet_address)
perms.allowed # True if any permission grant exists
perms.allowed_tools # ["transfer_funds", "get_account_balance", ...]
perms.denied_tools # tools explicitly blocked
perms.reason # optional explanation from nxtlinq
perms.has_permission("transfer_funds") # True | False
get_admin_granted_tools(wallet_address) → list[str]
Returns the list of tools the admin has pre-approved for this user to self-service request. Empty list means the admin hasn't granted any tools yet.
tools = await sdk.get_admin_granted_tools(wallet.wallet_address)
# ["calculate", "get_company_info", "get_account_balance"]
set_permissions(wallet_address, permissions, granted_by=None, roles=None) → dict
Writes an AIT on-chain with the given tool list for wallet_address. Used for self-service permission requests (user selects a subset of admin-granted tools), and for role-based auto-grant (pass roles for audit purposes).
await sdk.set_permissions(
wallet_address="0x...",
permissions=["calculate", "get_company_info"],
granted_by="user@example.com",
)
get_role_permissions(role) → list[str]
Returns the default tool list an admin has configured for a given Entra role — used to auto-grant AIT permissions right after IDV completes, without the user having to self-service pick anything.
tools = await sdk.get_role_permissions("Finance")
if tools:
await sdk.set_permissions(wallet.wallet_address, tools, roles=["Finance"])
async guard_tool_call(tool_name, session) → None
Enforces identity and permission checks before a tool executes. Call this inside the agent's tool dispatch loop, before executing each gated tool (await it — it's async). Raises ToolCallBlockedError if the call should be blocked; returns None (no-op) if permitted. session needs wallet_address, is_verified, allowed_tools, admin_allowed_tools, permissions_configured — satisfied by NxtidSessionMixin. entra_roles is read too, for the role-based fallback (see below).
This makes a live API call every time — it re-fetches the current admin grant from the AIT service on every invocation rather than trusting a cached session snapshot, so a revoke takes effect on the very next tool call, not just the next page reload or widget reopen. A network failure here fails closed (ToolCallBlockedError), not open.
await sdk.guard_tool_call("transfer_funds", session)
Internally this is resolve_admin_grant + apply_active_subset (below) + a membership check — the exact same two calls AITRouter's session_restore/permissions_refresh make, so real-time enforcement can never drift out of sync with what those endpoints report.
async resolve_admin_grant(session) → bool
Queries the current admin-granted AIT permission ceiling for session.wallet_address and sets session.permissions_configured / session.admin_allowed_tools: personal grant if present, otherwise role-based group default from session.entra_roles (DB read only, no on-chain write), otherwise cleared. Returns True if some grant was found. Does not touch session.allowed_tools — call apply_active_subset after.
apply_active_subset(session, requested_active) → None
Sets session.allowed_tools to whichever of requested_active is still covered by session.admin_allowed_tools — falls back to the full admin grant if nothing (or nothing still valid) was requested. This is how a user's self-service choice (turning a specific admin-granted tool off) survives a refresh instead of being silently re-enabled by the admin ceiling.
async refresh_session_permissions(session) → bool
Convenience wrapper around resolve_admin_grant + apply_active_subset — refreshes session.allowed_tools/session.admin_allowed_tools from the current admin-granted ceiling in one call. No-ops (returns False) if wallet/IDV aren't established yet; never raises — keeps whatever was last cached on the session if the underlying call fails.
Call this unconditionally at the start of every turn, before building anything that reads session.allowed_tools (e.g. a system prompt's "here's what you can't use" hint). That value is otherwise only refreshed as a side effect of guard_tool_call, which only runs if a tool call is actually attempted — if your prompt tells the LLM a tool is blocked based on a stale allowed_tools, the LLM has no reason to try it, so guard_tool_call never runs, and the stale state never refreshes even after an admin grants new permissions:
await sdk.refresh_session_permissions(session)
allowed = [t for t in TOOL_DEFINITIONS if t["name"] in session.allowed_tools]
system_prompt = build_prompt(allowed) # now reflects the current grant, not last turn's
register_tools(tools) → int
Registers the agent's tool catalogue with nxtID so admins can grant permissions per tool. Call once at agent startup.
count = await sdk.register_tools([
{"name": "transfer_funds", "description": "Move money between accounts"},
{"name": "get_account_balance", "description": "Read-only finance access"},
])
# count = 2
log_chat(session, user_message, assistant_message, ...) → None
Sends one conversation turn to nxtID Dashboard for monitoring. Fails silently — logging is non-fatal. Requires service_id at SDK init time. The external user identity is derived from the session as wallet_address or verified_email / wallet_email or verified_email.
await sdk.log_chat(
session,
user_message="What's my balance?",
assistant_message="Your balance is $500.",
model="meta/llama-3.3-70b-instruct",
has_tool_call=True,
tool_name="get_account_balance",
turn_index=0,
)
EntraAuth
Optional module for adding Microsoft Entra SSO login to a FastAPI agent. When enabled, mounts two routes onto the host app that the browser MSAL popup calls.
from nxtid_sdk import EntraAuth
entra = EntraAuth(
client_id=os.getenv("AZURE_AD_CLIENT_ID"),
tenant_id=os.getenv("AZURE_AD_TENANT_ID"),
get_session=get_or_create_session, # callable: session_id → Session object
)
entra.mount(app) # app is a FastAPI instance
Or read the same two env vars in one call: EntraAuth.from_env(get_session=get_or_create_session, on_login=on_entra_login).
If client_id or tenant_id is empty/None, entra.enabled returns False and the routes respond accordingly — no error is raised.
Routes mounted (FastAPI) / plain methods (any framework):
| Method | Path | Plain method | Description |
|---|---|---|---|
GET |
/auth/entra/config |
entra.config() → dict |
Returns { enabled, client_id, tenant_id } for MSAL initialisation |
POST |
/auth/entra |
await entra.login(session, name, email, tenant_id=None, roles=None) → dict |
Writes MSAL token claims onto the session, fires on_login |
POST /auth/entra body:
{
"session_id": "string",
"name": "string",
"email": "string",
"tenant_id": "string (optional)",
"roles": ["string"]
}
The get_session callable receives a session_id string and must return a session object with writable attributes: entra_authenticated, entra_name, entra_email, entra_tenant_id, entra_roles.
AITRouter
Bundles the wallet / AuthID IDV / AIT-permission HTTP endpoints onto a FastAPI app — the wallet/verify/permissions counterpart to EntraAuth.mount(app). Without this, every consuming app has to hand-write these ~9 routes itself.
from nxtid_sdk import NxtidSDK, AITRouter
sdk = NxtidSDK.from_env() # may be None if credentials are missing
ait_router = AITRouter(sdk, get_session=sessions.get, create_session=sessions.create)
ait_router.mount(app)
sdk may be None — construct and mount AITRouter unconditionally either way, same pattern as EntraAuth. When sdk is None, /wallet/connect responds 503 and everything downstream (which all require a connected wallet first) degrades gracefully; check ait_router.enabled where your app needs to know (e.g. to decide whether to show the setup widget).
Registering tools at startup: if your app has no other startup/shutdown needs, skip writing your own lifespan function — ait_router.lifespan(tools) returns a ready-made one. Extra keys (e.g. LLM parameters schemas) are fine; only name/description are read server-side, so your tool-calling TOOL_DEFINITIONS list can usually be passed straight through.
app = FastAPI(lifespan=ait_router.lifespan(TOOL_DEFINITIONS))
Already have a lifespan for other startup work? Call register_tools inside it instead:
@asynccontextmanager
async def lifespan(app):
await ait_router.register_tools(TOOL_DEFINITIONS)
yield
Routes mounted (FastAPI) / plain methods (any framework):
| Method | Path | Plain method | Description |
|---|---|---|---|
POST |
/wallet/connect |
await ait_router.wallet_connect(session, email) |
Connect a MetaKeep wallet by email |
POST |
/verify/start |
await ait_router.verify_start(session, id_type=None) |
Start AuthID IDV, returns a widget URL |
POST |
/verify/complete |
await ait_router.verify_complete(session, operation_id=None, transaction_id=None) |
Complete IDV; auto-loads permissions afterward |
POST |
/permissions/request |
await ait_router.permissions_request(session, allowed_tools) |
Self-service: user selects admin-approved tools |
GET |
/permissions/admin-granted |
await ait_router.permissions_admin_granted(session) |
Tools admin pre-approved for self-service selection |
POST |
/permissions/refresh |
await ait_router.permissions_refresh(session) |
Re-pull admin-granted permissions from the AIT service |
POST |
/session/restore |
await ait_router.session_restore(session, data) |
Restore identity state from browser localStorage (or any client-side cache) |
GET |
/ait/status/{session_id} |
ait_router.ait_status(session) (sync) |
Full identity snapshot for the given session |
GET |
/nxtlinq/widget.js |
ait_router.widget_js() (sync) |
Bundled NxtlinqWidget setup UI source |
GET |
/nxtlinq/session.js |
ait_router.session_js() (sync) |
Bundled NxtlinqSession localStorage helper source |
All the async plain methods raise NxtidHTTPError(status_code, detail) on failure instead of any FastAPI type — catch that if you're calling them outside .mount().
Unlike EntraAuth, AITRouter takes two separate callables rather than one get-or-create function:
get_session: (session_id: str) -> Session | None— a plain lookup (e.g.SessionStore.get). Every route except/session/restorecalls this and responds404if it returnsNone, rather than silently operating on a brand-new, blank session. This matters in practice: a session that completed IDV can otherwise look "expired" as a confusing downstream error (e.g.permissions_refreshfailing with "Complete IDV first") instead of a clear "session not found" — which happens whenever the in-memory session store loses state a browser tab'ssession_idstill points to (a server restart, a process recycle, an expired TTL in a custom store).create_session: () -> Session— makes a brand-new session (e.g.SessionStore.create). Used only by/session/restore, the one route that's expected to run with no session yet.
The session object needs plain, writable attributes (no interface/protocol required):
- Written by
AITRouter:wallet_address,wallet_email,is_verified,verified_email,permissions_configured,allowed_tools,admin_allowed_tools - Read by
AITRouter(set these yourself, e.g. viaEntraAuth'son_login):entra_roles,entra_email— used to auto-grant AIT permissions by role right after IDV completes
Use NxtidSessionMixin (below) instead of hand-declaring these fields.
Role-based auto-grant with EntraAuth:
auto_grant_by_role's signature — (session, roles=None) — matches on_login exactly, so it can be wired in directly with no wrapper function:
entra = EntraAuth(..., get_session=get_or_create_session, on_login=ait_router.auto_grant_by_role)
NxtidSessionMixin
Dataclass mixin providing every field AITRouter and EntraAuth read/write. Inherit from it instead of hand-declaring the ~12 identity fields on your own Session every time you integrate nxtID into a new project.
from dataclasses import dataclass, field
from nxtid_sdk import NxtidSessionMixin
@dataclass
class Session(NxtidSessionMixin):
messages: list = field(default_factory=list) # your own app state
session = Session() # session_id is auto-generated (UUID)
All mixin fields have defaults (session_id included, via a UUID factory), so — per the usual dataclass inheritance rule — any fields you add in your subclass need defaults too.
SessionStore
Generic in-memory session store keyed by session.session_id — the create/get/delete dict every nxtID integration otherwise hand-rolls, factored out once.
from nxtid_sdk import SessionStore
sessions = SessionStore(Session) # Session is your NxtidSessionMixin subclass
session = sessions.create()
session = sessions.get(session_id) # None if unknown
session = sessions.get_or_create(session_id) # None/unknown id -> new session
sessions.delete(session_id)
Session lifecycle endpoints: most frontends need a way to create a fresh session and to reset one (e.g. on sign-out). sessions.mount(app) mounts both so you don't have to hand-write them:
sessions.mount(app) # POST /session/new -> {"session_id": ...}, POST /session/reset {"session_id": ...} -> {"ok": true}
NxtidHTTPError
Framework-agnostic error raised by AITRouter/EntraAuth's plain methods (wallet_connect, verify_start, login, ...) — never a fastapi.HTTPException or any other web-framework type. .mount(app) catches this internally and converts it for you; call the plain methods directly and you need to catch it yourself.
from nxtid_sdk import NxtidHTTPError
try:
result = await ait_router.wallet_connect(session, email=email)
except NxtidHTTPError as exc:
exc.status_code # e.g. 400, 502, 503
exc.detail # user-facing explanation
ToolCallBlockedError
Exception raised to block a tool execution before it reaches the LLM tool handler.
from nxtid_sdk import ToolCallBlockedError
try:
await sdk.guard_tool_call(tool_name, session)
except ToolCallBlockedError as exc:
exc.reason # "verification_required" | "awaiting_admin_grant" | "permission_denied"
exc.message # user-facing explanation
reason |
Meaning | Fixable via the setup widget? |
|---|---|---|
verification_required |
Wallet not connected, IDV not completed, or the admin-grant check itself failed transiently | Yes — NxtlinqWidget.handleChatEvent auto-opens the setup modal for this |
awaiting_admin_grant |
Wallet + IDV are done, but the admin hasn't granted this account any permission yet (no personal or role grant at all) | No — nothing for the end user to click through; only an admin can grant something, so the widget does not auto-open |
permission_denied |
The account has an admin grant, just not for this specific tool | No — the account is already fully set up, so the widget does not auto-open; show exc.message in chat instead |
Factory method:
raise ToolCallBlockedError.permission_denied(
tool_name="transfer_funds",
allowed_tools=["calculate", "get_company_info"],
)
# message: "You don't have permission to use 'transfer_funds'.
# Your current permissions: [calculate, get_company_info].
# Contact your administrator."
Return Types
| Class | Fields |
|---|---|
WalletResult |
wallet_address: str, email: str |
VerifyStartResult |
flow: str, widget_url: str|None, operation_id: str|None, transaction_id: str|None |
VerifyCompleteResult |
is_verified: bool |
PermissionsResult |
allowed: bool, allowed_tools: list[str], denied_tools: list[str], reason: str|None, has_permission(tool_name) → bool |
Frontend Widget Integration
AITRouter.mount(app) serves two browser-ready JS files — no build step,
no npm package, just <script> tags:
NxtlinqWidget(/nxtlinq/widget.js) — the setup UI: walks the user through Entra SSO → wallet connect → AuthID IDV → AIT permissions, callingAITRouter's HTTP routes under the hood.NxtlinqSession(/nxtlinq/session.js) — localStorage persistence + server-side session restore. Usable standalone even without the widget UI.
NxtlinqWidget
<div id="nxtlinq-widget"></div>
<script src="/nxtlinq/session.js"></script>
<script src="/nxtlinq/widget.js"></script>
<script>
let sessionId = null;
NxtlinqWidget.init({
container: '#nxtlinq-widget',
apiBase: '', // '' = same origin as the page
mode: 'inline', // 'inline' (default) or 'modal' (gear-icon trigger + popup)
steps: ['entra', 'wallet', 'verify', 'permissions'],
entra: { clientId: '...', tenantId: '...', redirectUri: window.location.origin },
tools: TOOL_DEFINITIONS, // [{ name, label, desc }, ...] — same tools you passed to register_tools
onStepComplete: (step, data) => {
if (data.sessionId) sessionId = data.sessionId;
// data is camelCase (walletAddress, allowedTools, ...) — map only the
// fields you need into NxtlinqSession's snake_case shape.
if (step === 'entra' && data.roles) NxtlinqSession.update(sessionId, { entra_roles: data.roles });
},
onComplete: (data) => {
// Re-fetch a clean snake_case snapshot instead of hand-mapping the
// camelCase callback payload — ait_status() already returns exactly
// what NxtlinqSession.save() expects.
fetch(`/ait/status/${data.sessionId}`).then(r => r.json())
.then(snapshot => NxtlinqSession.save(data.sessionId, snapshot));
},
onError: (step, message) => console.error(step, message),
});
</script>
Options:
| Option | Default | Description |
|---|---|---|
container |
— (required) | CSS selector or element to mount into |
mode |
'inline' |
'inline' renders the steps directly; 'modal' renders a trigger button that opens a popup panel |
apiBase |
'' |
Base URL of the agent API (same-origin if empty) |
sessionId |
null |
Resume an existing server session instead of creating a new one |
steps |
['entra','wallet','verify','permissions'] |
Which steps to show — drop 'entra' if you don't use EntraAuth |
entra |
— | { clientId, tenantId, redirectUri } — only needed if 'entra' is in steps |
tools |
[] |
[{ name, label, desc }, ...] — tool catalogue rendered as checkboxes in the permissions step (same list you passed to register_tools). Without this, the permissions step has nothing to show. Auto-filtered to whatever the admin actually granted. |
onStepComplete(step, data) |
— | Fires after each step finishes. data uses camelCase (sessionId, walletAddress, allowedTools, deniedTools, permissionsConfigured, ...) — not the snake_case the REST API/NxtlinqSession use |
onComplete(data) |
— | Fires once every configured step is done. Same camelCase data shape as above |
onError(step, message) |
— | Fires on any step failure |
onRestored(saved) |
— | Fires if a saved session was restored from NxtlinqSession on load |
mode: 'modal'-only options: triggerLabel (default '⚙️'), modalTitle
(default '⚙️ Setup'), showSignOut (default true), onSignOut().
init() returns an instance with .destroy(), and in modal mode also
.open() / .close().
NxtlinqSession
<script src="/nxtlinq/session.js"></script>
<script>
// On every page load — restores identity state via POST /session/restore
NxtlinqSession.restore('').then(result => {
if (result) console.log('restored session', result.session_id);
});
// Read the cached state without hitting the server
const saved = NxtlinqSession.get();
// identityData must use the same snake_case field names as ait_status()'s
// response (wallet_address, allowed_tools, ...) — NOT NxtlinqWidget's
// camelCase callback payloads, see the onComplete example above.
NxtlinqSession.save(sessionId, identityData);
NxtlinqSession.update(sessionId, { allowed_tools: [...] }); // merge a partial update
// On sign-out
NxtlinqSession.clear();
</script>
save/update/get/clear are synchronous localStorage operations.
restore(apiBase) is the only one that hits the network — it posts the
locally saved state to AITRouter's /session/restore and resolves with
the server's up-to-date identity/permission snapshot, or null if nothing
was saved or the request failed.
Typical Agent Integration Pattern
AITRouter + EntraAuth + SessionStore handle all the HTTP surface and
session bookkeeping; your app only needs to write the tool dispatch loop and
call guard_tool_call before executing a gated tool.
# session_store.py
from dataclasses import dataclass, field
from nxtid_sdk import NxtidSessionMixin, SessionStore
@dataclass
class Session(NxtidSessionMixin):
messages: list = field(default_factory=list) # your own app state
sessions = SessionStore(Session)
get_session, get_or_create_session = sessions.get, sessions.get_or_create
# main.py (FastAPI)
from nxtid_sdk import NxtidSDK, AITRouter, EntraAuth, ToolCallBlockedError
sdk = NxtidSDK.from_env() # may be None if credentials are missing
ait_router = AITRouter(sdk, get_session=sessions.get, create_session=sessions.create) # construct/mount unconditionally
# auto_grant_by_role's signature matches on_login's — no wrapper needed.
entra = EntraAuth.from_env(
get_session=get_or_create_session,
on_login=ait_router.auto_grant_by_role,
)
# No hand-written lifespan/session endpoints, and no `if ait_router`/`if sdk`
# guards anywhere — AITRouter and its methods are all None-safe internally.
app = FastAPI(lifespan=ait_router.lifespan(TOOL_DEFINITIONS))
ait_router.mount(app)
entra.mount(app)
sessions.mount(app)
# In your tool dispatch loop, before executing a gated tool
# (a chat session is fine to auto-create, unlike the AIT identity routes above):
session = get_or_create_session(session_id)
try:
await sdk.guard_tool_call(tool_name, session)
except ToolCallBlockedError as exc:
exc.reason # "verification_required" | "awaiting_admin_grant" | "permission_denied"
exc.message # user-facing explanation
# At the start of every turn, before building a system prompt that lists
# which tools are currently off-limits — see refresh_session_permissions above
# for why this can't just wait for guard_tool_call to run:
await sdk.refresh_session_permissions(session)
Requirements
- Python ≥ 3.10
httpx >= 0.27fastapi >= 0.111,pydantic >= 2.7(forAITRouterandEntraAuth)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nxtid_sdk-0.1.7.tar.gz.
File metadata
- Download URL: nxtid_sdk-0.1.7.tar.gz
- Upload date:
- Size: 61.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e65aafe477b1203ccd87dc6f35157d7ef804f3a640bd8022b07c234560131bd9
|
|
| MD5 |
4f14630ee504cb21cb28a426d6302aea
|
|
| BLAKE2b-256 |
e230508d5d5affc02a76a704e11ad732421e019023106b22869207daee9af7a4
|
File details
Details for the file nxtid_sdk-0.1.7-py3-none-any.whl.
File metadata
- Download URL: nxtid_sdk-0.1.7-py3-none-any.whl
- Upload date:
- Size: 46.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbf83341d9e97a9632151e3073f0b1fb2c716f18f013a9df6e9bba928da453ea
|
|
| MD5 |
0cb34cb4aae9d66af43dda2f4b73cb6b
|
|
| BLAKE2b-256 |
baed8812a7134b00b44aa7330594962611d46cd988922a6e5151969f31861c96
|