Reference

API Reference

REST API endpoints for integrating Hikigai agents into your frontend applications.

Base URL: https://backend.hikigaiplatform.io

Authentication

Authenticate using an API key to receive a JWT token for all subsequent requests.

POST/api/v1/auth/exchange

Exchange your API key for a JWT access token.

Request Headers

X-API-Key: hikigai_your_api_key_here

cURL Example

curl --location --request POST 'https://backend.hikigaiplatform.io/api/v1/auth/exchange' \
  --header 'X-API-Key: your_api_key_here'

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scopes": ["deploy", "invoke", "read", "manage"],
  "project_id": "project-id-here"
}
Token lifetime: JWT tokens expire in 24 hours. Store the token securely and exchange a new one when it expires. Scopes reflect the API key used — they are not always the full set shown above.

Agent Deployment

POST/api/v1/agents/deploy

Deploy a new agent with optional MCP connector support.

Request Headers

Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
X-Project-ID: YOUR_PROJECT_ID

Request Body

{
  "name": "{agent-slug}",
  "display_name": "Agent Display Name",
  "version": "1.0.0",
  "description": "Agent description",
  "agent_type": "llm",
  "model": "gemini-2.5-flash",
  "instruction": "System Role: You are a helpful assistant...",
  "timeout": 60,
  "input_schema": {
    "type": "object",
    "properties": {
      "message": { "type": "string", "description": "User message" }
    },
    "required": ["message"]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "result": { "type": "string" }
    }
  },
  "mcp_connectors": [
    {
      "slug": "connector-slug",
      "tool_filter": ["*"],
      "required": true
    }
  ]
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/deploy' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --data '{
    "name": "{agent-slug}",
    "display_name": "Agent Display Name",
    "version": "1.0.0",
    "description": "Agent description",
    "agent_type": "llm",
    "model": "gemini-2.5-flash",
    "instruction": "System Role: You are a helpful assistant..."
  }'

Response

{
  "agent_id": "ag_abc123def456",
  "name": "cpt2-code-agent-v2",
  "slug": "cpt2-code-agent-v2",
  "status": "deploying",
  "endpoint": null,
  "job_id": "job_abc123"
}

Deploy is asynchronous. Poll the agent until status becomes active. Use GET /api/v1/agents/{agent_slug} for connector config and the live endpoint URL.

Agent Invocation

Request timeout (optional)

The timeout field is not required. Existing integrations keep working without any changes — if you omit it, the platform uses the agent's deploy-time timeout (console deploys default to 60 seconds; API/SDK deploys default to 30 unless you set one).

Deploying agents is unchanged: you do not need to set timeout at deploy time unless you want a different default for that agent. At invoke time, passtimeoutonly when you need a longer or shorter limit for a specific call (5–300 seconds).

There are two independent timeouts to be aware of:

  • Request body ("timeout": 300) — how long the Hikigai platform waits for the agent to finish processing.
  • HTTP client (e.g. requests.post(..., timeout=300)) — how long your application waits for the API response. Set this to match or exceed the body timeout so your app does not give up before the server responds.

This matches SDK behavior: agent.invoke(..., timeout=300) is also optional and uses the same defaults when omitted. Console deploys default the agent timeout to 60 seconds; API/SDK deploy defaults to 30 seconds unless you set timeout at deploy time.

POST/api/v1/agents/{agent_slug}/invoke

Invoke an agent to process a message with optional MCP connector data.

Request Headers

Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
X-Project-ID: YOUR_PROJECT_ID

Request Body

{
  "input": {
    "query": "Show me my blood pressure data for today",
    "userId": "android-user-010",
    "deviceId": "056ac949aa621795"
  },
  "timeout": 300,
  "connectors": {
    "google-health-connect": {}
  }
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/{agent-slug}/invoke' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --data '{
    "input": {
      "message": "Your message here"
    },
    "timeout": 300
  }'

Response

{
  "output": {
    "result": "Based on the health data, the patient shows stable vital signs..."
  },
  "agent_id": "ag_abc123def456",
  "status": "success",
  "session_id": "session-123",
  "latency_ms": 1250,
  "tokens_used": 342,
  "request_id": "req_xyz789",
  "tools_called": [],
  "artifacts": []
}

Structured results land in output (parsed against the agent's output schema when possible). status is one of success, partial, failed, or requires_human_review.

Streaming

POST/api/v1/agents/{agent_slug}/stream

Stream agent responses via Server-Sent Events (SSE). Useful for real-time UI updates.

Request Headers

Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
Accept: text/event-stream

Auth can also use X-API-Key. Project scope comes from the key or JWT — X-Project-ID is not required on this endpoint.

Request Body

{
  "input": {
    "query": "Your query here",
    "userId": "user-id",
    "deviceId": "device-id"
  },
  "timeout": 300,
  "connectors": {
    "connector-slug": {}
  },
  "session_id": "session-abc123"
}

timeout is optional for streaming as well — see the explanation above.

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/{agent-slug}/stream' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --header 'Accept: text/event-stream' \
  --data '{
    "input": {
      "query": "Your query here",
      "userId": "user-id",
      "deviceId": "device-id"
    },
    "connectors": {
      "connector-slug": {}
    }
  }' \
  --no-buffer

Response Format (SSE)

data: Based

data: on

data: the

data: health

data: data

data: [DONE]
SSE Format: Responses arrive as newline-delimited text chunks. Each chunk is prefixed with data: and followed by double newlines. The stream ends with data: [DONE].

Agent Management

GET/api/v1/agents

List all agents with pagination. Includes mcp_connectors configuration.

Request Headers

Authorization: Bearer YOUR_TOKEN
X-Project-ID: YOUR_PROJECT_ID

Query Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
per_pageintegerItems per page (default: 20)

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/agents?page=1&per_page=20' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'X-Project-ID: YOUR_PROJECT_ID'

Response

{
  "agents": [
    {
      "id": "ag_abc123",
      "name": "agent-name",
      "slug": "agent-slug",
      "description": "Agent description",
      "status": "active",
      "request_count": 1542,
      "mcp_connectors": [
        {
          "slug": "connector-slug",
          "tool_filter": ["*"],
          "required": true
        }
      ]
    }
  ],
  "total": 5,
  "page": 1,
  "per_page": 20
}
GET/api/v1/agents/{agent_slug}

Get details of a specific agent, including mcp_connectors configuration.

Request Headers

Authorization: Bearer YOUR_TOKEN
X-Project-ID: YOUR_PROJECT_ID

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/{agent-slug}' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'X-Project-ID: YOUR_PROJECT_ID'

Response

{
  "id": "ag_abc123def456",
  "name": "agent-name",
  "slug": "agent-slug",
  "model": "gemini-2.5-flash",
  "instruction": "You are a helpful assistant...",
  "description": "Agent description",
  "status": "active",
  "endpoint": "https://....run.app",
  "request_count": 1542,
  "mcp_connectors": [
    {
      "slug": "connector-slug",
      "tool_filter": ["*"],
      "required": true
    }
  ]
}
POST/api/v1/agents/{agent_slug}/redeploy

Redeploy an agent with updated configuration and mcp_connectors.

Request Headers

Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
X-Project-ID: YOUR_PROJECT_ID

Request Body

{
  "instruction": "Updated system prompt",
  "model": "gemini-2.5-flash",
  "mcp_connectors": [
    {
      "slug": "connector-slug",
      "tool_filter": ["*"],
      "required": true
    }
  ]
}

cURL Example

curl --location --request POST 'https://backend.hikigaiplatform.io/api/v1/agents/{agent-slug}/redeploy' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --data '{
    "instruction": "Updated system prompt",
    "model": "gemini-2.5-flash",
    "mcp_connectors": [
      {
        "slug": "connector-slug",
        "tool_filter": ["*"],
        "required": true
      }
    ]
  }'

Response

{
  "agent_id": "ag_abc123def456",
  "name": "agent-name",
  "slug": "agent-slug",
  "status": "deploying",
  "endpoint": "https://....run.app",
  "job_id": "job_abc123"
}

Redeploy is asynchronous. Poll until status is active. Connector configuration is returned on GET /api/v1/agents/{agent_slug}.

End-User Identity

Give your app's end users — clinicians, patients, staff — their own signup, login, multi-factor auth, QR badge login, and single sign-on. Each app gets a dedicated AWS Cognito user pool that the platform provisions and runs for you, so you never touch AWS. Login returns standard OIDC tokens (ID, access, refresh).

Two ways to host users. managed keeps users in Hikigai's Cognito (zero setup). byo (bring-your-own) keeps them in your own AWS account, which the platform reaches through a cross-account IAM role you create.

Base path: /api/v1/identity
Auth (server / SDK): X-API-Key: hikigai_…, X-Project-ID: YOUR_PROJECT_ID, and User-Agent: hikigai-sdk/… (required — the platform only accepts raw API keys from SDK user agents). The AppSDK sends the same values as Authorization: Bearer hikigai_… plus those headers.
Auth (JWT): Authorization: Bearer <jwt> from POST /api/v1/auth/exchange with X-Project-ID.

Browser note: Identity endpoints cannot be called directly from a browser (CORS + User-Agent policy). Call them from your backend or the AppSDK — the included identity test harness proxies requests with the correct headers.

Scopes

Each endpoint requires one scope on your API key. End users hitting auth endpoints from your app use invoke; one-time setup uses manage / read.

ScopeUsed byEndpoints
invokeYour app, per end usersignup, confirm, login, refresh, logout, forgot-password, reset-password, change-password, MFA, SSO sign-in, QR login
manageSetup & adminenable, configure SSO, issue QR badge, BYO test, update end-user profile (role, etc.), disable MFA
readSetup & adminget config, BYO setup

Typical integration flow

Every call below uses the same three headers (see each endpoint for full cURL). Replace YOUR_PROJECT_ID, YOUR_APP_ID, and hikigai_your_api_key.

# 0. Check pool is active
GET /api/v1/identity/apps/YOUR_APP_ID

# 1. Sign up an end user
POST /api/v1/identity/signup
{ "app_id": "YOUR_APP_ID", "email": "clinician@hospital.com", "password": "Temp#Pass2026", "first_name": "Asha", "last_name": "Mehta", "role": "clinician", "attributes": { "phone_number": "+14155552671" }, "metadata": { "doctorID": "6278383837" } }

# 1b. Confirm (if the pool requires email verification)
POST /api/v1/identity/confirm
{ "app_id": "YOUR_APP_ID", "email": "clinician@hospital.com", "code": "123456" }

# 2. Log in → OIDC tokens (or MFA challenge)
POST /api/v1/identity/login
{ "app_id": "YOUR_APP_ID", "email": "clinician@hospital.com", "password": "Temp#Pass2026" }

# 2b. If login returns "status": "challenge", finish with:
POST /api/v1/identity/mfa/respond
{ "app_id": "YOUR_APP_ID", "email": "...", "session": "...", "code": "482913" }

# 3. MFA enrollment (uses access_token from login)
POST /api/v1/identity/mfa/associate   → secret_code
POST /api/v1/identity/mfa/verify      → { "status": "SUCCESS" }
POST /api/v1/identity/mfa/set-preference  → { "enabled": true }

# 4. QR badge (admin issues, kiosk exchanges)
POST /api/v1/identity/apps/YOUR_APP_ID/users/clinician%40hospital.com/qr-login
POST /api/v1/identity/qr-login
{ "app_id": "YOUR_APP_ID", "qr_payload": "HKG2...." }

# Session maintenance
POST /api/v1/identity/refresh   → { "refresh_token": "..." }
POST /api/v1/identity/logout    → { "email": "..." }

# Forgot password (Cognito emails a verification code)
POST /api/v1/identity/forgot-password
{ "app_id": "YOUR_APP_ID", "email": "clinician@hospital.com" }
POST /api/v1/identity/reset-password
{ "app_id": "YOUR_APP_ID", "email": "clinician@hospital.com", "code": "123456", "new_password": "NewTemp#Pass2026" }

# Change password (user is logged in — uses access_token from login)
POST /api/v1/identity/change-password
{ "app_id": "YOUR_APP_ID", "access_token": "...", "current_password": "...", "new_password": "..." }

# Update profile / role (Cognito — path may be email or Cognito sub; app_id required)
PATCH /api/v1/end-users/clinician%40hospital.com?app_id=YOUR_APP_ID
{ "role": "doctor", "first_name": "Asha", "last_name": "Mehta" }

# Runtime verification (apps — Cognito/GCP source of truth)
POST /api/v1/identity/verify-qr
{ "app_id": "YOUR_APP_ID", "qr_payload": "HKG2...." }
POST /api/v1/identity/verify-pin
{ "app_id": "YOUR_APP_ID", "email": "clinician@hospital.com", "pin": "123456" }
POST /api/v1/identity/verify-sso
{ "app_id": "YOUR_APP_ID", "token": "eyJraWQiOi..." }

# Get one user's detail (console path — profile + Cognito status)
GET /api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/identity/users/clinician%40hospital.com
# Shared headers for every curl (SDK / server-side only):
curl ... \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{ ... }'

Setup & status

POST/api/v1/identity/apps/{app_id}/enable

Provision the app's Cognito user pool. Run this once per app. Returns immediately with status 'provisioning' or 'active'.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "mode": "managed",                      // "managed" or "byo"
  "enabled_methods": ["password", "mfa", "qr", "sso"],
  "byo_role_arn": null,                   // required when mode = "byo"
  "byo_account_id": null
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/apps/YOUR_APP_ID/enable' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{ "mode": "managed", "enabled_methods": ["password", "mfa", "qr"] }'

Response

{
  "app_id": "app_123",
  "project_id": "proj_123",
  "mode": "managed",
  "status": "active",
  "cognito_pool_id": "us-east-2_aBcD1234",
  "cognito_client_id": "1h57kf5a2...",
  "cognito_region": "us-east-2",
  "enabled_methods": ["password", "mfa", "qr"],
  "error_message": null
}
GET/api/v1/identity/apps/{app_id}

Get the app's identity config and provisioning status.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/apps/YOUR_APP_ID' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response

{
  "app_id": "app_123",
  "project_id": "proj_123",
  "mode": "managed",
  "status": "active",
  "cognito_pool_id": "us-east-2_aBcD1234",
  "cognito_client_id": "1h57kf5a2...",
  "cognito_region": "us-east-2",
  "enabled_methods": ["password", "mfa", "qr"],
  "error_message": null
}

End-user accounts

Console management path: /api/v1/projects/{project_id}/apps/{app_id}/identity
Permissions: apps:read to list users; apps:update to create, import, or delete.
Auth: API key (Authorization: Bearer hikigai_…) or session JWT from POST /api/v1/auth/exchange.

GET/api/v1/projects/{project_id}/apps/{app_id}/identity/users

List end users registered in the app's Cognito pool. Used by the Developer Console User Management tab.

Request Headers

Authorization: Bearer YOUR_TOKEN

Query Parameters

ParameterDefaultDescription
limit50Max 200 per page
offset0Pagination offset

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/identity/users' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Response

{
  "users": [
    {
      "id": "a1b2c3d4-....",
      "project_id": "proj_123",
      "first_name": "Asha",
      "last_name": "Mehta",
      "email": "clinician@hospital.com",
      "role": "clinician",
      "external_subject": "a1b2c3d4-....",
      "is_active": true,
      "qr_issued": false,
      "metadata": { "doctorID": "6278383837" },
      "created_at": "2026-06-11T12:00:00Z",
      "last_verified_at": null
    }
  ]
}
Cognito is the source of truth: id and external_subject are the Cognito subject (sub / user_sub) — the same value as the sub claim in the login id_token. Names, role, and metadata come from Cognito attributes (given_name, family_name, custom:role, custom:metadata). Prefer email as the stable handle in SDK/admin paths.
POST/api/v1/identity/signup

Register a new end user in the app's pool. If the pool requires confirmation, follow with /identity/confirm; otherwise the user can log in right away.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com",
  "password": "Temp#Pass2026",
  "first_name": "Asha",                   // optional
  "last_name": "Mehta",                   // optional
  "role": "clinician",                    // optional — app-domain role (Cognito custom:role)
  "attributes": {                         // optional — Cognito user attributes (string values)
    "phone_number": "+14155552671"        // E.164 format; other Cognito attrs also allowed
  },
  "metadata": { "doctorID": "6278383837" } // optional — opaque app JSON → custom:metadata
}
Attributes vs metadata: these are not interchangeable.
  • attributes — Cognito profile fields. Values must be strings. Keys must be Cognito attributes (or custom attributes defined on the pool), e.g. phone_number, given_name, custom:…. Unknown keys can cause Cognito to reject signup.
  • metadata — opaque app-specific JSON object (any shape). Stored on Cognito as custom:metadata (JSON string, max 2048 chars; GCP BYO uses custom claims). Use for things like doctorID, clinic id, flags — not for Cognito identity fields like phone.
Phone number: pass it in attributes, not metadata:
{ "attributes": { "phone_number": "+14155552671" } }
Use E.164 format (+ and country code). It is stored in Cognito and returned under cognito.phone_number on GET …/identity/users/{email}.
Role on signup: role is stored on Cognito as custom:role (e.g. clinician, nurse, doctor). Pass it as a top-level field or inside attributes as role / custom:role (those keys are mapped to custom:role). To change role later, use PATCH /api/v1/end-users/{email}?app_id=… or the console PATCH …/identity/users/{email} path.
Metadata on signup: metadata is an optional JSON object — pass any keys/values your app needs (e.g. doctorID, clinic id, flags). It is not for Cognito attributes like phone. Stored as Cognito custom:metadata (JSON string, max 2048 chars). Returned by the users list and GET .../identity/users/{email}. Update later via PATCH /api/v1/end-users/{email}?app_id=….

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/signup' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com",
    "password": "Temp#Pass2026",
    "first_name": "Asha",
    "last_name": "Mehta",
    "role": "clinician",
    "attributes": { "phone_number": "+14155552671" },
    "metadata": { "doctorID": "6278383837" }
  }'

Response (201)

{
  "user_sub": "a1b2c3d4-....",   // Cognito subject (sub); may be null until confirmed
  "user_id": "a1b2c3d4-....",    // same as user_sub (Cognito sub)
  "email": "clinician@hospital.com",
  "confirmed": false
}
User IDs on signup: user_id and user_sub are both the Cognito subject. Prefer email as the handle for management APIs. user_sub may be null until the account is confirmed (auto-confirm pools return it right away). The same ids are returned by /identity/login and the users list.
POST/api/v1/identity/confirm

Confirm a signup with the code emailed to the user.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com",
  "code": "123456"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/confirm' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com",
    "code": "123456"
  }'

Response (200)

{
  "success": true,
  "message": "Account confirmed"
}
POST/api/v1/identity/login

Authenticate an end user. Returns OIDC tokens, or an MFA challenge if multi-factor is required.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com",
  "password": "Temp#Pass2026"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/login' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com",
    "password": "Temp#Pass2026"
  }'

Response — success

{
  "status": "authenticated",
  "id_token": "eyJraWQiOi...",
  "access_token": "eyJraWQiOi...",
  "refresh_token": "eyJjdHkiOi...",
  "expires_in": 3600,
  "token_type": "Bearer",
  "user_sub": "a1b2c3d4-....",   // Cognito subject (same as the id_token 'sub' claim)
  "user_id": "a1b2c3d4-...."    // same as user_sub
}
User identifiers: user_sub and user_id are the Cognito subject (equal to the sub claim inside the id_token). Prefer email for management APIs. They are only present when status is authenticated.

Response — MFA required

{
  "status": "challenge",
  "challenge_name": "SOFTWARE_TOKEN_MFA",
  "session": "AYABeF..."
}
MFA challenge: when status is challenge or challenge_name is present, collect the user's TOTP code and call POST /api/v1/identity/mfa/respond with the returned session to finish the login and receive tokens.
POST/api/v1/identity/refresh

Exchange a refresh token for fresh access and ID tokens.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "refresh_token": "eyJjdHkiOi..."
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/refresh' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "refresh_token": "eyJjdHkiOi..."
  }'

Response

{
  "status": "authenticated",
  "id_token": "eyJraWQiOi...",
  "access_token": "eyJraWQiOi...",
  "refresh_token": "eyJjdHkiOi...",
  "expires_in": 3600,
  "token_type": "Bearer"
}
POST/api/v1/identity/logout

Globally sign the user out, revoking their refresh tokens.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/logout' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com"
  }'

Response

{
  "success": true,
  "message": "Logged out"
}

Password management

Reset a forgotten password via email verification code (sent by Cognito), or change the password when the user is already logged in. All three endpoints require the invoke scope.

POST/api/v1/identity/forgot-password

Request a password-reset verification code. Cognito emails the code to the user if the account exists. Does not change the password yet.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/forgot-password' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com"
  }'

Response (200)

{
  "success": true,
  "message": "If an account exists for this email, a password reset code has been sent."
}
Email delivery: the verification code is sent by AWS Cognito to the user's email — not by the Hikigai API directly. Follow with POST /api/v1/identity/reset-password once the user enters the code.
POST/api/v1/identity/reset-password

Set a new password using the verification code from the forgot-password email.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com",
  "code": "123456",
  "new_password": "NewTemp#Pass2026"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/reset-password' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com",
    "code": "123456",
    "new_password": "NewTemp#Pass2026"
  }'

Response (200)

{
  "success": true,
  "message": "Password reset successfully"
}
POST/api/v1/identity/change-password

Change password for a logged-in user who knows their current password. Requires the access_token from login.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "access_token": "eyJraWQiOi...",
  "current_password": "Temp#Pass2026",
  "new_password": "NewTemp#Pass2026"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/change-password' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "access_token": "eyJraWQiOi...",
    "current_password": "Temp#Pass2026",
    "new_password": "NewTemp#Pass2026"
  }'

Response (200)

{
  "success": true,
  "message": "Password changed successfully"
}

Multi-factor auth (TOTP)

Enroll a user's authenticator app, then require it at login. The associate step returns a secret you render as an otpauth:// QR code for Google Authenticator, Authy, etc.
Typical enroll flow: POST /identity/login (user password) → POST /identity/mfa/associate → render QR → POST /identity/mfa/verify → POST /identity/mfa/set-preference with enabled: true.
Admin disable: POST /api/v1/end-users/{email}/mfa/disable?app_id=… (manage scope) when the user cannot sign in to turn MFA off themselves.

POST/api/v1/identity/mfa/associate

Begin TOTP enrollment for a signed-in user. Returns the shared secret to display as a QR code.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "access_token": "eyJraWQiOi..."        // from login
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/mfa/associate' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "access_token": "eyJraWQiOi..."
  }'

Response

{
  "secret_code": "JBSWY3DPEHPK3PXP",
  "session": null
}
POST/api/v1/identity/mfa/verify

Verify the first TOTP code to finish enrollment.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "access_token": "eyJraWQiOi...",
  "code": "482913",
  "device_name": "iPhone Authenticator"   // optional
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/mfa/verify' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "access_token": "eyJraWQiOi...",
    "code": "482913",
    "device_name": "iPhone Authenticator"
  }'

Response

{
  "status": "SUCCESS"
}
POST/api/v1/identity/mfa/set-preference

Turn TOTP MFA on or off for a user.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "access_token": "eyJraWQiOi...",
  "enabled": true
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/mfa/set-preference' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "access_token": "eyJraWQiOi...",
    "enabled": true
  }'

Response

{
  "success": true,
  "message": "MFA preference updated"
}
POST/api/v1/identity/mfa/respond

Complete a login that returned an MFA challenge. Returns OIDC tokens.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com",
  "session": "AYABeF...",                 // from the login challenge
  "code": "482913"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/mfa/respond' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "email": "clinician@hospital.com",
    "session": "AYABeF...",
    "code": "482913"
  }'

Response

{
  "status": "authenticated",
  "id_token": "eyJraWQiOi...",
  "access_token": "eyJraWQiOi...",
  "refresh_token": "eyJjdHkiOi...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

QR badge login

Issue a per-user QR credential a clinician can scan to sign in without typing a password — ideal for shared workstations. Issue the badge once (admin), then exchange a scanned payload for tokens at the kiosk.

POST/api/v1/identity/apps/{app_id}/users/{email}/qr-login

Issue a QR badge credential for a user. No request body — email is in the path (URL-encode @ as %40). The payload is shown once — encode it into the badge.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1

cURL Example

curl --location --request POST 'https://backend.hikigaiplatform.io/api/v1/identity/apps/YOUR_APP_ID/users/clinician%40hospital.com/qr-login' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response (201)

Returns qr_payload and qr_code_png_base64. May also return 200 on re-issue.

{
  "credential_id": "qr-version-uuid",
  "end_user_id": "a1b2c3d4-....",
  "email": "clinician@hospital.com",
  "qr_payload": "HKG2....",
  "qr_code_png_base64": "iVBORw0KGgo..."
}
POST/api/v1/identity/qr-code/render

Render a qr_payload (or any text) as a PNG QR image. Returns image/png.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{ "data": "HKG2...." }

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/qr-code/render' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{ "data": "HKG2...." }' \
  -o badge-qr.png

Response (200)

Binary PNG image (Content-Type: image/png).

POST/api/v1/identity/qr-login

Exchange a scanned QR payload for OIDC tokens.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "qr_payload": "HKG2...."
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/qr-login' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "qr_payload": "HKG2...."
  }'

Response

{
  "status": "authenticated",
  "id_token": "eyJraWQiOi...",
  "access_token": "eyJraWQiOi...",
  "refresh_token": "eyJjdHkiOi...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

End-user profile & role

Cognito (or GCP Identity Platform) is the source of truth for end-user profile data. Fetch or update role, name, metadata, phone, and active status. Email is immutable — it is the Cognito login identity.
Console path (by email): GET /api/v1/projects/{project_id}/apps/{app_id}/identity/users/{email} — full detail including live Cognito status. Auth: Bearer; permission apps:read.
SDK path: GET or PATCH /api/v1/end-users/{email_or_sub}?app_id=… —app_id is required. Path may be the user's email or Cognito sub. Auth: SDK headers; scopes read (GET) or manage (PATCH). Do not use POST /api/v1/end-users to create users — use POST /api/v1/identity/signup.

GET/api/v1/projects/{project_id}/apps/{app_id}/identity/users/{email}

Get full detail for one end user by email from Cognito/GCP (names, role, metadata) plus live pool status (MFA, enabled, email verified).

Request Headers

Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

Path Parameters

project_id, app_id — your project and app ids.
email — the end user's email (URL-encode @, e.g. test1%40gmail.com).

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/identity/users/test1%40gmail.com' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json'

Response

{
  "id": "a1b2c3d4-....",
  "email": "test1@gmail.com",
  "first_name": "Asha",
  "last_name": "Mehta",
  "role": "clinician",
  "is_active": true,
  "external_subject": "a1b2c3d4-....",
  "qr_issued": false,
  "created_at": "2026-06-11T12:00:00Z",
  "last_login_at": null,
  "metadata": { "doctorID": "6278383837" },
  "cognito": {
    "user_sub": "a1b2c3d4-....",
    "status": "CONFIRMED",
    "enabled": true,
    "email_verified": true,
    "mfa_enabled": false,
    "mfa_methods": [],
    "preferred_mfa": null,
    "created_at": "2026-06-11T12:00:00Z",
    "last_modified_at": "2026-06-12T08:30:00Z",
    "phone_number": "+14155552671",
    "metadata": { "doctorID": "6278383837" }
  }
}
Cognito-backed: top-level profile fields are read from the identity provider (given_name, family_name, custom:role, custom:metadata). The cognito object is live pool status. Phone from signup attributes appears under cognito.phone_number.
GET/api/v1/end-users/{email_or_sub}?app_id=

Get an end user's Cognito profile by email or Cognito sub (SDK path). Query param app_id is required.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1

Path & query

email_or_sub — user email or Cognito sub (URL-encode email).
app_id (query, required) — app whose Cognito/GCP pool to use.

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/end-users/clinician%40hospital.com?app_id=YOUR_APP_ID' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response (200)

{
  "id": "a1b2c3d4-....",
  "project_id": "proj_123",
  "first_name": "Asha",
  "last_name": "Mehta",
  "email": "clinician@hospital.com",
  "role": "clinician",
  "external_subject": "a1b2c3d4-....",
  "is_active": true,
  "qr_issued": false,
  "metadata": { "doctorID": "6278383837" },
  "created_at": "2026-06-11T12:00:00Z",
  "last_verified_at": null
}
PATCH/api/v1/end-users/{email_or_sub}?app_id=

Update an end user's Cognito profile. All body fields are optional. Email cannot be updated. Query param app_id is required.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Path & query

email_or_sub — user email or Cognito sub.
app_id (query, required).

Request Body

{
  "role": "doctor",                       // optional — Cognito custom:role
  "first_name": "Asha",                   // optional — given_name
  "last_name": "Mehta",                   // optional — family_name
  "phone_number": "+14155552671",         // optional — Cognito attribute (E.164); "" clears
  "metadata": { "department": "cardiology" },  // optional — replaces custom:metadata
  "is_active": true                       // optional — enable/disable in Cognito
}

cURL Example

curl --location --request PATCH 'https://backend.hikigaiplatform.io/api/v1/end-users/clinician%40hospital.com?app_id=YOUR_APP_ID' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "role": "doctor"
  }'

Response (200)

{
  "id": "a1b2c3d4-....",
  "project_id": "proj_123",
  "first_name": "Asha",
  "last_name": "Mehta",
  "email": "clinician@hospital.com",
  "role": "doctor",
  "external_subject": "a1b2c3d4-....",
  "is_active": true,
  "qr_issued": false,
  "metadata": null,
  "created_at": "2026-06-11T12:00:00Z",
  "last_verified_at": null
}
Immutable fields: email, external_subject, password, and ids cannot be changed here. Sending them returns 422. To change a login email, create a new account (or delete + recreate). Use the password endpoints to change passwords. phone_number updates the Cognito attribute (E.164, e.g. +14155552671); send an empty string to clear it.
POST/api/v1/end-users/{email_or_sub}/mfa/disable?app_id=

Admin-disable TOTP MFA for an end user (manage scope). Requires app_id query. Unlike POST /identity/mfa/set-preference, this does not require the user's access token.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Path & query

email_or_sub — user email or Cognito sub.
app_id (query, required).

cURL Example

curl --location --request POST 'https://backend.hikigaiplatform.io/api/v1/end-users/clinician%40hospital.com/mfa/disable?app_id=YOUR_APP_ID' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response (200)

{ "success": true, "message": "MFA disabled" }

QR & PIN credentials (Cognito-backed)

QR badges and PINs live on the app's Cognito user (custom attributes / QR v2), not a separate DB table. Base path: /api/v1/end-users with required app_id query (or use /identity/apps/{app_id}/users/{email}/qr-login and …/pin).
Auth: SDK headers (X-API-Key, X-Project-ID, User-Agent: hikigai-sdk/…).
Scopes: read to list; manage to issue, rotate, or revoke.
Verify at runtime: POST /api/v1/identity/verify-qr, verify-pin, verify-sso — each requires app_id in the body.

GET/api/v1/end-users/{email_or_sub}/credentials?app_id=

List synthesized credential metadata from Cognito (QR version id and/or pin). app_id query required.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1

Path & query

email_or_sub — user email or Cognito sub.
app_id (query, required).

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/end-users/clinician%40hospital.com/credentials?app_id=YOUR_APP_ID' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response

{
  "credentials": [
    {
      "id": "qr-version-uuid",
      "end_user_id": "a1b2c3d4-....",
      "credential_type": "qr",
      "status": "active",
      "issued_at": "2026-06-11T12:00:00Z",
      "expires_at": null,
      "last_used_at": null
    },
    {
      "id": "pin",
      "end_user_id": "a1b2c3d4-....",
      "credential_type": "pin",
      "status": "active",
      "issued_at": null,
      "expires_at": null,
      "last_used_at": null
    }
  ]
}
Where to get CREDENTIAL_ID: the id field on each object (QR version uuid, or pin). Also returned as credential_id from issue/rotate QR responses.
DELETE/api/v1/end-users/{email_or_sub}/credentials/{credential_id}?app_id=

Revoke a Cognito QR badge (rotates version) or clear PIN hash. app_id query required.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1

Path & query

email_or_sub — user email or Cognito sub.
credential_id — from list credentials (credentials[].id).
app_id (query, required).

cURL Example

curl --location --request DELETE 'https://backend.hikigaiplatform.io/api/v1/end-users/clinician%40hospital.com/credentials/CREDENTIAL_ID?app_id=YOUR_APP_ID' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response

{
  "success": true,
  "message": "Credential revoked"
}

Runtime verification (QR / PIN / SSO)

Apps verify clinicians through the platform against the app's Cognito pool. Every verify call requires app_id. Scope: invoke.

POST/api/v1/identity/verify-qr

Verify a scanned HKG2 QR badge against the app's Cognito pool.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "qr_payload": "HKG2...."
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/verify-qr' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{ "app_id": "app_123", "qr_payload": "HKG2...." }'

Response (200)

{
  "end_user_id": "a1b2c3d4-....",
  "project_id": "proj_123",
  "first_name": "Asha",
  "last_name": "Mehta",
  "role": "clinician",
  "method": "qr",
  "verified_at": "2026-06-12T08:30:00Z",
  "metadata": { "doctorID": "6278383837" }
}
POST/api/v1/identity/verify-pin

Verify an end user's PIN stored on Cognito (custom:pin_hash).

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "email": "clinician@hospital.com",
  "pin": "123456"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/verify-pin' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{ "app_id": "app_123", "email": "clinician@hospital.com", "pin": "123456" }'
POST/api/v1/identity/verify-sso

Verify an SSO id token against the app's Cognito pool.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "token": "eyJraWQiOi..."
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/verify-sso' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{ "app_id": "app_123", "token": "eyJraWQiOi..." }'

Single sign-on & social

Let users sign in with Google, Apple, Okta, Azure AD, SAML, or any OIDC provider. Configure the provider once, then run the standard authorize-redirect / code-exchange flow.

POST/api/v1/identity/apps/{app_id}/sso

Add or update an SSO / social provider on the app's pool.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "provider_name": "Google",
  "provider_type": "Google",
  "provider_details": {
    "client_id": "...",
    "client_secret": "...",
    "authorize_scopes": "openid email profile"
  },
  "callback_urls": ["https://yourapp.com/auth/callback"],
  "attribute_mapping": { "email": "email" },
  "scopes": ["openid", "email", "profile"]
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/apps/YOUR_APP_ID/sso' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "provider_name": "Google",
    "provider_type": "Google",
    "provider_details": {
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "authorize_scopes": "openid email profile"
    },
    "callback_urls": ["https://yourapp.com/auth/callback"],
    "attribute_mapping": { "email": "email" },
    "scopes": ["openid", "email", "profile"]
  }'

Response

{
  "app_id": "app_123",
  "project_id": "proj_123",
  "mode": "managed",
  "status": "active",
  "cognito_pool_id": "us-east-2_aBcD1234",
  "cognito_client_id": "1h57kf5a2...",
  "cognito_region": "us-east-2",
  "enabled_methods": ["password", "mfa", "qr", "sso"],
  "error_message": null
}
POST/api/v1/identity/sso/authorize-url

Build the hosted-UI authorize URL to redirect the user to for sign-in.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "redirect_uri": "https://yourapp.com/auth/callback",
  "provider": "Google",                    // optional
  "scopes": ["openid", "email", "profile"] // optional
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/sso/authorize-url' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "redirect_uri": "https://yourapp.com/auth/callback",
    "provider": "Google"
  }'

Response

{
  "authorize_url": "https://app-123.auth.us-east-2.amazoncognito.com/oauth2/authorize?..."
}
POST/api/v1/identity/sso/token

Exchange the authorization code from the callback for OIDC tokens.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "app_id": "app_123",
  "code": "auth-code-from-callback",
  "redirect_uri": "https://yourapp.com/auth/callback"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/sso/token' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "app_id": "app_123",
    "code": "auth-code-from-callback",
    "redirect_uri": "https://yourapp.com/auth/callback"
  }'

Response

{
  "status": "authenticated",
  "id_token": "eyJraWQiOi...",
  "access_token": "eyJraWQiOi...",
  "refresh_token": "eyJjdHkiOi...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Bring-your-own Cognito

To keep users in your own AWS account, create a cross-account IAM role from the template below, then enable the app with mode: "byo" and the role ARN.

GET/api/v1/identity/byo/setup

Get the CloudFormation template and the trust values (Hikigai account ID + ExternalId) to create the cross-account role.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/byo/setup' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1'

Response

{
  "hikigai_account_id": "439563881130",
  "external_id": "hikigai-app-123",
  "template": "AWSTemplateFormatVersion: '2010-09-09' ..."
}
POST/api/v1/identity/byo/test

Verify the platform can assume your cross-account role before enabling BYO.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
User-Agent: hikigai-sdk/0.0.1
Content-Type: application/json

Request Body

{
  "role_arn": "arn:aws:iam::123456789012:role/HikigaiIdentityProvisioningRole",
  "region": "us-east-2"                    // optional
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/identity/byo/test' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'User-Agent: hikigai-sdk/0.0.1' \
  --header 'Content-Type: application/json' \
  --data '{
    "role_arn": "arn:aws:iam::123456789012:role/HikigaiIdentityProvisioningRole",
    "region": "us-east-2"
  }'

Response

{
  "ok": true,
  "account_id": "123456789012",
  "error": null
}
Prefer an SDK? The same flow is available as client.identity in the Python and Node App SDKs.

Object Storage

Platform-managed object storage for deployed apps. Upload PDFs, images, audio, and other documents scoped to a project and app. Files are stored in a private AWS S3 bucket — they are never public. Access is via time-limited signed URLs generated by the platform API.

How signed URLs work: Upload may return a short-lived S3 URL for immediate use. When you call /storage/{object_id}/url, the API returns a Hikigai URL under /api/v1/storage/public/{object_id} with exp and sig query params. That URL redirects (302) to S3. Your app hits Hikigai first; file bytes still come from S3 until the link expires.

Base path: /api/v1/projects/{project_id}/apps/{app_id}/storage
Permissions: apps:read for list, get, stats, and signed URLs; apps:update for upload and delete.
Auth: API key (Authorization: Bearer hikigai_…) or session JWT.

POST/api/v1/projects/{project_id}/apps/{app_id}/storage/upload

Upload a file to platform storage. Returns object metadata and a 1-hour signed URL for immediate use.

Request Headers

Authorization: Bearer hikigai_your_api_key
Content-Type: multipart/form-data
X-Project-ID: YOUR_PROJECT_ID

Form Fields

FieldTypeDescription
filebinaryRequired. Max 100MB. PDF, images, audio, zip, etc.
metadataJSON stringOptional key-value metadata stored with the object. Common keys: description, agent_id, generated_by. Shown in the Developer Portal Documents tab.
ttlintegerOptional auto-expiry in seconds (omit = permanent)
filenamestringOptional display name override

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/storage/upload' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  --form 'file=@report.pdf' \
  --form 'metadata={"description":"Monthly report","agent_id":"pdf-generator"}' \
  --form 'ttl=86400'

Response (201)

{
  "object_id": "uuid",
  "object_key": "projects/{pid}/apps/{aid}/{object_id}/report.pdf",
  "original_filename": "report.pdf",
  "content_type": "application/pdf",
  "size_bytes": 245120,
  "signed_url": "https://hikigai-apps-storage-....s3.amazonaws.com/...?X-Amz-...",
  "expires_at": null,
  "created_at": "2026-06-11T12:00:00Z"
}
GET/api/v1/projects/{project_id}/apps/{app_id}/storage

List stored objects for an app (paginated).

Query Parameters

ParameterDefaultDescription
page1Page number
limit20Max 100 per page
statusactiveactive | expired | all
content_type—Filter by exact MIME type (e.g. application/pdf)
content_type_prefix—Filter by MIME prefix (e.g. image/ or audio/)
search—Filename search
sortcreated_atcreated_at | size_bytes | original_filename
orderdescasc | desc

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/storage?status=active&limit=20' \
  --header 'Authorization: Bearer hikigai_your_api_key'

Response

{
  "objects": [
    {
      "id": "uuid",
      "original_filename": "report.pdf",
      "content_type": "application/pdf",
      "size_bytes": 245120,
      "metadata": { "description": "Monthly report", "agent_id": "pdf-generator" },
      "status": "active",
      "upload_source": "sdk",
      "expires_at": null,
      "created_at": "2026-06-11T12:00:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20,
  "total_pages": 1
}
GET/api/v1/projects/{project_id}/apps/{app_id}/storage/{object_id}

Get metadata for a single stored object (does not return file bytes).

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/storage/OBJECT_ID' \
  --header 'Authorization: Bearer hikigai_your_api_key'
GET/api/v1/projects/{project_id}/apps/{app_id}/storage/{object_id}/url

Mint a time-limited download URL. Default TTL 1 hour; max 7 days. The returned URL is a Hikigai public link that redirects to S3.

Query Parameters

ttl=3600   # seconds (min 60, max 604800)

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/storage/OBJECT_ID/url?ttl=3600' \
  --header 'Authorization: Bearer hikigai_your_api_key'

Response

{
  "object_id": "uuid",
  "signed_url": "https://backend.hikigaiplatform.io/api/v1/storage/public/uuid?exp=...&sig=...",
  "expires_in": 3600,
  "expires_at": "2026-06-11T13:00:00Z"
}
Expiry: When the link expires, request a fresh URL from this endpoint. Opening the URL follows a 302 redirect to S3 for the file bytes.
GET/api/v1/projects/{project_id}/apps/{app_id}/storage/{object_id}/download

Redirect (302) to a fresh S3 signed URL — convenient for browser downloads.

Query Parameters

ttl=3600   # optional; seconds (min 60, max 604800). Defaults to platform signed-URL TTL.

cURL Example

curl -L --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/storage/OBJECT_ID/download' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  -o report.pdf
DELETE/api/v1/projects/{project_id}/apps/{app_id}/storage/{object_id}

Delete an object from S3 and mark it deleted in the platform database.

cURL Example

curl --location --request DELETE 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/storage/OBJECT_ID' \
  --header 'Authorization: Bearer hikigai_your_api_key'

Response (200)

{
  "success": true,
  "message": "Storage object deleted"
}
GET/api/v1/projects/{project_id}/apps/{app_id}/storage/stats

Storage usage stats for the app (file count, total size, PDF count, expiring soon).

Response

{
  "total_objects": 5,
  "active_objects": 4,
  "expired_objects": 1,
  "total_size_bytes": 12582912,
  "total_size_human": "12.0 MB",
  "pdf_count": 3,
  "expiring_soon_count": 0
}

upload_source on list/get responses is sdk when the request uses an API key (Authorization: Bearer hikigai_…), otherwise portal (browser session upload).

AppSDK & AgentSDK

Both SDKs expose the same client.storage client from a deployed app or agent:

from hikigai.appsdk import AppClient

client = AppClient(api_key="hikigai_...", project_id="...")

# Upload
with open("report.pdf", "rb") as f:
    result = client.storage.upload(
        app_id="your-app-id",
        file=f,
        filename="report.pdf",
        content_type="application/pdf",
        metadata={
            "description": "Monthly report",
            "agent_id": "pdf-generator",
        },
        ttl=86400,
    )
print(result.signed_url)  # short-lived download URL from upload

# Fresh signed URL later (Hikigai public link that redirects to S3)
url = client.storage.signed_url(app_id="your-app-id", object_id=result.object_id, ttl=3600)

# List, get, delete
objects = client.storage.list(app_id="your-app-id")
info = client.storage.get(app_id="your-app-id", object_id=result.object_id)
client.storage.delete(app_id="your-app-id", object_id=result.object_id)

Live Sessions

Bidirectional realtime audio over WebSocket (protocol v2). A typical live transcription client exchanges an API key for a platform token, mints a short-lived session, opens the WebSocket URL returned by mint, streams raw PCM microphone audio, and renders caption frames as they arrive.

How the two endpoints work together

Both calls are required, in order. Step 2 (/live/session) mints credentials over HTTPS. Step 3 (/live) is the WebSocket that carries audio and transcription — connect with the wss_url returned by mint.

CallPathRole
POST (HTTPS)/api/v1/agents/{agent_slug}/live/sessionMint wss_url, live_token, and session_id. This path is not a WebSocket endpoint.
WSwss_url from mint (ends in /live)Open the socket with the exact wss_url returned by mint. Authenticate with the first JSON frame using live_token.

Typical integration flow

Replace YOUR_PROJECT_ID, hikigai_your_api_key, and {agent_slug} with your values. Audio must be mono signed 16-bit LE PCM at 16 kHz (binary WebSocket frames, not base64 JSON).

# 1. Exchange API key → platform access_token (once at app start; cache until expiry)
POST /api/v1/auth/exchange
Headers: X-API-Key, X-Project-ID
Body: {}

# 2. Mint live session (every connect / reconnect)
POST /api/v1/agents/{agent_slug}/live/session
Headers: Authorization: Bearer <access_token>, X-API-Key, X-Project-ID
Body: { "session_id": "<fresh UUID>", "user_id": "app-transcriber" }
→ wss_url, live_token, session_id

# 3. Open WebSocket(wss_url)  // exact URL from step 2; path ends in /live
#    First frame:
{ "type": "auth", "live_token": "…", "session_id": "…", "protocol_version": 2 }

# 4. Start mic capture
{ "type": "start", "modality": "audio", "mime_type": "audio/pcm;rate=16000", "protocol_version": 2 }
→ stream binary PCM frames while recording

# 5. Receive captions (source: input_transcription); replace UI text on partials
{ "type": "data", "modality": "text", "content": "…", "source": "input_transcription", "finished": false|true }

# 6. Stop capture / disconnect
{ "type": "end" }
{ "type": "disconnect" }
→ close WebSocket

Step 1 — Auth exchange

Call once when the app starts and cache the token for mint requests. This is a platform access token for API calls, not an end-user login token. Send it as Authorization: Bearer <access_token> on the mint request in Step 2.

POST/api/v1/auth/exchange

Exchange your project API key for a platform access token.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
Content-Type: application/json

Request Body

{}

cURL Example

curl --location --request POST 'https://backend.hikigaiplatform.io/api/v1/auth/exchange' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'Content-Type: application/json' \
  --data '{}'

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scopes": ["deploy", "invoke", "read", "manage"],
  "project_id": "YOUR_PROJECT_ID"
}
Token lifetime: Exchange again when the token expires. Switching project or API key requires a fresh exchange. Scopes reflect the API key used.

Step 2 — Mint live session (/live/session)

HTTPS only — this call issues credentials; it does not open the stream. Mint before every connect and reconnect. The app creates a fresh UUID for session_id and an opaque user_id for your client. The response includes wss_url, live_token, and session_id, which you pass into Step 3.

POST/api/v1/agents/{agent_slug}/live/session

Mint wss_url and live_token for a live WebSocket session.

Request Headers

Authorization: Bearer <access_token from Step 1>
X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
Content-Type: application/json

Request Body

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "user_id": "app-transcriber"
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/{agent_slug}/live/session' \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "app-transcriber"
  }'

Response

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "wss_url": "wss://backend.hikigaiplatform.io/api/v1/agents/<agent_id>/live",
  "live_token": "<live_token>",
  "protocol_version": 2,
  "expires_in": 3600,
  "agent_id": "ag_…",
  "direct_connect": true
}
Next: wss_url is the Step 3 WebSocket address (path ends in /live). Use it unchanged — host and path are set by the platform (gateway or direct container).

Step 3 — Connect and authenticate (/live)

After Step 2 succeeds, open a WebSocket to the wss_url in that response. That URL is the live stream endpoint (path ends in /live). Both Step 2 and Step 3 are required: mint issues credentials; this socket carries audio and captions. Authenticate with the first JSON frame using the minted live_token and the same session_id. The project API key is not sent on this socket.

WS/api/v1/agents/{agent_id}/live

Live stream WebSocket. Connect using the exact wss_url from Step 2.

URL

Use wss_url from the Step 2 response as-is. Example shape (host may be the platform gateway or a direct agent endpoint):

wss://backend.hikigaiplatform.io/api/v1/agents/<agent_id>/live

Browser example (mint → /live → captions)

Assumes accessToken from Step 1. Mint over HTTPS, then open wss_url (the /live socket). Stream PCM from an AudioWorklet downsampled to 16 kHz; replace transcript text on partials.

const PROTOCOL_VERSION = 2;
const PCM_MIME = "audio/pcm;rate=16000";
let sessionId = crypto.randomUUID();

// Step 2 — mint (/live/session)
const mint = await fetch(`${baseUrl}/api/v1/agents/${agentSlug}/live/session`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "X-API-Key": apiKey,
    "X-Project-ID": projectId,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ session_id: sessionId, user_id: "browser-transcriber" }),
});
const { wss_url, live_token, session_id } = await mint.json();
sessionId = session_id || sessionId;

// Step 3 — connect + auth (/live via wss_url)
const ws = new WebSocket(wss_url);
ws.binaryType = "arraybuffer";
ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "auth",
    live_token,
    session_id: sessionId,
    protocol_version: PROTOCOL_VERSION,
  }));
};

// Step 4 — after getUserMedia + AudioWorklet → 16 kHz PCM
ws.send(JSON.stringify({
  type: "start",
  modality: "audio",
  mime_type: PCM_MIME,
  protocol_version: PROTOCOL_VERSION,
}));
// workletPort.onmessage = (e) => ws.send(e.data);  // binary PCM frames

// Step 5 — captions
ws.onmessage = (event) => {
  if (typeof event.data !== "string") return;
  const msg = JSON.parse(event.data);
  if (msg.type === "data" && msg.modality === "text" && msg.content) {
    if (msg.source === "input_transcription" || !msg.source) {
      transcriptEl.textContent = msg.content; // replace on partials
    }
  }
  if (msg.type === "error") {
    console.error(msg.message);
    ws.close();
  }
};

// Step 6
ws.send(JSON.stringify({ type: "end" }));
ws.send(JSON.stringify({ type: "disconnect" }));
ws.close();

First frame — auth

{
  "type": "auth",
  "live_token": "<live_token from Step 2>",
  "session_id": "<session_id from Step 2>",
  "protocol_version": 2
}

Step 4 — Stream audio

When capture starts, send a start frame, then continuous binary PCM. When capture stops, send end. Resample microphone input to mono PCM16 at 16 kHz before sending (browsers often capture at 44.1/48 kHz).

{ "type": "start", "modality": "audio", "mime_type": "audio/pcm;rate=16000", "protocol_version": 2 }

// binary WebSocket frames: raw PCM16 mono @ 16 kHz
// …

{ "type": "end" }
  • Optional: send one short silent PCM frame (~40 ms) immediately after start to prime the session.
  • While paused: stop microphone PCM, keep the socket open, and send silent PCM periodically (for example every 15 s) to keep the session alive.

Step 5 — Handle transcription frames

Captions arrive as text data frames with source: "input_transcription". Transcription often emits data without a preceding server start. Silence may produce no frames.

  • On each data with non-empty content, update the current pending caption.
  • Partials are cumulative: replace the pending line rather than appending.
  • When finished is true (or the server sends end), commit the pending line.
  • An error frame ends the session; disconnect and stop reconnecting for that attempt.
{ "type": "data", "modality": "text", "content": "hello how are", "source": "input_transcription", "partial": true, "finished": false }
{ "type": "data", "modality": "text", "content": "hello how are you", "source": "input_transcription", "partial": false, "finished": true }
{ "type": "end" }
{ "type": "error", "message": "…" }

Step 6 — Close

Stop capture with end, optionally send disconnect, then close the WebSocket. Closing the socket alone is sufficient; sending both frames before close is recommended.

{ "type": "end" }
{ "type": "disconnect" }
// then ws.close()

Text turns

Non-transcription live agents can send text turns on the same socket after auth:

{ "type": "start", "modality": "text", "protocol_version": 2 }
{ "type": "data", "modality": "text", "content": "Summarize the last turn." }
{ "type": "end" }

Troubleshooting

SymptomLikely cause
WS closes with 1008 / auth failureMint skipped, API key sent on the socket, or live_token / session_id mismatched.
404 / failed WebSocket upgradeSocket URL was constructed manually instead of using wss_url, or /live/session was opened as a WebSocket.
No captions after N msIncorrect URL, base64 JSON audio instead of binary PCM, wrong sample rate, waiting only for a server start, or silence (no transcription emitted).
Garbled or empty transcriptMicrophone not resampled to 16 kHz mono PCM; verify mime_type on start.
Agent config: Deploy-time input_modality / output_modality (and optional vad) configure the live model. Transcription agents typically use audio in → text out.

Speech

Real-time speech-to-speech translation and speech-to-text transcription over dedicated WebSockets. These are gateway-native endpoints, not agents — there is nothing to deploy, and they do not appear in the model registry.

Team organizations only. Speech runs on your own Google Cloud project via BYOC, so your organization is billed directly and no platform key is used as a fallback. A personal organization, or a team without GCP connected, gets credentials_missing. Connect GCP under Cloud accounts.

Audio format

Input is raw little-endian 16-bit mono PCM at 16 kHz. Translate output is the same at 24 kHz. Chunks of about 100 ms are the recommended balance of throughput and latency. Both SDKs convert from μ-law, Float32, and other sample rates for you.

Step 1 — Mint a speech token

POST/api/v1/speech/tokens

Exchange a project credential for a short-lived, browser-safe speech pass.

Call this from your backend so your API key never reaches an untrusted client. Credentials and entitlement are validated here, so a misconfigured organization fails with an actionable error rather than a socket that dies on first audio.

Request

{
  "endpoint": "translate",
  "config": {
    "target_language_code": "es",
    "echo_target_language": true,
    "input_transcription": true,
    "output_transcription": true
  },
  "locked": false,
  "ttl_seconds": 600
}

Set locked: true to forbid configure frames, pinning the session to this exact config — useful for a kiosk or a phone line.

Response

{
  "speech_token": "<jwt>",
  "expires_in": 600,
  "session_id": "spch_4f1c9a2b8e0d7c65",
  "endpoint": "translate",
  "config": { "target_language_code": "es", ... },
  "locked": false
}
GET/api/v1/speech/languages

Supported BCP-47 codes per endpoint. Unauthenticated.

Translate takes bare language subtags (es, pt-BR); transcribe takes full locales (es-ES, en-US). Sending the wrong shape returns unsupported_language with a suggestion.

{
  "translate":  { "field": "target_language_code", "codes": ["af", "ak", ...] },
  "transcribe": { "field": "language_codes",       "codes": ["af-ZA", "am-ET", ...] }
}

Step 2 — Connect

Both endpoints speak the same protocol: JSON control frames plus raw binary PCM, identical in grammar to the live agent tunnel. The first frame must be auth, within 10 seconds.

Authentication

Any one of three credentials:

{"type": "auth", "speech_token": "<from step 1>"}
{"type": "auth", "api_key": "hikigai_your_project_api_key"}
{"type": "auth", "access_token": "YOUR_JWT", "project_id": "YOUR_PROJECT_ID"}

Server frames

FrameMeaning
readySetup acknowledged upstream. Carries session_id, model, resolved config, audio formats, and a resume_token. Wait for this before sending audio — the model discards anything that arrives earlier.
transcriptsource is input or output; final distinguishes interim from settled text.
binaryTranslated audio, 24 kHz PCM. Translate only.
reconfiguredA configure was applied; echoes the new config.
turn_complete / interruptedTurn boundaries.
errorTyped code plus a human-readable message.
WS/api/v1/speech/translate

Speech-to-speech translation across 80+ languages. Emits translated audio and transcripts.

Model

gemini-3.5-live-translate-preview

Configuration

FieldDefaultDescription
target_language_code"en"BCP-47 output language. One per session.
echo_target_languagetrueWhen the speaker already uses the target language, echo them through rather than staying silent.
input_transcriptiontrueTranscribe the source audio.
output_transcriptiontrueTranscribe the translated audio (subtitles).

The last three defaults differ from Google's, which default echo off and both transcriptions off.

Full exchange

--> {"type": "auth", "speech_token": "<token>"}
--> {"type": "start", "modality": "audio", "mime_type": "audio/pcm;rate=16000"}
<-- {"type": "ready", "session_id": "spch_...", "model": "gemini-3.5-live-translate-preview",
     "config": {"target_language_code": "es", "echo_target_language": true, ...},
     "audio": {"input":  {"encoding": "pcm_s16le", "sample_rate": 16000, "channels": 1},
               "output": {"encoding": "pcm_s16le", "sample_rate": 24000, "channels": 1}}}

--> <binary PCM @ 16 kHz>
<-- {"type": "transcript", "source": "input",  "text": "Good morning", "language_code": "en", "final": true}
<-- <binary PCM @ 24 kHz>
<-- {"type": "transcript", "source": "output", "text": "Buenos días",  "final": true}
<-- {"type": "turn_complete"}

Changing language mid-session

Send configure at any time. The gateway brings up a replacement model session before retiring the old one, so audio keeps flowing and the listener hears no gap. No reconnect, no new token.

--> {"type": "configure", "config": {"target_language_code": "fr"}}
<-- {"type": "reconfigured", "changed": true, "config": {"target_language_code": "fr", ...}}

Rejected if the token was minted with locked: true.

WS/api/v1/speech/transcribe

Speech-to-text across 85+ locales, with interim results as the speaker talks.

Model

gemini-3.5-transcribe-live-preview

Configuration

FieldDefaultDescription
language_codesauto-detectUp to 8 full locales (en-US, not en) to bias recognition.
custom_vocabulary—Up to 1000 names, jargon, or product terms to bias towards.

Full exchange

--> {"type": "auth", "speech_token": "<token>"}
--> {"type": "start", "modality": "audio", "mime_type": "audio/pcm;rate=16000"}
<-- {"type": "ready", "session_id": "spch_...", "model": "gemini-3.5-transcribe-live-preview"}

--> <binary PCM @ 16 kHz>
<-- {"type": "transcript", "source": "input", "text": "the quarterly",       "language_code": "en-US", "final": false}
<-- {"type": "transcript", "source": "input", "text": "the quarterly report", "language_code": "en-US", "final": true}
Long sessions are handled for you. Google caps a transcription session at 10 minutes. The gateway rotates the upstream at 9, bringing the replacement up before dropping the old one, so a multi-hour session needs no client-side handling.

Errors

CodeCause and fix
credentials_missingPersonal organization, or no GCP connected. Speech is team-only and bills to your own cloud project.
unsupported_languageWrong code shape for the endpoint. The message suggests the right one.
stream_cap_exceededOrganization is at its concurrent-session cap (50 by default, raisable by an admin). Reconnecting with the same resume_token reclaims the original slot rather than competing for a new one.
config_invalidA config field failed validation; the message names it.
forbiddenA configure was sent on a token minted with locked: true.
session_duration_exceededSession passed the 1 hour wall-clock ceiling.
upstream_unavailableThe model was unreachable. Retry with backoff.
Try it first: the Voice Lab tab in your project console runs both endpoints from the browser with every config field exposed, and generates the matching Python, Node, or raw-WebSocket code.

Rooms

Project-scoped realtime collaboration over a single WebSocket endpoint WS /api/v1/rooms/ws. Two complementary protocols share that socket:

  • WebSocket rooms — JSON pub/sub (join, publish, presence, replay). Use for text events like live transcripts.
  • WebRTC signalling — rtc_* frames for mesh A/V calls. The platform relays SDP/ICE only; media is peer-to-peer.

Rooms are namespaced server-side as {project_id}:{room} so members of one project never see another project's traffic. Use a project-scoped API key, a JWT from POST /api/v1/auth/exchange, or a short-lived room_token.

Room naming, creation & lifecycle

There is no separate "create room" API. You choose a room name in your application and send a join frame — the platform creates the room implicitly the first time anyone joins that name within your project.

QuestionAnswer
Who picks the room name?Your application — pass any valid string in join (e.g. consultation-42).
When is a room created?On the first join to that name. No pre-registration.
How do two apps find each other?Agree on the same room string outside the protocol (config, URL, DB). The platform does not auto-route clients.
When does a room go away?When the last member leaves (implicit GC). Leaving also drops any WebRTC peer on that connection.
Cross-project isolation: Two projects can both use consultation-42 — they are separate rooms keyed as {project_id}:consultation-42.

WebSocket rooms (pub/sub)

JSON event fan-out for text / structured data. Typical flow: App A publishes transcript lines; App B only joins and displays them.

Example flow — publisher → subscriber

Publisher (App A)                      Subscriber (App B)
─────────────────                      ──────────────────
1. connect WS                          (idle)
2. auth (api_key | access_token)       (idle)
3. ← ready                             (idle)
4. join "consultation-42"       ←── creates room if new
5. ← joined
6. publish transcript.*                1. connect WS
                                       2. auth (same project)
                                       3. ← ready
                                       4. join "consultation-42"
                                       5. ← joined
                                       6. ← message { event, data, seq }

Both clients must use the same project and same room name. Order does not matter — whichever joins first creates the room.

WS/api/v1/rooms/ws

Rooms WebSocket protocol. First frame must be auth; then join, publish, and receive broadcasts. WebRTC rtc_* frames use this same socket (see WebRTC section below).

Connection URL

wss://backend.hikigaiplatform.io/api/v1/rooms/ws

Protocol summary

DirectionFrames
Client → serverauth (first only) → join → publish / leave / who / ping / refresh. Optional: kick / close_room (manage) / disconnect.
Server → clientready → joined / left → message, presence, who, pong, refreshed, or error
Room names1–128 characters: letters, digits, _, ., :, -. Must start with a letter or digit.

Step 1 — Connect and authenticate

{"type": "auth", "api_key": "hikigai_your_project_api_key"}

Or JWT from auth/exchange:

{"type": "auth", "access_token": "YOUR_JWT_TOKEN", "project_id": "YOUR_PROJECT_ID"}

Or minted room token:

{"type": "auth", "room_token": "YOUR_ROOM_TOKEN"}

Success reply (API keys include rtc):

{"type": "ready", "connection_id": "conn_a1b2c3d4e5f6", "project_id": "YOUR_PROJECT_ID", "capabilities": ["manage", "publish", "subscribe", "rtc"]}

Step 2 — Join a room

{"type": "join", "room": "consultation-42"}

Rejoin with replay cursor:

{"type": "join", "room": "consultation-42", "last_seq": 12}
{"type": "joined", "room": "consultation-42", "capabilities": ["manage", "publish", "subscribe", "rtc"], "seq": 0, "members": [{"id": "conn_a1b2c3d4e5f6", "principal": "..."}]}

Step 3 — Publish

{"type": "publish", "room": "consultation-42", "event": "transcript.partial", "data": {"text": "Patient reports mild headache", "turn": 1, "finished": false}}
{"type": "publish", "room": "consultation-42", "event": "transcript.final", "data": {"text": "Patient reports mild headache since yesterday.", "turn": 1, "finished": true}}

Set echo: true to also receive your own publish (default false).

Step 4 — Receive

{"type": "message", "room": "consultation-42", "event": "transcript.partial", "data": {"text": "...", "turn": 1, "finished": false}, "seq": 3, "sender": "conn_xyz", "ts": "2026-07-29T12:00:00.000Z"}

Step 5 — Leave / disconnect

{"type": "leave", "room": "consultation-42"}
{"type": "disconnect"}

End-to-end example (wscat)

# Tab 1 — publisher
wscat -c "wss://backend.hikigaiplatform.io/api/v1/rooms/ws"
{"type": "auth", "api_key": "hikigai_your_project_api_key"}
{"type": "join", "room": "consultation-42"}
{"type": "publish", "room": "consultation-42", "event": "transcript.final", "data": {"text": "Hello", "turn": 1, "finished": true}}

# Tab 2 — subscriber (same key + room)
{"type": "auth", "api_key": "hikigai_your_project_api_key"}
{"type": "join", "room": "consultation-42"}
# ← message { "event": "transcript.final", "data": { "text": "Hello", ... } }

JavaScript client pattern

const ws = new WebSocket("wss://backend.hikigaiplatform.io/api/v1/rooms/ws");

ws.onopen = () => {
  ws.send(JSON.stringify({ type: "auth", api_key: "hikigai_your_project_api_key" }));
};

ws.onmessage = (ev) => {
  const frame = JSON.parse(ev.data);
  if (frame.type === "ready") {
    ws.send(JSON.stringify({ type: "join", room: "consultation-42" }));
  } else if (frame.type === "message") {
    console.log(frame.event, frame.data);
  }
};

function publish(event, data) {
  ws.send(JSON.stringify({ type: "publish", room: "consultation-42", event, data }));
}

Error codes (WebSocket rooms)

CodeMeaning
unauthorizedInvalid credentials, auth timeout, or project mismatch
forbiddenNot allowed to join or publish
bad_frameMalformed JSON or invalid room name
payload_too_largeExceeds size limit (128 KB default)
rate_limitedJoin or publish rate exceeded
room_fullMember cap reached
cursor_expiredReplay buffer no longer covers last_seq
Server push: Job events (e.g. job.completed) fan out to the project notifications room automatically.
POST/api/v1/rooms/tokens

Mint a short-lived room token for end-user clients (scoped room patterns and capabilities).

Request Headers

X-API-Key: hikigai_your_project_api_key
X-Project-ID: YOUR_PROJECT_ID
Content-Type: application/json

Request Body

{
  "rooms": ["consultation-*"],
  "capabilities": ["subscribe", "publish"],
  "principal": "end-user-123",
  "ttl_seconds": 600
}

For WebRTC, add "rtc" to capabilities. API-key / JWT principals already have rtc.

POST/api/v1/rooms/{room}/publish

Server-side publish without holding a WebSocket — for backends, workers, or services.

Request Headers

X-API-Key: hikigai_your_project_api_key
X-Project-ID: YOUR_PROJECT_ID
Content-Type: application/json

Request Body

{
  "event": "appointment.reminder",
  "data": {"patient_id": "p-001", "slot": "2026-07-30T09:00:00Z"}
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/rooms/consultation-42/publish' \
  --header 'X-API-Key: hikigai_your_project_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'Content-Type: application/json' \
  --data '{"event": "note.updated", "data": {"text": "Labs ordered"}}'
GET/api/v1/rooms/stats

Hub statistics including backplane health (project-scoped API key required).

Request Headers

X-API-Key: hikigai_your_project_api_key
X-Project-ID: YOUR_PROJECT_ID

WebRTC signalling (mesh)

Peer-to-peer audio/video calls negotiated over the same /api/v1/rooms/ws socket. The platform is a signalling authority only— it relays SDP and ICE. Media never traverses Hikigai services.

Structure — WebSocket vs WebRTC

WebRTC rooms are a layer on rooms WebSocket, not a replacement for it. The WebSocket is always required for setup; WebRTC carries media after peers connect.

Rooms WebSocket  (/api/v1/rooms/ws)          ← always needed
  • auth / join / leave
  • publish / message     (JSON events, e.g. transcript text)
  • rtc_* signalling      (SDP + ICE — how the call is set up)
  Hikigai sees these frames. It does NOT see mic/camera bytes.

WebRTC  (browser ↔ browser)                   ← after rtc_join succeeds
  • audio / video tracks  (P2P)
  • optional DataChannel  (P2P text via sendText)
  Media never goes through Hikigai.
LayerCarriesThrough Hikigai?
Rooms WebSocketAuth, membership, publish, rtc_*Yes — always
WebRTC mediaMic / cameraNo (P2P after connect)
WebRTC DataChannelsendText / sendDataNo (P2P after connect)

Why WebSocket is required: peers must exchange SDP/ICE before a call exists. That cannot use WebRTC itself (chicken-and-egg). Hikigai’s rooms socket is that signalling channel. After the call is up, optional DataChannel text can stay P2P.

An RTC peer must already be a room member: join first, then rtc_join. Requires the rtc capability (included for API keys / JWTs; must be requested on room_token).

Example flow — two peers

Peer A (first)                         Peer B (second)
──────────────                         ───────────────
1. WS connect + auth                   (later)
2. join "call-1"
3. rtc_join "call-1"
4. ← rtc_joined { peers: [], ice_servers }
                                       1. WS connect + auth
                                       2. join "call-1"
                                       3. rtc_join "call-1"
                                       4. ← rtc_joined { peers: [A], initiator: false }
5. ← rtc_peer_joined { id: B, initiator: true }
6. createOffer → rtc_offer → B
                                       5. ← rtc_offer
                                       6. createAnswer → rtc_answer → A
7. ← rtc_answer
8. ↔ rtc_candidate (ICE trickle)       ↔ rtc_candidate
9. P2P media connected                 P2P media connected

The server tells the existing peer to create the offer (initiator: true). The newcomer waits. Each pair also gets a polite flag for perfect-negotiation glare recovery.

Client → server frames

FrameFieldsPurpose
rtc_joinroom, mode?, agent_id?, metadata?Announce as an RTC peer (idempotent)
rtc_leaveroomLeave the call; stay in the room
rtc_offer / rtc_answerroom, target, sdpDirected SDP (exactly one peer)
rtc_candidateroom, target, candidateICE trickle; empty candidate = end-of-candidates
rtc_whoroomList current RTC peers

Server → client frames

FrameCarries
rtc_joinedpeer_id, session_id, mode, peers[], ice_servers[], max_peers
rtc_peer_joinedid, initiator, polite, metadata
rtc_peer_leftid, reason
rtc_offer / rtc_answer / rtc_candidateRelayed to target only, plus sender

JavaScript example (signalling + RTCPeerConnection)

const ws = new WebSocket("wss://backend.hikigaiplatform.io/api/v1/rooms/ws");
const pcs = new Map();
let iceServers = [];

ws.onopen = () => ws.send(JSON.stringify({ type: "auth", api_key: "hikigai_your_project_api_key" }));

ws.onmessage = async (ev) => {
  const f = JSON.parse(ev.data);
  if (f.type === "ready") {
    ws.send(JSON.stringify({ type: "join", room: "call-1" }));
  } else if (f.type === "joined") {
    ws.send(JSON.stringify({ type: "rtc_join", room: "call-1", metadata: { display: "Ada" } }));
  } else if (f.type === "rtc_joined") {
    iceServers = f.ice_servers || [];
  } else if (f.type === "rtc_peer_joined" && f.initiator) {
    const pc = ensurePc(f.id);
    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);
    ws.send(JSON.stringify({ type: "rtc_offer", room: "call-1", target: f.id, sdp: offer.sdp }));
  } else if (f.type === "rtc_offer") {
    const pc = ensurePc(f.sender);
    await pc.setRemoteDescription({ type: "offer", sdp: f.sdp });
    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);
    ws.send(JSON.stringify({ type: "rtc_answer", room: "call-1", target: f.sender, sdp: answer.sdp }));
  } else if (f.type === "rtc_answer") {
    await pcs.get(f.sender)?.setRemoteDescription({ type: "answer", sdp: f.sdp });
  } else if (f.type === "rtc_candidate" && f.candidate?.candidate) {
    await pcs.get(f.sender)?.addIceCandidate(f.candidate);
  }
};

function ensurePc(peerId) {
  if (pcs.has(peerId)) return pcs.get(peerId);
  const pc = new RTCPeerConnection({ iceServers });
  pc.onicecandidate = (e) => {
    if (!e.candidate) return;
    ws.send(JSON.stringify({
      type: "rtc_candidate", room: "call-1", target: peerId, candidate: e.candidate.toJSON()
    }));
  };
  pcs.set(peerId, pc);
  return pc;
}

Browser helper — WebRtcRoom

The console ships a mesh client at frontend/lib/webrtc-room.ts(and useWebRtcRoom). It owns signalling + RTCPeerConnections; you attach streams and optionally send text.

import { WebRtcRoom } from "@/lib/webrtc-room";

const room = new WebRtcRoom({
  room: "call-1",
  auth: { roomToken },
  remoteAudioEnabled: true,          // false = join muted on remote playback
  onLocalStream: (stream) => { /* <video>/<audio muted> */ },
  onRemoteStream: (peerId, stream) => { /* attach */ },
  onMessage: (frame) => { /* rooms publish fan-out */ },
  onDataMessage: (peerId, msg) => { /* P2P DataChannel */ },
});

await room.start({ audio: true, video: true });  // or start(false) receive-only

room.setMicrophoneEnabled(false);
room.setCameraEnabled(true);
room.setRemoteAudioEnabled(false);   // mute remote tracks, stay in call

// Text — rooms WebSocket (any member, even without rtc_join on the other side):
room.publish("note", { text: "hello" });

// Text — RTCDataChannel (connected RTC peers only; not rooms publish):
room.sendText("hello");
room.sendData("chat", { text: "hi" });

await room.stop();

publish / onMessage = rooms WebSocket.sendText / sendData / onDataMessage = peer DataChannel. Signalling always uses the rooms socket.

Combining WebRTC + live transcription

Media stays P2P. For ASR, stream mic PCM to a live agent session in parallel, then publish transcript events into the same room so text-only subscribers see them:

1. App A: rooms auth → join → rtc_join (optional agent_id) + live-transcription-agent PCM
2. App A: on ASR text → publish { event: "transcript.final", data: { text, turn } }
3. App B: rooms auth → join only (no rtc_join) → render message frames as text

Pass agent_id on rtc_join to also emit stream.started / stream.stopped with transport="webrtc" on the platform event bus.

WebRTC error codes

CodeMeaning
rtc_disabledWebRTC rooms disabled or SFU mode without config
rtc_not_joinedSent signalling before rtc_join
rtc_room_fullPeer cap reached (default 6 for mesh)
peer_not_foundDirected frame target is gone
forbiddenMissing rtc capability or not a room member
SDK: Use RoomSession in AgentSDK / AppSDK (Python) or @hikigai/agent-sdk / @hikigai/app-sdk (Node) for signalling helpers. Browsers can drive RTCPeerConnection from these frames or use the platform WebRtcRoom helper.

Multi-Cloud

Catalog-driven deployment targets for agents, apps, and MCP servers on GCP or AWS. Optional BYOC credentials are managed under /api/v1/cloud/credentials.

GET/api/v1/cloud/catalog

List providers, services, regions, zones, and per-service JSON Schema. Optional workload filter: app | agent | mcp.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/cloud/catalog?workload=agent' \
  --header 'X-API-Key: hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID'
POST/api/v1/cloud/targets/validate

Dry-run a deployment target (service, region, config) before deploy.

Request Body

{
  "workload": "app",
  "service_id": "gcp-cloud-run",
  "region": "us-central1",
  "config": {"cpu": "1", "memory": "512Mi"}
}

Webhooks (Event Bus)

Project-scoped webhook subscriptions for platform CloudEvents (agent deploy/delete, jobs, invocations, storage). Deliveries are HMAC-SHA256 signed.

GET/api/v1/webhooks

List webhook subscriptions for the project.

Request Headers

X-API-Key: hikigai_your_api_key
X-Project-ID: YOUR_PROJECT_ID
POST/api/v1/webhooks

Create a webhook subscription. The signing secret is returned once.

Request Body

{
  "url": "https://ci.example.com/hooks/hikigai",
  "event_types": ["agent.deployed", "agent.deleted"],
  "description": "deploy notifications"
}
POST/api/v1/webhooks/{id}/test

Send a synthetic webhook.test ping to verify the endpoint.

Also supports rotate-secret, deliveries list, and redelivery.

Even Realities G2 Glasses

Architecture

Even Realities G2 glasses have a 640×350 green monochrome display, mic, and touch temples — but no browser. The Even phone app loads your page in a WebView and drives the lenses over Bluetooth via @evenrealities/even_hub_sdk. Hikigai deploys that page like any other app (framework even-g2), builds the Even Hub .ehpk package, and exposes console APIs for info, QR testing, package download, and manifest validation.

  • Dev mode: scan the QR (live URL) in the Even app — no store review.
  • Even Hub store: download the .ehpk and upload it manually in the Even Hub portal (no public upload API).
  • Agent calls: keep the API key on a server proxy (see templates/even-g2). Use normal agent invoke — not a separate glasses runtime API.
  • Voice: agent must allow audio in its input modalities; list/text questions work with any text agent.

Proxy environment (your app server)

Not console API fields — these are what the deployed G2 proxy reads:

  • HIKIGAI_API_KEY / HIKIGAI_PROJECT_ID — injected on Hikigai deploy; set locally in .env.
  • HIKIGAI_AGENT_ID — required; set under Environment in the console (slug or id).
  • HIKIGAI_PLATFORM_URL (optional) — API base URL override.
  • GLASSES_PAGE_CHARS (optional) — page size for answers (starter default 420).

app.json: name ≤ 20, tagline ≤ 50; unique reverse-DNS package_id; typical permissions network + g2-microphone. Starter: templates/even-g2 (check / qr / pack).

Base path: /api/v1/projects/{project_id}/apps/{app_id}/even-g2
Permission: apps:read on all four endpoints.
Auth: project session JWT or API key with project scope (same as other app routes).
Detection: framework is even-g2 when the repo has both app.json and @evenrealities/even_hub_sdk. SDK helpers: Node AppSDK · Python AppSDK.

GET/api/v1/projects/{project_id}/apps/{app_id}/even-g2

Console panel payload: whether the app is even-g2, live URL, QR payload, validated manifest, package state, and Even Hub package/portal links.

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/even-g2' \
  --header 'Authorization: Bearer hikigai_your_api_key'

Response (200)

{
  "app_id": "app_...",
  "enabled": true,
  "framework": "even-g2",
  "live_url": "https://your-app.example.com",
  "dev_mode_qr_payload": "https://your-app.example.com",
  "manifest": {
    "package_id": "ai.hikigai.demo",
    "edition": "g2",
    "name": "Demo",
    "version": "0.1.0",
    "min_sdk_version": "0.0.15",
    "entrypoint": "index.html",
    "permissions": [{ "name": "network", "desc": "...", "whitelist": [] }],
    "supported_languages": ["en"]
  },
  "deployment_id": "dep_...",
  "deployment_status": "active",
  "package_available": true,
  "package_size_bytes": 184320,
  "package_filename": "ai.hikigai.demo-0.1.0.ehpk",
  "package_error": null,
  "cli_version": "0.1.14",
  "portal_url": "https://hub.evenrealities.com",
  "docs_url": "https://hub.evenrealities.com/docs/get-started/overview",
  "sdk_package": "@evenrealities/even_hub_sdk",
  "cli_package": "@evenrealities/evenhub-cli",
  "simulator_package": "@evenrealities/evenhub-simulator"
}

enabled is true only when framework == "even-g2".dev_mode_qr_payload is the live URL string (same as evenhub qr --url). Package fields come from artifacts.even_g2 on the latest active deployment.

GET/api/v1/projects/{project_id}/apps/{app_id}/even-g2/qr.png

Dev-mode QR as PNG. Scanning it in the Even phone app opens the live URL on the glasses.

Query Parameters

ParamTypeDescription
scaleint 2–20Pixel scale for the QR modules. Default 8.

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/even-g2/qr.png?scale=8' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  --output even-g2-dev-mode.png

Response

200 — image/png, Cache-Control: no-store.

409 — app has no live URL yet (deploy first). 404 — app not found.

GET/api/v1/projects/{project_id}/apps/{app_id}/even-g2/package

Stream the packed .ehpk for manual upload in the Even Hub developer portal. The backend fetches it from the live host under an unguessable path.

Query Parameters

ParamTypeDescription
deployment_idstringOptional. Defaults to the latest active deployment with an even_g2 artifact.

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/even-g2/package' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  --output ai.hikigai.demo-0.1.0.ehpk

Response

200 — application/octet-stream with Content-Disposition: attachment; filename="…ehpk".

400 — not an even-g2 app. 404 — no package built yet.413 — over size cap. 502 — live host did not return the package.

POST/api/v1/projects/{project_id}/apps/{app_id}/even-g2/validate-manifest

Validate a parsed app.json with the same rules the deploy pipeline (and Even Hub CLI) apply. Always returns 200; check valid.

Request Body

{
  "manifest": {
    "package_id": "ai.hikigai.demo",
    "edition": "g2",
    "name": "Demo",
    "version": "0.1.0",
    "min_sdk_version": "0.0.15",
    "entrypoint": "index.html",
    "permissions": [
      { "name": "network", "desc": "Talk to your Hikigai agent", "whitelist": [] },
      { "name": "g2-microphone", "desc": "Capture voice questions" }
    ],
    "supported_languages": ["en"],
    "tagline": "Ask an agent from your glasses",
    "description": "Optional longer description (max 1024 chars)."
  }
}

cURL Example

curl --location 'https://backend.hikigaiplatform.io/api/v1/projects/YOUR_PROJECT_ID/apps/YOUR_APP_ID/even-g2/validate-manifest' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  --header 'Content-Type: application/json' \
  --data @manifest-body.json

Response (200)

{ "valid": true, "error": null, "manifest": { /* normalised */ } }
// or
{ "valid": false, "error": "app.json: name must be 1 to 20 characters", "manifest": null }

Required fields: package_id, edition, name (≤20), version (x.y.z), min_sdk_version, entrypoint, permissions[], supported_languages[]. Permissions: network, g2-microphone, phone-microphone, album, location, camera (whitelist only on network). Languages: en, de, fr, es, it, zh, ja, ko.

Talking to agents from a G2 app

There is no glasses-specific invoke route. Your WebView talks to your proxy (credentials never ship in the public page). The proxy uses the standard agent invoke API. For voice, send WAV as an attachments entry — the agent must allow audio in its input modalities; list/text questions work with any text agent.

# Text question (from your proxy)
curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/YOUR_AGENT_SLUG/invoke' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "input": "What are the contraindications?",
    "session_id": "wearer-123"
  }'

# Voice (WAV attachment — same shape the starter template uses)
curl --location 'https://backend.hikigaiplatform.io/api/v1/agents/YOUR_AGENT_SLUG/invoke' \
  --header 'Authorization: Bearer hikigai_your_api_key' \
  --header 'X-Project-ID: YOUR_PROJECT_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "input": "Answer the question in this recording.",
    "session_id": "wearer-123",
    "attachments": [
      { "data_base64": "<BASE64_WAV>", "mime_type": "audio/wav" }
    ]
  }'

Paginate the answer for the 640×350 display with the SDK helpers (default char budget is 2000; the starter template uses 420 via GLASSES_PAGE_CHARS):

import { normalizeForGlasses, paginateForGlasses } from '@hikigai/app-sdk'

const pages = paginateForGlasses(response.content, { maxChars: 420 })
// pages → string[] ready for TextContainerProperty on the glasses

Need more help? Check the Getting Started guide or review the Platform API Reference.