Skip to main content
This page provides a quick, actionable path to authenticate, call REST endpoints, and capture streaming events from the middleware.

API Structure

The Praxis AI API is organized into three functional areas:

Authenticate

Sign in or authenticate to retrieve an access token.

Runtime

Endpoints for selecting a Digital Twin, creating conversations, developing assistants, and dialoguing with AI models.

Administration

Configure Digital Twins, set up permissions and entitlements, audit user history, and monitor activity.

Getting Started

1

Obtain an authorization token

All API requests require an authorization token issued after successful authentication. See the Authentication API for how to sign in and use the token in requests.
For automation, Admins can use a persistent API key. Interactive sign-in returns a session token that expires after 24 hours — fine for a user session, awkward for a script. Instead, an Admin can create a long-lived API key (format pria_…) from their profile’s API Key settings, store it as a secret, and exchange it for a fresh session token whenever needed:
# Exchange a persistent API key for a 24-hour session token
curl -s -X POST https://pria.praxislxp.com/api/auth/api-key-signin \
  -H "x-api-key: pria_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# → { "token": "<session-token>", "profile": { ... } }
Use the returned token as the x-access-token header on subsequent requests. The API key itself never expires (rotate or revoke it from the same settings panel), so automation can re-mint a session token on demand without storing user credentials.
API keys are available to Admin accounts and are scoped to that admin’s permissions — the key stops working if the account is deactivated or loses admin access.
2

Know your Base URLs

3

Make a test request

Use your token and send a simple JSON request to verify connectivity and headers.
# Replace your_api_access_token and fields as needed
curl https://pria.praxislxp.com/api/user/refresh/profile \
  -H "x-access-token: your_api_access_token" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "welcome-message",
    "tag": "production"
  }'

Using the API

The Praxis API follows standard RESTful conventions:
  • JSON for all requests and responses
  • HTTP status codes communicate success/failure
  • Error responses return a consistent JSON shape with details
  • Rate limits protect service stability
If you’re building a browser client, ensure you include the correct auth header on every request. Authorization tokens are only valid for 24 hours.

Automating with the Live API Definition

Both APIs publish a live OpenAPI 3 (Swagger) definition that always reflects the currently deployed endpoints, schemas, parameters, and auth schemes. These are the exact specs that power this API Reference — fetching them programmatically gives you a machine-readable contract you can hand to code generators, AI coding agents, and orchestration platforms.
Because these URLs serve the live definition, anything you generate from them stays aligned with the deployed API — re-fetch after a Praxis AI release to pick up new endpoints and fields automatically.

Why use the live definition

  • Always current — no hand-maintained endpoint lists to drift out of date.
  • Machine-readable — standard OpenAPI 3 JSON that virtually every tool and agent understands.
  • Self-describing — request/response schemas, required parameters, and auth schemes are all encoded, so tools can scaffold correct calls without guesswork.

Use it with AI coding agents (Claude, Claude Code)

AI coding assistants such as Claude and Claude Code can consume the OpenAPI definition directly to scaffold typed clients, write request code, or build an MCP server that exposes Pria endpoints as tools.
1

Fetch the definition

Download the spec so the agent can read it locally:
curl -s https://pria.praxislxp.com/api/rt-docs.json -o pria-runtime-openapi.json
curl -s https://pria.praxislxp.com/api/admin-docs.json -o pria-admin-openapi.json
2

Point the agent at it

Give the agent the file (or the live URL) and describe what you want. For example:
“Using pria-runtime-openapi.json, generate a typed TypeScript client for the Q&A and conversation endpoints, with x-access-token auth wired in.”
“From this OpenAPI spec, build an MCP server that exposes the Runtime API as tools so I can call Pria from my agent.”
3

Wire in authentication

The spec describes the auth scheme, but the agent still needs a valid token. Supply one from the Authentication API and have the agent attach it as the x-access-token header.
For Claude tool use / function calling, transform each OpenAPI operation into a tool definition so the model can invoke Pria endpoints directly. Most agent frameworks (and Claude Code) can do this automatically from an OpenAPI file or URL.

Import into orchestration platforms

Low-code/iPaaS tools, API clients, and agent frameworks accept an OpenAPI URL or file to auto-generate connectors — no manual endpoint mapping required:
Tool / platformHow to import
Postman / InsomniaImport → Link and paste the definition URL to generate a ready-to-run request collection.
n8n / Make / ZapierUse their HTTP/OpenAPI or custom-connector import to register Pria operations.
LangChain / LlamaIndexLoad the spec with an OpenAPI toolkit to expose endpoints as agent tools.
openapi-generator / swagger-codegenGenerate typed client libraries in 50+ languages.
# Generate a typed client from the live Runtime API definition
npx @openapitools/openapi-generator-cli generate \
  -i https://pria.praxislxp.com/api/rt-docs.json \
  -g typescript-fetch \
  -o ./pria-runtime-client
The OpenAPI definition describes every endpoint, but access is still governed by your token’s permissions and entitlements. Administrator API operations require admin credentials — generating a client does not grant access you don’t already hold.

HTTP Streaming with Server-Sent Events (SSE)

Start here. SSE is the recommended way to stream responses for most integrations. It’s a pure HTTP, stateless endpoint with nothing to manage beyond a standard request — ideal for automation, SDKs, and server-side code. Reach for Socket.IO only when you specifically need a persistent, bidirectional real-time connection (typically a browser app).
Praxis AI offers a pure HTTP streaming endpoint using Server-Sent Events (SSE). This approach is ideal for:
  • Server-side applications (Node.js, Python, Go, etc.)
  • Automation, agents, and orchestration tools
  • Environments where WebSockets are restricted
  • Simpler integrations without persistent socket connections
  • SDK and CLI tool development
The SSE streaming endpoint uses the same JWT authentication as other REST endpoints. No Socket.IO connection is required.

Endpoint

POST /api/ai/personal-stream/qanda-stream

Request Headers

HeaderRequiredDescription
x-access-tokenYesYour JWT authentication token
Content-TypeYesapplication/json

Request Body

{
  "inputs": ["Your prompt or question here"],
  "requestArgs": {
    "institutionPublicId": "f831501f-b645-481a-9cbb-331509aaf8c1",
    "assistantId": "6856fa89cbafcff8d98680f5",
    "selectedCourse": {
      "course_id": 1750532703472,
      "course_name": "AI Fundamentals"
    },
    "ragOnly": false,
    "userTimezone": "America/New_York"
  }
}
FieldTypeRequiredDescription
inputsstring[]YesUser messages to send to the AI
requestArgs.institutionPublicIdstring (UUID)NoSpecifies the digital twin (institution) to send the command to. Validates user membership and switches the user’s active institution for the request.
requestArgs.assistantIdstring (ObjectId)NoAssistant to use. Takes priority over selectedCourse.assistant._id (legacy). Falls back to history record resolution.
requestArgs.selectedCourseobjectNoConversation context with course_id and course_name
requestArgs.ragOnlybooleanNoWhen true, returns only RAG search results without AI generation
requestArgs.userTimezonestringNoUser’s IANA timezone identifier

cURL Example

curl -s -N -X POST "https://pria.praxislxp.com/api/ai/personal-stream/qanda-stream" \
  -H "Content-Type: application/json" \
  -H "x-access-token: your_api_access_token" \
  -d '{
    "inputs": ["What is the capital of Germany?"],
    "requestArgs": {
      "institutionPublicId": "e455529a-4f51-479e-94fc-bbebb41d19a1",
      "assistantId": "6970576f40219f56e41ec00a",
      "selectedCourse": {
        "course_id": 1769040847660,
        "course_name": "free Wednesday, Jan 21, 2026"
      },
      "ragOnly": false
    }
  }'
Use -s to suppress progress output and -N to disable buffering, so SSE chunks appear in real time.

Response Headers

Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache, no-transform
Connection: keep-alive
Content-Encoding: none
Transfer-Encoding: chunked
X-Accel-Buffering: no

Response Format

The endpoint returns text/event-stream; charset=utf-8 content type with SSE-formatted JSON chunks. Each line is prefixed with data: followed by a JSON object and two newlines.
Event TypeDescriptionKey Fields
connectedStream establishedmessage
streamAI-generated text chunkprompt (cumulative), delta (incremental)
thinkingModel reasoning / chain-of-thought (where the provider exposes it)id (round-N), round, delta (new chars) on intermediate frames; text, done: true, optional signature on round close
tool_callTool invocation started (RAG, web search, etc.)call_id, name, arguments, displayInfo
tool_resultTool execution completed with resultscall_id, name, response, success, responseDurationMs
completeFinal response with usage metricssuccess, usage, outputs, model, cached, completion
errorProcessing errorerror.message, error.status
doneStream terminated — no more events will follow(none)
Example SSE Response:
data: {"type":"connected","message":"Stream connected"}

data: {"type":"thinking","id":"round-0","round":0,"delta":"The user is asking about France."}

data: {"type":"thinking","id":"round-0","round":0,"delta":" Paris is the capital."}

data: {"type":"thinking","id":"round-0","round":0,"text":"The user is asking about France. Paris is the capital.","signature":"abc123","done":true}

data: {"type":"stream","prompt":"The capital","delta":"The capital"}

data: {"type":"stream","prompt":"The capital of France","delta":" of France"}

data: {"type":"stream","prompt":"The capital of France is Paris.","delta":" is Paris."}

data: {"type":"tool_call","call_id":"tooluse_abc123","name":"search_uploads","arguments":null,"displayInfo":{"icon":"search","label":"Searching documents"}}

data: {"type":"tool_result","call_id":"tooluse_abc123","name":"search_uploads","arguments":{"query":"France capital"},"response":"...","responseLength":512,"responseDurationMs":150,"success":true}

data: {"type":"complete","success":true,"usage":1234,"outputs":["The capital of France is Paris."],"model":"us.anthropic.claude-sonnet-4-5-20250929-v1:0","cached":0,"completion":42}

data: {"type":"done"}
The prompt field on stream events is cumulative (full response up to this point); the delta field contains only the new characters since the last event. Use delta for incremental UI rendering and prompt for the current full text.
thinking events are gated by an institution-level Display Thinking Details toggle (default on). When disabled, no thinking events fire and the underlying History record stores no thinking text. Personal users (no institution) always receive thinking events. Providers that do not expose thinking (Mistral) simply emit no thinking events. OpenAI emits summary-only thinking — OpenAI hides raw chain-of-thought by policy.
Bedrock Opus 4.6/4.7 reasoning is opaque. These models use Anthropic’s adaptive thinking mode, which emits an encrypted reasoning signature only — no plaintext. No thinking events fire and no lightbulb appears in the UI even when the toggle is on. To see plaintext reasoning on Bedrock, use Claude Sonnet 3.7+ or Opus 4.0/4.5 (standard thinking with budget_tokens). Plaintext reasoning is also available on Anthropic Direct API, Google Gemini 2.5+, xAI Grok 3-mini / 4.x, and OpenAI Responses API (summary-only).
Thinking is collected per tool round — each round of model output before/after a tool call gets its own entry, keyed by id (round-0, round-1, …). Intermediate frames within a round are append-only deltas; the round closes with a single done: true frame carrying the authoritative full text and (for Anthropic / Bedrock) a signature used for provider-side multi-step continuation.

Node.js Client Example

// sseClient.js - Complete Node.js SSE streaming client
const https = require('https');

/**
 * Stream a chat request using HTTP SSE
 * @param {string} token - JWT authentication token
 * @param {string} prompt - User's question or prompt
 * @param {object} options - Optional configuration
 * @param {string} [options.institutionPublicId] - Digital twin UUID
 * @param {string} [options.assistantId] - Assistant ObjectId
 * @param {object} [options.selectedCourse] - Conversation context
 * @param {boolean} [options.ragOnly] - RAG-only mode
 * @returns {Promise<object>} - Final response data
 */
async function streamChat(token, prompt, options = {}) {
  const baseUrl = 'https://pria.praxislxp.com';

  const requestBody = JSON.stringify({
    inputs: [prompt],
    requestArgs: {
      institutionPublicId: options.institutionPublicId,
      assistantId: options.assistantId,
      selectedCourse: options.selectedCourse || {},
      ragOnly: options.ragOnly || false,
    }
  });

  return new Promise((resolve, reject) => {
    const req = https.request(
      `${baseUrl}/api/ai/personal-stream/qanda-stream`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-access-token': token
        }
      },
      (res) => {
        if (res.statusCode !== 200) {
          reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
          return;
        }

        let buffer = '';
        let finalResponse = null;

        res.on('data', (chunk) => {
          buffer += chunk.toString();

          // Process complete SSE messages
          const lines = buffer.split('\n\n');
          buffer = lines.pop(); // Keep incomplete message in buffer

          for (const line of lines) {
            if (line.startsWith('data: ')) {
              try {
                const data = JSON.parse(line.slice(6));

                switch (data.type) {
                  case 'connected':
                    console.log('Stream connected');
                    break;

                  case 'stream':
                    // Use delta for incremental rendering
                    process.stdout.write(data.delta || '');
                    break;

                  case 'thinking':
                    // Model reasoning — keyed by `id` (round-0, round-1, ...).
                    // Render in a collapsible UI block separate from the answer.
                    if (data.done) {
                      console.log(`\n[Thinking ${data.id} complete: ${(data.text || '').length} chars]`);
                    } else if (data.delta) {
                      // Incremental thinking — accumulate per `data.id`
                      // process.stdout.write(`💡${data.delta}`);
                    }
                    break;

                  case 'tool_call':
                    console.log(`\n[Tool: ${data.name}]`);
                    break;

                  case 'tool_result':
                    console.log(`[Tool ${data.name} completed in ${data.responseDurationMs}ms]`);
                    break;

                  case 'complete':
                    console.log(`\n\nComplete - Tokens: ${data.usage}, Model: ${data.model}`);
                    finalResponse = data;
                    break;

                  case 'error':
                    console.error('\nError:', data.error);
                    reject(new Error(data.error?.message || 'Stream error'));
                    return;

                  case 'done':
                    resolve(finalResponse);
                    return;
                }
              } catch (e) {
                // Skip malformed JSON
              }
            }
          }
        });

        res.on('end', () => {
          resolve(finalResponse);
        });

        res.on('error', reject);
      }
    );

    req.on('error', reject);
    req.write(requestBody);
    req.end();
  });
}

// Example usage
(async () => {
  const token = 'your_jwt_token_here';

  try {
    const result = await streamChat(token, 'What is machine learning?', {
      institutionPublicId: 'f831501f-b645-481a-9cbb-331509aaf8c1',
      assistantId: '6856fa89cbafcff8d98680f5',
      selectedCourse: {
        course_id: 1750532703472,
        course_name: 'AI Fundamentals'
      }
    });

    console.log('\nFinal result:', result);
  } catch (error) {
    console.error('Error:', error.message);
  }
})();

Using Fetch API (Browser/Node.js 18+)

// Modern fetch-based SSE client
async function streamChatFetch(token, prompt, onChunk, requestArgs = {}) {
  const response = await fetch('https://pria.praxislxp.com/api/ai/personal-stream/qanda-stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-access-token': token
    },
    body: JSON.stringify({
      inputs: [prompt],
      requestArgs
    })
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n\n');
    buffer = lines.pop();

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));

        // Call the chunk handler
        if (onChunk) onChunk(data);

        if (data.type === 'complete') {
          return data; // Return final response
        }
      }
    }
  }
}

// Usage with callback — use delta for incremental rendering
await streamChatFetch(
  token,
  'Explain quantum computing',
  (chunk) => {
    if (chunk.type === 'stream') {
      document.getElementById('output').textContent += chunk.delta;
    } else if (chunk.type === 'tool_call') {
      console.log(`Tool invoked: ${chunk.name}`);
    } else if (chunk.type === 'tool_result') {
      console.log(`Tool ${chunk.name} completed in ${chunk.responseDurationMs}ms`);
    }
  },
  {
    institutionPublicId: 'f831501f-b645-481a-9cbb-331509aaf8c1',
    assistantId: '6856fa89cbafcff8d98680f5',
    selectedCourse: { course_id: 1750532703472, course_name: 'AI Fundamentals' }
  }
);

Cancelling an HTTP Stream

To cancel an in-flight HTTP stream request, you can abort the fetch request:
const controller = new AbortController();

// Start the request with abort signal
const response = await fetch('https://pria.praxislxp.com/api/ai/personal-stream/qanda-stream', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-access-token': token
  },
  body: JSON.stringify({
    inputs: ['Your prompt here'],
    requestArgs: { assistantId: '...' }
  }),
  signal: controller.signal  // Pass the abort signal
});

// Later, to cancel:
controller.abort();
The HTTP SSE endpoint is stateless - each request is independent. This makes it ideal for serverless functions, CLI tools, and microservices where maintaining WebSocket connections is impractical.

Comparing Socket.IO vs HTTP SSE

FeatureSocket.IOHTTP SSE
Connection typePersistent WebSocketPer-request HTTP
Setup complexityHigher (connection management)Lower (standard HTTP)
BidirectionalYesNo (server → client only)
Cancel mid-streamcancel_request eventAbort controller
Best forReal-time apps, browsersAPIs, CLIs, serverless
Proxy compatibilityMay require configurationWorks everywhere

Streaming with Socket.IO

Socket.IO is an alternative to the HTTP SSE method above. Prefer it when you need a persistent, bidirectional real-time connection — for example, an interactive browser client. For automation, SDKs, CLIs, and most server-side integrations, use SSE instead.
You can connect to Praxis AI middleware to receive streaming events while REST requests are in-flight. The server emits stream chunks to your socket; you must include the current socket ID in Q&A requests to link streams to your session.

1 Install the Socket.IO Client

npm i socket.io-client

2 Connect and register your user

// socket.ts (client)
import { io } from 'socket.io-client';

const URL = 'https://pria.praxislxp.com/socket.io'; // Middleware streaming endpoint
localStorage.debug = '*'; // Optional: enable Socket.IO debug logs

// Keep a simple local state holder
const state = {};
state.socket = io(URL, { autoConnect: false });

// Mocked user profile (replace with your authenticated session values)
const user = {
  userId: profile?._id,
  email: profile?.email,
  profileName: `${profile?.fname} ${profile?.lname}`,
  institutionId: profile?.institution?._id,
  institutionName: profile?.institution?.name,
};

// Open the connection and register your user
state.socket.on('connect', () => {
  state.socket.emit('REGISTER', user);
});

// Handle graceful disconnect/unsubscribe
state.socket.on('disconnect', (e) => {
  console.log('SocketContext', 'Disconnect', e);
  state.socket.emit('UNSUBSCRIBE', user);
});

// Optional: initiate connection if autoConnect is false
state.socket.connect();

3 Listen for streaming and diagnostic events

// Stream chunks arrive while REST Q&A requests are processed
state.socket.on('RECEIVE_STREAM', (payload) => {
  console.log('SocketContext', 'RECEIVE_STREAM', payload);
});

// Error & connectivity diagnostics
state.socket.on('error', (e) => console.log('SocketContext', 'Error', e));
state.socket.on('connect_success', (e) => console.log('SocketContext', 'Connect Success', e));
state.socket.on('connect_timeout', (e) => console.log('SocketContext', 'Connect Timeout', e));
state.socket.on('connect_error', (e) => console.log('SocketContext', 'Connect Error', e));
These are typical events payload received from the stream
{
    "prompt": " I'm doing great, thank you for asking! 😊 
    I'm here, energized, and ready to help you with whatever you need today.Since we last spoke just a minute ago when you checked if I was here, I'm curious—what's on your mind? Are you:
    - Looking to learn something new?
    - Working on a project or assignment?
    - Curious about a specific topic?
    - Just wanting to chat?
    
    I'm all ears and ready to assist! What would you like to explore",
    "delta": " What would you like to explore",
    "type": "STREAM"
}
prompt: Contains the full response from the beginning of the interaction. delta Contains only the last text segment reported type: “STREAM” for messages streamed by the LLM
Note that the final response may differ from the prompt returned while streaming.

Event types

All RECEIVE_STREAM payloads carry a type field. Branch on it to handle each kind:
typeWhen firedKey fields
STREAMIncremental assistant textprompt (cumulative), delta (new chars)
THINKINGModel reasoning / chain-of-thought (where the provider exposes it)id (round-N), round, delta (new chars) on intermediate frames; text, done: true, optional signature on round close
TOOL_CALLTool invocation started (RAG, web search, MCP, etc.)call_id, name, arguments, displayInfo
TOOL_PROGRESSLong-running tool emits a status updatecall_id, displayInfo, optional progress (0–1)
TOOL_RESULTTool execution completedcall_id, name, response, success, responseDurationMs
RELOADNew conversation turn started (clears prior turn’s UI state)(none)
THINKING — model reasoning stream
THINKING events surface the model’s internal reasoning text (a.k.a. chain-of-thought / “thinking” mode) for the providers that expose it: Anthropic, AWS Bedrock, Google Gemini, OpenAI (summary only — OpenAI hides raw chain-of-thought by policy), and xAI. Mistral does not expose thinking at all and emits no THINKING events. Thinking is collected per tool round — each round of model output before/after a tool call gets its own entry, keyed by id of the form round-0, round-1, etc. Frames within a round are append-only deltas; the round closes with a single done: true frame carrying the authoritative full text and (for Anthropic / Bedrock) a signature for provider-side continuation.
// Intermediate frame — accumulate delta
{ "type": "THINKING", "id": "round-0", "round": 0, "delta": " Let me consider..." }

// Round close — overwrite with authoritative full text
{ "type": "THINKING", "id": "round-0", "round": 0, "text": "Let me consider the user's question carefully...", "signature": "abc123…", "done": true }
The THINKING stream is gated by an institution-level Display Thinking Details toggle (default on). When the institution disables the toggle, no THINKING events fire and the underlying History record stores no thinking text. Personal users (no institution) always receive thinking events.
Render thinking deltas in a collapsible UI block separate from the main answer — users tend to find the chain-of-thought interesting as context, not as the answer itself. The Praxis UI shows thinking under a 💡 lightbulb &lt;details&gt; toggle.

4 Include socketId in your Q&A API requests

The server requires the active Socket.IO session ID to stream chunks to your client. Include socketId in requestArgs.
// Example: POST Q&A request linking REST call to Socket.IO session
const payload = {
  inputs: [userMessage],
  requestArgs: {
    socketId: state.socket.id, // CRITICAL: links the streaming session
    assistantId: currentAssistantId,
    selectedCourse: {
      course_id: currentCourseId,
      course_name: currentCourseName,
    },
  },
};

const response = await fetch('https://pria.praxislxp.com/api/ai/personal/qanda', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    // If your deployment requires header-based auth for this endpoint, include your token here.
    // 'x-access-token': your_api_access_token
  },
  body: JSON.stringify(payload),
});

if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
If you wrap Q&A calls, pass socketId from your Socket.IO context into the wrapper and expose a stream callback to consume RECEIVE_STREAM chunks.]
// Example wrapper usage
const { sessionId } = useSocketIO();

await priaApi.qanda(
  {
    inputs: [userMessage],
    requestArgs: {
      socketId: sessionId, // CRITICAL: Link to Socket.IO session
      assistantId: currentAssistantId,
      selectedCourse: {
        course_id: currentCourseId,
        course_name: currentCourseName,
      },
    },
  },
  onStreamCallback // handle incremental chunks
);

5 Manage reconnections

When the QANDA request response includes "streamingFailed": true, it indicates that the middleware has lost the backend Socket ID mapping (user email to client socket ID), preventing it from communicating response chunks back to the client. This typically occurs when multiple browser windows or tabs are open for the same user email, causing them to compete for the same backend session and overwriting each other’s socket registrations. To resolve this issue, you can re-establish the socket connection to re-register the most current client handle and restore proper communication between the middleware and the active client session.
       
if (response?.streamingFailed){
       
  // reconnecting
  if (state.socket.connected){
    await state.socket.disconnect()
  }
  await state.socket.connect()
}

6 Abort requests

To abort a request while executing, emit a cancel_request event:
state.socket.emit('cancel_request', user);
The cancel_request message signals the controller API to interrupt the ongoing communication and attempt a graceful termination of the conversation. While the controller works to terminate resources cleanly, there may be a slight delay before the process completes. You will only be charged for any partial response generated up to the point of cancellation.

7 Stop streaming the current request

To stop streaming the current response without aborting the request itself, emit a cancel_streaming event:
state.socket.emit('cancel_streaming', user);
cancel_streaming applies only to the current response stream and is cleared automatically before the next interaction.

API vs SDK

While the API provides direct access to all capabilities, the TypeScript/JavaScript SDK simplifies most tasks.

API Benefits

• Direct access to all Praxis features
• Language-agnostic integration
• Fine-grained request/response control
• Ideal for custom platforms or gateways, mobile applications

SDK Benefits

• Higher-level abstractions
• Seamless SSO authentication • UI Ready to use
• Supports LMS Such as Canvas, Moodle, D2L, LTI
For most use cases, we recommend using our SDKs, while the API remains available for mobile applications, low-level or platform-neutral integrations.

Error Handling

The API uses standard HTTP status codes:
  • 2xx: Success
  • 4xx: Client error (invalid request, missing parameters, unauthorized)
  • 5xx: Server error
When an error occurs, the JSON body includes details to help diagnose and resolve issues.

Next Steps

Explore endpoint-specific docs:

Authentication

How to authenticate requests and attach tokens.

AI Q&A

Send conversation requests and receive streaming responses.

SSE Streaming

Stream AI responses over HTTP using Server-Sent Events.