AppSDK
Application SDK
Lightweight SDK for invoking healthcare AI agents in your medical applications and EHR systems.HIPAA-ready, fast, and production-ready with streaming responses for clinical workflows and session support for patient consultations.
Installation
Using pip
pip install hikigai-appsdkQuick Start
import asyncio
from hikigai.appsdk import AppClient
# Initialize client (or set HIKIGAI_API_KEY / HIKIGAI_PROJECT_ID)
client = AppClient(
api_key="your-api-key",
project_id="your-project-id",
)
# Get an agent by slug or id
agent = client.agent("medical-coder")
# Invoke
response = agent.invoke("Patient presents with fever and cough...")
print(response.content)
# Stream
for chunk in agent.stream("Tell me about diabetes"):
print(chunk, end="", flush=True)
# Session-based conversations
session_agent = agent.with_session("user-123")
session_agent.invoke("What is hypertension?")
session_agent.invoke("How is it treated?") # Remembers context
# Live WebSocket (install: pip install websockets)
async def live():
async with client.live(agent.slug, user_id="app-user") as session:
await session.send_text("Hello")
async for event in session:
print(event)
if event.get("type") in ("turn_complete", "end"):
break
asyncio.run(live())Client Reference
class AppClient
AppClientCoreMain client for invoking AI agents. Manages authentication, HTTP connections, and provides access to deployed agents.
Constructor
client = AppClient(
api_key: Optional[str] = None, # API key (or HIKIGAI_API_KEY env var)
project_id: Optional[str] = None, # Project ID (or HIKIGAI_PROJECT_ID env var)
base_url: Optional[str] = None, # API endpoint (defaults to production)
timeout: float = 30.0, # Request timeout in seconds
sona_url: Optional[str] = None, # Optional SONA service URL
sona_api_key: Optional[str] = None, # Optional SONA API key
)Methods
| Method | Returns | Description |
|---|---|---|
agent(agent_id: str) | RuntimeAgent | Get agent by ID or slug for invocation |
list_agents(category=None, tags=None, search=None) | List[RuntimeAgent] | List available agents with optional filters |
invoke(agent_id, input, session_id=None, provider=None, model=None, connectors=None, plugin_context=None) | InvokeResponse | Invoke an agent without fetching RuntimeAgent first |
live(agent_id, session_id=None, user_id='sdk-user', context=None, connectors=None) | StreamSession | Open a live WebSocket session (optional MCP connectors) |
create_session(external_user_ref=None, ttl_seconds=None, app_id='_', metadata=None) | SessionContext | Create a server-side session context store |
session(session_id, app_id='_') | SessionContext | Attach to an existing session context |
list_sessions(user_ref=None, limit=50, cursor=None) | Dict | List session contexts |
get_auth_token() | Dict[str, Any] | Exchange API key for a short-lived access token |
Properties
identity() | IdentityClient | End-user auth (Cognito, MFA, QR, SSO) |
sona() | SONAClient | SONA personalization (edits, preferences, suggestions) |
storage() | StorageClient | App object storage |
Context Manager
with AppClient() as client:
agent = client.agent("my-agent")
response = agent.invoke("Hello")
# Resources automatically releasedAgent Reference
class RuntimeAgent
RuntimeAgentModelRepresents a deployed agent ready for invocation. Provides methods for synchronous invocation, streaming, and session management.
Properties
| Property | Type | Description |
|---|---|---|
id | str | Unique agent ID |
name | str | Agent name |
slug | str | URL-friendly slug |
version | str | Agent version |
description | str | None | Agent description |
status | str | Deployment status ('active', 'pending', etc.) |
endpoint | str | None | Invocation endpoint URL |
Methods
| Method | Returns | Description |
|---|---|---|
invoke(input, session_id=None, provider=None, model=None, timeout=None, connectors=None, plugin_context=None) | InvokeResponse | Invoke agent synchronously with optional session, MCP connectors, and plugins |
stream(input, session_id=None, provider=None, model=None, connectors=None) | Iterator[str] | Stream agent response chunks in real-time (SSE) |
with_session(session_id: str) | RuntimeAgent | Create agent instance with persistent session |
live_session(session_id=None, *, connectors=None, context=None, user_id='sdk-user') | StreamSession | Open a bidirectional WebSocket live session (optional MCP connectors) |
Basic Usage
agent = client.agent("medical-coder")
# Synchronous invocation
response = agent.invoke("Patient has diabetes...")
print(response.content)
# With MCP connectors + SONA plugin context
response = agent.invoke(
"Prepare patient chart",
connectors={"epic-ehr": {"headers": {"Authorization": "Bearer ..."}}},
plugin_context={"sona": {"user_id": "dr-smith-uuid"}},
)
# Streaming
for chunk in agent.stream("Explain hypertension"):
print(chunk, end="")
# With session
response = agent.invoke("My name is Alice", session_id="user-123")
response = agent.invoke("What's my name?", session_id="user-123") # Remembers "Alice"Workflow agents (including those that reuse marketplace leaves via AgentSDK source="marketplace") are invoked the same way — use the root agent's slug or id.
Response Types
class InvokeResponse
InvokeResponsePydantic ModelResponse from a synchronous agent invocation containing output content and metadata.
| Property | Type | Description |
|---|---|---|
content* | str | Agent output content |
agent_id | str | ID of the agent that responded |
agent_version | str | None | Version of the agent |
session_id | str | None | Session ID if used |
status | str | Status (success, requires_human_review, etc.) |
output | Dict | None | Structured agent output when available |
confidence | ClinicalConfidence | None | Clinical confidence score (0–1) |
safety_flags | List[SafetyFlag] | Safety flags (severity: low|medium|high|critical) |
citations | List[ClinicalCitation] | Evidence-based clinical citations |
metadata | InvocationMetadata | Invocation metadata (latency, tokens, etc.) |
message | str | None | Optional warning or info message |
plugins | Dict | None | Plugin metadata (e.g. plugins['sona']) |
Example
response = agent.invoke("Hello")
print(response.content) # "Hi! How can I help you?"
print(response.status) # "success"
print(response.metadata.latency_ms) # 234
print(response.metadata.tokens_used) # 156
print(response.metadata.tools_called) # ["web_search"]
print(response.plugins) # {"sona": {"output_id": "..."}}class InvocationMetadata
InvocationMetadataPydantic ModelMetadata about an agent invocation including performance metrics and execution details.
invocation_id | str | Unique invocation ID |
latency_ms | int | None | Response latency in milliseconds |
timestamp | datetime | Invocation timestamp |
status | str | Status ('success', 'error', etc.) |
tokens_used | int | None | Total tokens consumed |
tools_called | List[str] | Tools used (from top-level gateway tools_called — includes MCP CallTool on Sequential/Parallel/Loop leaves) |
phi_redacted | bool | Whether PHI was redacted |
trace_id | str | None | Trace ID for troubleshooting |
MCP on Workflow Agents
Deploy a Sequential / Parallel / Loop agent with root connectors in AgentSDK → MCP on Sub-Agents. Then pass the same connector map on invoke or live. MCP tools attach to every LlmAgent leaf. Check metadata.tools_called (populated from the gateway top-level field) to confirm CallTool ran.
from hikigai.appsdk import AppClient
client = AppClient(api_key="...", project_id="...")
agent = client.agent("vitals-fetch-subagent")
# Same connectors shape for simple LLM roots and Sequential leaves
response = agent.invoke(
{"site_id": 1, "patient_id": "PATIENT001"},
connectors={"idexx-mcp-server": {}},
timeout=120,
)
print(response.content) # summary text / {"response": "..."}
print(response.metadata.tools_called) # e.g. ["get_vitals_for_today"]
print(response.metadata.latency_ms)
# Live WebSocket — MCP on leaves too
# async with client.live(
# "vitals-fetch-subagent",
# connectors={"idexx-mcp-server": {}},
# ) as session:
# await session.start(modality="text")
# await session.send_text("Fetch today's vitals")
# async for event in session:
# if event.get("type") == "tool_call":
# print(event) # leaf CallTool frames
{"response": "..."} so schema retries do not re-fire MCP. Empty tools_called with MCP server logs showing CallTool usually means an outdated AppSDK — upgrade and re-check top-level gateway fields.Live WebSocket Sessions
live_handoff when deploying (see AgentSDK → Workflow Agents). To reuse a marketplace agent as a leaf, deploy with source="marketplace" + name (documented under SubAgentConfig). AppSDK only opens the session — it does not define workflow structure.Bidirectional realtime via StreamSession. Flow: auth/exchange → POST …/live/session → WebSocket at the returned wss_url (…/live) → binary PCM (protocol v2). Captions use source: input_transcription; replace pending text on partials. Requires the optional websockets dependency. Full protocol: API Reference → Live Sessions.
import asyncio
from hikigai.appsdk import AppClient
async def main():
client = AppClient(api_key="...", project_id="...")
client.get_auth_token() # POST /auth/exchange — cache for mint
# AppSDK generates session_id if omitted; always mints with {session_id, user_id}
async with client.live("YOUR_AGENT_SLUG", user_id="app-transcriber") as session:
await session.start(modality="audio", mime_type="audio/pcm;rate=16000")
await session.send_data(pcm_bytes) # raw bytes → binary frames
async for event in session:
if event.get("source") == "input_transcription":
print(event["content"]) # replace UI on partials
if event.get("type") == "end":
break
await session.end()
asyncio.run(main())Attach MCP tools on live the same way as invoke: pass connectors. Mint then uses the gateway /live path so connectors resolve before bidirectional run_live.
async with client.live(
"clinical-live-agent",
user_id="app-user",
connectors={"epic-ehr": {}}, # same shape as invoke
) as session:
await session.send_text("Look up the patient chart")
async for event in session:
if event.get("type") == "tool_call":
print(event)
if event.get("type") in ("turn_complete", "end"):
breakSpeech: Translate & Transcribe
Real-time speech-to-speech translation and speech-to-text transcription via client.speech. These are not agents — nothing to deploy, and no agent slug. Available to team organizations with GCP connected, billed to your own cloud project. Full protocol: API Reference → Speech.
Translate
import asyncio
from hikigai.appsdk import AppClient
async def main():
client = AppClient(api_key="hikigai_...", project_id="...")
session = await client.speech.translate(
target_language="es",
echo_target_language=True, # default: echo when input is already Spanish
input_transcription=True, # default
output_transcription=True, # default
)
async def send():
# Any source format — converted to 16 kHz mono PCM for you.
async for chunk in phone_call():
await session.send_audio(chunk, encoding="mulaw", sample_rate=8000)
asyncio.create_task(send())
async for event in session:
if event.type == "audio":
speaker.write(event.audio) # 24 kHz PCM
elif event.type == "transcript":
print(event.source, event.text, event.language_code) # "input" / "output"
asyncio.run(main())Transcribe
session = await client.speech.transcribe(
languages=["en-US", "es-ES"], # optional; omit to auto-detect
custom_vocabulary=["Hikigai", "Kubernetes"],
)
await session.send_audio(pcm_16k) # already 16 kHz mono PCM
async for event in session:
if event.type == "transcript":
print(event.text, event.language_code, "final" if event.final else "interim")Note the code shapes differ: translate takes bare subtags (es), transcribe takes full locales (es-ES). Google caps transcription at 10 minutes; the gateway rotates the upstream transparently, so long sessions need no handling.
Changing language mid-conversation
Returns once the new language is live. A replacement model session is brought up before the old one is retired, so audio keeps flowing and the listener hears no gap.
await session.set_language("fr") # no reconnect, no new tokenAudio helpers
send_audio converts for you, but the helpers are exported for callers doing their own buffering.
from hikigai.appsdk import to_model_audio, chunk_audio, pcm_to_wav
pcm = to_model_audio(raw, encoding="mulaw", sample_rate=8000) # telephony → 16 kHz
pcm = to_model_audio(raw, encoding="f32", sample_rate=48000) # browser Float32
pcm = to_model_audio(raw, sample_rate=48000, channels=2) # stereo → mono
for chunk in chunk_audio(pcm): # ~100 ms frames, never splits a sample
await session.send_audio(chunk)
open("out.wav", "wb").write(pcm_to_wav(translated)) # 24 kHz by defaultBrowser and mobile clients
Mint a short-lived token server-side so your API key never reaches the client.
token = client.speech.mint_token(
"translate",
target_language_code="ja",
locked=True, # pin the config — client cannot call configure
)
# Hand token["speech_token"] to the browser; it connects directly.Session Context Store
Server-side key/value context for apps (SessionContext), separate from conversation session_id memory on invoke.
# Create a context store
ctx = client.create_session(external_user_ref="dr-smith", ttl_seconds=3600)
ctx.set("patient_id", "MRN-12345")
ctx.set("visit_id", "V-99", if_match=None)
print(ctx.get("patient_id"))
print(ctx.get_all())
print(ctx.metadata())
# Attach later
ctx = client.session(ctx.session_id)
ctx.update({"chief_complaint": "chest pain"})
ctx.touch(ttl_seconds=7200)
ctx.delete("visit_id")
ctx.clear()Session Management
Maintain conversation context across multiple invocations using sessions. Sessions enable multi-turn conversations where the agent remembers previous interactions.
Per-Invocation Sessions
Pass a session_id to each invocation to maintain context.
agent = client.agent("chatbot")
# First message
response1 = agent.invoke(
"My name is Alice",
session_id="user-123"
)
# Follow-up - agent remembers the name
response2 = agent.invoke(
"What's my name?",
session_id="user-123"
)
print(response2.content) # "Your name is Alice"Persistent Session Agent
Create a session-bound agent using with_session(). All invocations automatically use the same session.
agent = client.agent("assistant")
# Create session-bound agent
session_agent = agent.with_session("user-456")
# All invocations share the same session
session_agent.invoke("I live in New York")
session_agent.invoke("What's the weather like here?") # Knows "here" = NYC
# Streaming works too
for chunk in session_agent.stream("Tell me more about my city"):
print(chunk, end="")Multiple Users
Maintain separate contexts for different users with different session IDs.
agent = client.agent("support-bot")
# User 1's conversation
user1 = agent.with_session("user-001")
user1.invoke("I'm having trouble logging in")
# User 2's isolated conversation
user2 = agent.with_session("user-002")
user2.invoke("How do I reset my password?")
# Contexts are completely separate
# User 1's issues don't affect User 2's conversationRooms
Create and use multi-party realtime rooms with RoomSession — same class as AgentSDK. Call join(name); the first join creates the room in your project. Then publish, listen for frames, and optionally negotiate WebRTC with the rtc_* helpers. Standalone (not on AppClient). Requires websockets.
Full method list and frame types: AgentSDK → RoomSession. Protocol details: API reference → Rooms.
Create, join, publish
import asyncio
from hikigai.appsdk import RoomSession
async def main():
room = RoomSession(
base_url="https://backend.hikigaiplatform.io",
api_key="your-api-key",
project_id="your-project-id",
)
async with room:
await room.join("care-team-7") # creates the room if new
await room.publish("care-team-7", "note", {"text": "Labs ready"})
await room.who("care-team-7")
async for frame in room:
if frame.get("type") == "message":
print(frame["event"], frame["data"])
elif frame.get("type") == "error":
print(frame.get("code"), frame.get("message"))
break
asyncio.run(main())Auth
Pass exactly one of api_key, access_token, or room_token, plus project_id when using a key/JWT. End-user tokens are scoped to specific rooms and capabilities; project keys get full access in that project.
WebRTC signalling
await room.join("call-1")
await room.rtc_join("call-1")
async for frame in room:
if frame["type"] == "rtc_peer_joined" and frame.get("initiator"):
await room.rtc_offer("call-1", frame["id"], await make_offer())
elif frame["type"] == "rtc_offer":
await room.rtc_answer("call-1", frame["sender"], await answer(frame["sdp"]))Even Realities G2 Glasses Integration
Build apps for Even Realities G2 smart glasses with a Python proxy that keeps your API key private. G2 apps run in the Even phone app WebView and drive the 640×350 green monochrome display via @evenrealities/even_hub_sdk. The glasses UI is always TypeScript; Python (or raw HTTP) only replaces the proxy. Full WebView / event loop: Node.js AppSDK G2 Guide. Console HTTP APIs: API Reference → Even G2. Starter (Node UI + Express): templates/even-g2.
Environment variables
Proxy needs HIKIGAI_API_KEY, HIKIGAI_PROJECT_ID, and HIKIGAI_AGENT_ID. On Hikigai deploy, key and project id are injected; set the agent id under Environment in the console. Optional: HIKIGAI_PLATFORM_URL, GLASSES_PAGE_CHARS (starter default 420).
app.json & voice notes
name≤ 20,tagline≤ 50; permissions typicallynetwork+g2-microphone.- Voice requires an agent with audio in its input modalities; list/text questions work with any text agent.
- Send WAV via
attachments(mime_type="audio/wav"), not aninput_audiocontent array.
SDK helpers (pure, no network)
from hikigai.appsdk import (
validate_even_g2_manifest, # same app.json rules as the deploy pipeline
paginate_for_glasses, # split agent answers into display-sized pages
normalize_for_glasses, # strip markdown decorations agents emit
even_g2_dev_mode_url, # validate the live URL used in the dev-mode QR
EVEN_G2_DISPLAY, # {"width": 640, "height": 350}
EVEN_G2_MAX_TEXT_CHARS, # 2000 (official text-heavy template cap)
)
check = validate_even_g2_manifest(json.load(open("app.json")))
if not check.valid:
raise SystemExit(check.error)
pages = paginate_for_glasses(response.content, max_chars=420)
qr_payload = even_g2_dev_mode_url(live_url)1. Proxy API with Pagination
The proxy receives queries from the WebView, invokes the agent, and returns pages sized for the G2 display.
import os
from fastapi import FastAPI
from pydantic import BaseModel
from hikigai.appsdk import AppClient, normalize_for_glasses, paginate_for_glasses
app = FastAPI()
client = AppClient() # reads HIKIGAI_API_KEY / HIKIGAI_PROJECT_ID
PAGE_CHARS = int(os.environ.get("GLASSES_PAGE_CHARS", "420"))
AGENT_ID = os.environ["HIKIGAI_AGENT_ID"]
class AskRequest(BaseModel):
question: str
sessionId: str | None = None
@app.post("/api/ask")
async def ask(req: AskRequest):
agent = client.agent(AGENT_ID)
response = agent.invoke(req.question, session_id=req.sessionId)
clean = normalize_for_glasses(response.content)
return {
"answer": response.content,
"pages": paginate_for_glasses(clean, max_chars=PAGE_CHARS),
"sessionId": response.session_id,
}
2. Voice Queries (WAV attachments)
Forward base64 WAV from the glasses mic via attachments. The agent must allow audio in its input modalities — text-only agents reject voice. List/preset text questions work with any text agent.
class AudioRequest(BaseModel):
audio_base64: str
mime_type: str | None = "audio/wav"
sessionId: str | None = None
@app.post("/api/ask-audio")
async def ask_audio(req: AudioRequest):
agent = client.agent(AGENT_ID)
response = agent.invoke(
"Answer the question in this recording.",
session_id=req.sessionId,
attachments=[{
"data_base64": req.audio_base64,
"mime_type": req.mime_type or "audio/wav",
}],
)
clean = normalize_for_glasses(response.content)
return {
"pages": paginate_for_glasses(clean, max_chars=PAGE_CHARS),
"sessionId": response.session_id,
}
Multi-Cloud Deployment
client.cloud exposes the deployment catalog and BYOC credentials. Apps, agents, and MCP servers deploy to GCP or AWS targets from the same catalog.
from hikigai.appsdk import AppClient
client = AppClient(api_key=..., project_id=...)
catalog = client.cloud.catalog(workload="app")
for service in catalog.services:
print(service.id, service.display_name, service.regions[0].id)
result = client.cloud.validate_target(
workload="app",
service_id="gcp-cloud-run",
region="europe-west1",
config={"cpu": "2", "memory": "1Gi"},
)Events & Webhooks
client.events is the SDK surface for the platform event bus — HMAC-signed webhooks (at-least-once) andevents.stream() (at-most-once).
sub = client.events.create_webhook(
url="https://ci.example.com/hooks/hikigai",
event_types=["agent.deployed", "agent.deleted"],
description="deploy notifications",
)
print(sub.secret) # shown once — store it
# Live stream
async with client.events.stream(patterns=["job.*", "agent.deployed"]) as stream:
async for event in stream:
print(event.type, event.data)Storage
Access via client.storage. Platform-managed object storage scoped to project + app_id — PDFs, images, audio, and other app outputs. Same client as AgentSDK.
Methods
| Method | Returns | Description |
|---|---|---|
upload(app_id, file, filename, content_type=..., metadata=None, ttl=None) | StorageUploadResult | Multipart upload; optional metadata + object TTL. Includes signed_url on success. |
signed_url(app_id, object_id, ttl=3600) | SignedUrlResult | Mint a temporary download URL |
list(app_id, content_type=None, status='active', limit=20, page=1) | StorageListResult | Paginated list with filters |
get(app_id, object_id) | StorageObjectInfo | Fetch object metadata |
delete(app_id, object_id) | None | Delete an object |
with open("audio.wav", "rb") as f:
uploaded = client.storage.upload(
app_id="app_123",
file=f,
filename="audio.wav",
content_type="audio/wav",
metadata={"session": "s-9"},
ttl=86400,
)
url = client.storage.signed_url("app_123", uploaded.object_id, ttl=3600)
print(url.signed_url, url.expires_at)
info = client.storage.get("app_123", uploaded.object_id)
print(info.original_filename, info.size_bytes, info.metadata)
page = client.storage.list("app_123", content_type="audio/wav", status="active", limit=20, page=1)
for obj in page.objects:
print(obj.object_id, obj.original_filename)
print(page.total, page.total_pages)
client.storage.delete("app_123", uploaded.object_id)SONA Personalization
Access via client.sona. Invoke with plugin_context={"sona": {"user_id": "..."}}to receive SONA metadata on response.plugins.
response = agent.invoke(
"Draft discharge summary",
plugin_context={"sona": {"user_id": "dr-smith-uuid"}},
)
output_id = response.plugins["sona"]["output_id"]
client.sona.submit_edit(output_id, "User-edited note text...")
client.sona.approve_output(output_id, user_id="dr-smith-uuid")
prefs = client.sona.get_preferences("dr-smith-uuid", agent_id=agent.id)
suggestions = client.sona.get_suggestions("dr-smith-uuid")End-User Identity
Add signup, login, MFA, QR badge login, and SSO for your app's end users — clinicians, patients, staff. Each app is backed by a dedicated AWS Cognito user pool the platform provisions for you; login returns standard OIDC tokens. Everything is reached through client.identity.
class IdentityClient
client.identityCoreEnd-user authentication and account management for the app's Cognito pool, plus QR/PIN badge credentials. Access it as a property on AppClient.
Account & auth methods
| Method | Returns | Description |
|---|---|---|
signup(app_id, email, password, first_name=None, last_name=None, role=None, attributes=None, metadata=None) | dict | Register a new end user. attributes = Cognito fields (e.g. phone_number); metadata = opaque JSON as custom:metadata. |
confirm(app_id, email, code) | dict | Confirm a signup with the emailed code |
login(app_id, email, password) | dict | Authenticate — returns OIDC tokens or an MFA challenge |
refresh(app_id, refresh_token) | dict | Exchange a refresh token for fresh tokens |
logout(app_id, email) | dict | Globally sign the user out |
forgot_password(app_id, email) | dict | Request a password-reset code (emailed by Cognito) |
reset_password(app_id, email, code, new_password) | dict | Set a new password with the emailed code |
change_password(app_id, access_token, current_password, new_password) | dict | Change password for a logged-in user |
create_user(app_id, email, password, first_name, last_name, role=None, metadata=None) | dict | Same as signup — creates the Cognito/GCP user |
get_user(app_id, email) | dict | Fetch Cognito profile (requires app_id) |
list_users(app_id, limit=50, offset=0) | list[dict] | List users in the app's pool |
update_user(app_id, email, **fields) | dict | Update Cognito profile (role, name, metadata, is_active, phone_number). Email cannot be changed. |
list_app_users(app_id, limit=50, offset=0) | list[dict] | List the app's end users |
delete_app_user(app_id, email) | None | Delete a user from the pool |
verify_qr(app_id, qr_payload) | VerifiedUser | Verify an HKG2 QR badge against the app pool |
verify_pin(app_id, email, pin) | VerifiedUser | Verify PIN stored on Cognito |
verify_sso(app_id, token) | VerifiedUser | Verify SSO id token for the app pool |
issue_qr(app_id, email) | dict | Issue Cognito QR badge (same as issue_qr_login) |
issue_pin(app_id, email, pin) | dict | Set PIN on the Cognito user |
MFA, QR & SSO
| Method | Returns | Description |
|---|---|---|
mfa_associate(app_id, access_token) | dict | Begin TOTP enrollment — returns secret for a QR code |
mfa_verify(app_id, access_token, code, device_name=None) | dict | Verify the first code to finish enrollment |
mfa_set_preference(app_id, access_token, enabled=True) | dict | Turn TOTP MFA on or off |
mfa_respond(app_id, email, session, code) | dict | Complete a login that returned an MFA challenge |
issue_qr_login(app_id, email) | dict | Issue a QR badge credential for a user |
qr_login(app_id, qr_payload) | dict | Exchange a scanned QR payload for tokens |
configure_sso(app_id, provider_name, provider_type, …) | dict | Add a Google/Okta/SAML/OIDC provider |
sso_authorize_url(app_id, redirect_uri, provider=None) | dict | Build the hosted-UI authorize URL |
sso_exchange(app_id, code, redirect_uri) | dict | Exchange the callback code for tokens |
Setup (one-time, per app)
enable_identity(app_id, mode='managed', enabled_methods=…) | dict | Provision the app's Cognito pool |
get_identity_config(app_id) | dict | Get config + provisioning status |
byo_setup() | dict | CloudFormation params for bring-your-own Cognito |
byo_test(role_arn, region=None) | dict | Verify a cross-account role before enabling BYO |
Sign up, confirm, log in
attributes are Cognito profile fields (string values — e.g. phone_number in E.164). metadata is opaque app JSON stored as Cognito custom:metadata (GCP BYO uses custom claims). They are not interchangeable. Cognito/GCP is the source of truth — there is no separate EndUser DB index for profile data.
from hikigai.appsdk import AppClient
client = AppClient(api_key="hikigai_...", project_id="proj_123")
# 1. Register the end user
client.identity.signup(
app_id="app_123",
email="clinician@hospital.com",
password="Temp#Pass2026",
first_name="Asha",
last_name="Mehta",
role="clinician",
# attributes → Cognito fields (strings only; e.g. phone_number in E.164)
attributes={"phone_number": "+14155552671"},
# metadata → Cognito custom:metadata JSON
metadata={"doctorID": "6278383837"},
)
# 2. Confirm (skip if the pool auto-confirms)
client.identity.confirm("app_123", "clinician@hospital.com", code="123456")
# 3. Log in -> OIDC tokens
tokens = client.identity.login("app_123", "clinician@hospital.com", "Temp#Pass2026")
print(tokens["access_token"])
# 4. Refresh later
fresh = client.identity.refresh("app_123", tokens["refresh_token"])Forgot password
# 1. Request reset code (Cognito emails it)
client.identity.forgot_password("app_123", "clinician@hospital.com")
# 2. User enters code + new password
client.identity.reset_password(
"app_123", "clinician@hospital.com", code="123456", new_password="NewTemp#Pass2026"
)Change password (logged in)
tokens = client.identity.login("app_123", "clinician@hospital.com", "Temp#Pass2026")
client.identity.change_password(
"app_123",
access_token=tokens["access_token"],
current_password="Temp#Pass2026",
new_password="NewTemp#Pass2026",
)Update role / profile
# app_id + email required (Cognito is source of truth)
client.identity.update_user(
"app_123", "clinician@hospital.com",
role="doctor", first_name="Asha", last_name="Mehta",
)Multi-factor login
# Login may return an MFA challenge instead of tokens
result = client.identity.login("app_123", "clinician@hospital.com", "Temp#Pass2026")
if result.get("status") == "challenge":
code = input("Enter your authenticator code: ")
tokens = client.identity.mfa_respond(
app_id="app_123",
email="clinician@hospital.com",
session=result["session"],
code=code,
)
print(tokens["access_token"])QR badge login
# Admin: issue a badge for the user (HKG2 payload shown once)
badge = client.identity.issue_qr_login("app_123", "clinician@hospital.com")
print(badge["qr_payload"]) # encode into the printed badge
# Kiosk: scan the badge, exchange it for tokens
tokens = client.identity.qr_login("app_123", scanned_payload)
# Or verify without minting tokens (runtime check)
user = client.identity.verify_qr("app_123", scanned_payload)
print(user.first_name, user.role)signup / create_user
Use identity.signup() (or create_user, which calls the same path) for Cognito/GCP accounts. Profile, QR, and PIN all live on the IdP — pass app_id on management and verify calls. Prefer email over legacy platform UUIDs.
Complete Examples
Basic Agent Invocation
from hikigai.appsdk import AppClient
client = AppClient()
# Get agent
agent = client.agent("customer-support")
# Invoke
response = agent.invoke("How do I reset my password?")
print(response.content)
print(f"Latency: {response.metadata.latency_ms}ms")Streaming Responses
agent = client.agent("content-writer")
# Stream in real-time
print("Agent response: ", end="")
for chunk in agent.stream("Write a paragraph about AI"):
print(chunk, end="", flush=True)
print() # New lineSession-Based Chat Application
import uuid
from hikigai.appsdk import AppClient
def chat_session(user_id: str):
"""Start a chat session for a user."""
client = AppClient()
agent = client.agent("chatbot")
# Create session-bound agent
session = agent.with_session(user_id)
print("Chat started. Type 'exit' to quit.")
while True:
user_input = input("You: ")
if user_input.lower() == "exit":
break
# Invoke with session context
response = session.invoke(user_input)
print(f"Bot: {response.content}")
# Start chat for a user
chat_session(f"user-{uuid.uuid4()}")Listing and Filtering Agents
client = AppClient()
# List all agents
agents = client.list_agents()
for agent in agents:
print(f"{agent.name} (v{agent.version}): {agent.status}")
# Filter by category
medical_agents = client.list_agents(category="Medical Coding")
# Search
search_results = client.list_agents(search="diagnosis")Error Handling
from hikigai.appsdk import (
AppClient,
AgentNotFoundError,
InvocationError,
RateLimitError
)
client = AppClient()
try:
agent = client.agent("my-agent")
response = agent.invoke("Hello")
print(response.content)
except AgentNotFoundError:
print("Agent not found. Check the agent ID.")
except InvocationError as e:
print(f"Invocation failed: {e}")
except RateLimitError:
print("Rate limit exceeded. Please wait.")
except Exception as e:
print(f"Unexpected error: {e}")Error Handling
AgentNotFoundError
Raised when the requested agent doesn't exist
from hikigai.appsdk import AgentNotFoundError
try:
agent = client.agent("nonexistent-agent")
except AgentNotFoundError as e:
print(f"Agent not found: {e}")InvocationError
Raised when agent invocation fails
from hikigai.appsdk import InvocationError
try:
response = agent.invoke("Complex query...")
except InvocationError as e:
print(f"Invocation failed: {e}")AuthenticationError
Raised when the API key is invalid or missing
from hikigai.appsdk import AuthenticationError, ConfigurationError
try:
client = AppClient(api_key="invalid-key", project_id="proj_123")
except (AuthenticationError, ConfigurationError) as e:
print(f"Auth/config failed: {e}")RateLimitError
Raised when API rate limit is exceeded
from hikigai.appsdk import RateLimitError
import time
try:
response = agent.invoke("Query")
except RateLimitError as e:
print(f"Rate limit hit. Retry after: {e.retry_after}s")
time.sleep(e.retry_after)
response = agent.invoke("Query") # Retry