Skip to main content

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 methodswallet_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 raise NxtidHTTPError (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 converts NxtidHTTPError to fastapi.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/restore calls this and responds 404 if it returns None, 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_refresh failing with "Complete IDV first") instead of a clear "session not found" — which happens whenever the in-memory session store loses state a browser tab's session_id still 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. via EntraAuth's on_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, calling AITRouter'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.27
  • fastapi >= 0.111, pydantic >= 2.7 (for AITRouter and EntraAuth)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nxtid_sdk-0.1.8.tar.gz (61.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nxtid_sdk-0.1.8-py3-none-any.whl (47.0 kB view details)

Uploaded Python 3

File details

Details for the file nxtid_sdk-0.1.8.tar.gz.

File metadata

  • Download URL: nxtid_sdk-0.1.8.tar.gz
  • Upload date:
  • Size: 61.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.3

File hashes

Hashes for nxtid_sdk-0.1.8.tar.gz
Algorithm Hash digest
SHA256 cca943f83c0d025a4f531c58cda82eee3d108fb69024e8950c97b7b4996ae1eb
MD5 291989968145e0d4abd2c78737fa220e
BLAKE2b-256 4c4015e34786fd6aeb39f6ad294d1a8503a03b4e7e51726b847e09a71a88b774

See more details on using hashes here.

File details

Details for the file nxtid_sdk-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: nxtid_sdk-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 47.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.3

File hashes

Hashes for nxtid_sdk-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 e56d86b03730e5eb11f8acfb696ef3f5a674b98d89fb761eefe0cd0e737c0ebd
MD5 1b2f1848f1ce5b6097c995bb1cccbf26
BLAKE2b-256 fe6c96c4aad6031be28116a72f186c8c53eb335783aa129589b5ee6452c530ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page