@ftaip/sdk
v0.11.0
Published
React SDK for AI Paralegal — AI-powered legal assistant integration
Maintainers
Readme
@ftaip/sdk
React SDK for AI Paralegal — lets third-party apps embed inside the AI Paralegal host as an iframe tool.
Quick Scaffold
Create a new SDK tool project with a single command:
npx @ftaip/create-appThis interactive CLI will prompt for your project name, description, and which capabilities you need, then generate a complete Vite + React + TypeScript project with the selected hooks pre-wired.
Installation
npm install @ftaip/sdkQuick Start
When your app is loaded inside the AI Paralegal admin, the host injects token, baseUrl, and apiKey as URL query parameters. The SessionGate component handles everything:
import { SessionGate, useSubmitResult, useAskMatterAI } from "@ftaip/sdk";
function App() {
return (
<SessionGate>
{(session, client) => <ToolPanel session={session} client={client} />}
</SessionGate>
);
}Or use useSession directly for more control:
import { useSession } from "@ftaip/sdk";
function App() {
const { session, client, loading, error } = useSession({});
if (loading) return <p>Authenticating...</p>;
if (error) return <p>Authentication failed: {error.message}</p>;
if (!session || !client) return <p>No exchange token provided.</p>;
return <ToolPanel session={session} client={client} />;
}Authentication Modes
1. Token Exchange (recommended for iframe apps)
The host loads your app in an iframe with ?token=<exchange_token>&baseUrl=<host_origin>&apiKey=<sdk_api_key>. The useSession hook handles everything:
import { useSession, useAskMatterAI } from "@ftaip/sdk";
import type { AiParalegalClient, SessionContext } from "@ftaip/sdk";
function App() {
const { session, client, loading, error } = useSession({});
if (loading) return <p>Authenticating...</p>;
if (error) return <p>Authentication failed: {error.message}</p>;
if (!session || !client) return <p>No exchange token provided.</p>;
return <AskPanel session={session} client={client} />;
}
function AskPanel({
session,
client,
}: {
session: SessionContext;
client: AiParalegalClient;
}) {
const { ask, data, loading, error } = useAskMatterAI(client, session);
return (
<div>
<button onClick={() => ask("Summarise this matter")} disabled={loading}>
{loading ? "Asking..." : "Ask AI"}
</button>
{error && <p>Error: {error.message}</p>}
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}For local development you can override the URL params or use a dev token (see Local Development).
const { session, client } = useSession({
apiKey: "your-api-key",
baseUrl: "http://localhost:8000",
});2. API Key + Explicit IDs (server-side / direct)
For server-side usage or when you manage firm/matter context yourself:
import { AiParalegalClient, useAskMatterAI } from "@ftaip/sdk";
const client = new AiParalegalClient({
baseUrl: "https://your-aiparalegal-instance.com",
apiKey: process.env.AI_PARALEGAL_API_KEY!,
});
function AskAI() {
const { ask, data, loading, error, reset } = useAskMatterAI(client, {
firmId: "firm-uuid",
matterId: "matter-uuid",
loadMatterFacts: true,
});
return (
<div>
<button
onClick={() => ask("What is the status of this matter?")}
disabled={loading}
>
{loading ? "Asking..." : "Ask AI"}
</button>
{error && <p>Error: {error.message}</p>}
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
<button onClick={reset}>Reset</button>
</div>
);
}Local Development
To develop SDK apps locally without the iframe exchange flow, use a dev token:
- In AI Paralegal: Admin → SDK Applications → your app → App Preview tab
- Click Generate Dev Token, select firm and matter
- Copy the token (expires in 30 days)
- In your app, configure
useSession:
const { session, client } = useSession({
devToken: import.meta.env.VITE_DEV_TOKEN,
baseUrl: import.meta.env.VITE_BASE_URL,
apiKey: import.meta.env.VITE_API_KEY,
});The kitchen sink app includes a full setup guide with .env.example, 10 working examples of every SDK capability, and troubleshooting.
Developing the SDK
To work on the SDK source and test against an app:
# In the SDK package
cd aiparalegalsdk
npm run build
npm link
# In your app
cd your-app
npm link @ftaip/sdkAfter SDK changes, rebuild:
cd aiparalegalsdk && npm run buildUse npm run dev in the SDK package for watch mode during active development.
To revert to the published package:
npm unlink @ftaip/sdk
npm install @ftaip/sdkSubmitting Results
Use useSubmitResult to send tool results back to the AI agent:
import { useSubmitResult } from "@ftaip/sdk";
function ResultPanel({ client, session }) {
const { submit, loading, submitted, error } = useSubmitResult(client, session);
const handleSubmit = () => {
submit({ stamp_duty: 5000, jurisdiction: "NSW" });
};
if (submitted) return <p>Result submitted successfully.</p>;
return (
<button onClick={handleSubmit} disabled={loading}>
{loading ? "Submitting..." : "Submit Result"}
</button>
);
}Available Hooks
Core
| Hook | Description | Key methods |
|------|-------------|-------------|
| useSession | Token exchange and session lifecycle | session, client |
| useAskMatterAI | Ask the AI about the current matter | ask(prompt) |
| useSubmitResult | Submit tool results back to the agent | submit(result) |
| useLLM | LLM text generation with streaming and structured output | generate(prompt, opts) |
Documents & Files
| Hook | Description | Key methods |
|------|-------------|-------------|
| useDocs | Document CRUD, markdown-to-doc, download | create, createAndDownload, list, get, update, delete |
| useOCR | Extract text from images and PDFs | extract(file, opts) |
| useFiles | Upload files to the host | upload(files) |
| useMarkItDown | Convert documents to markdown | convert(file) |
| useTemplate | Template placeholder extraction, merge, value suggestion | extractPlaceholders, merge, suggestValues |
Audio & Speech
| Hook | Description | Key methods |
|------|-------------|-------------|
| useTranscribe | Audio transcription with streaming | transcribe(file, opts) |
| useTextToSpeech | Text-to-speech audio generation | speak(text, opts) |
| useDictation | Real-time dictation | start(), stop() |
Document Collections
| Hook | Description | Key methods |
|------|-------------|-------------|
| useCollections | Document collection management | create, list, remove |
| useCollection | Single collection operations | get, uploadDocuments, search |
| useCollectionSearch | Semantic search within a collection | search(query) |
| useCollectionQuery | AI-powered collection Q&A with streaming | query(prompt) |
| useCollectionTable | Generate structured tables from collections | generate(columns) |
| useCollectionAnalyze | AI analysis of collection documents | analyze(prompt) |
| useSuggestedPrompts | Get AI-suggested prompts for a collection | fetch(collectionId) |
Review & Storage
| Hook | Description | Key methods |
|------|-------------|-------------|
| useContractReview | Contract review with staged streaming | review(docId, opts) |
| useStorage | Persist key-value data via host storage | get, put, remove, list |
Developer Experience
| Hook / Component | Description | Key methods |
|------|-------------|-------------|
| SessionGate | Component that handles session loading/error/no-session states | children(session, client) |
| useLlmEffect | React to LLM completion with callbacks | onSuccess(text, structured) |
| useLoadingMessages | Rotating status messages during long operations | Returns current message string |
| useEditableList | Generic editable list state (add/update/remove) | items, addItem, updateItem, removeItem |
| useHistory | In-memory prompt/result history | history, pushCurrent, lastPrompt |
Utility Functions
All utilities are imported directly from @ftaip/sdk:
import { downloadBlob, formatFileSize, parseLlmResponse } from "@ftaip/sdk";File Operations
| Function | Description |
|----------|-------------|
| downloadBlob(content, filename, mimeType?) | Trigger browser download from Blob or string |
| downloadText(content, filename, mimeType?) | Download string as text file |
| fileToDataUrl(file) | Convert File to base64 data URL |
| dataUrlToFile(dataUrl, filename, mimeType?) | Convert data URL back to File |
Formatting
| Function | Description |
|----------|-------------|
| formatFileSize(bytes) | 1.2 MB, 340 KB, etc. |
| formatDate(date, locale?, options?) | Locale-aware date formatting |
| formatDateLong(date?, locale?) | 04 March 2025 for document headers |
| formatCurrency(amount, currency?, locale?) | $1,234.56 (defaults to AUD) |
| wordCount(text) | Count words in a string |
| sanitizeFilename(name, maxLength?) | Filesystem-safe filename |
| escapeHtml(text) | Escape HTML entities |
| capitalizeWords(text) | Title Case each word |
LLM Response Parsing
| Function | Description |
|----------|-------------|
| parseLlmResponse<T>(response) | Extract structured data from LLM responses (handles structured, text JSON, markdown fences) |
| stripJsonMarkdown(text) | Remove ```json / ``` fences from LLM text |
Clipboard & Export
| Function | Description |
|----------|-------------|
| copyToClipboard(content, { asHtml? }) | Copy with optional rich HTML (preserves formatting in Word) |
| toCsv(rows, columns, headerRows?) | Build CSV string from typed data |
File Validation
| Function / Constant | Description |
|----------|-------------|
| validateFile(file, options?) | Validate file size and type |
| validateFiles(files, options?) | Validate multiple files, returns { valid, errors } |
| isImageFile(file) | Check if file is an image |
| isSupportedDocument(filename) | Check against platform-supported extensions |
| getFileExtension(filename) | Get lowercase extension with dot |
| SUPPORTED_DOCUMENT_EXTENSIONS | All supported extensions |
| SUPPORTED_DOCUMENT_MIMES | All supported MIME types |
Standalone Functions
Token Exchange
import { AiParalegalClient, exchangeToken } from "@ftaip/sdk";
const client = new AiParalegalClient({
baseUrl: "https://your-aiparalegal-instance.com",
apiKey: process.env.AI_PARALEGAL_API_KEY!,
});
const session = await exchangeToken(client, "exchange-token-from-url");
// session.session_token, session.firm_id, session.matter_id, session.expires_atAsk AI (with API key)
import { AiParalegalClient, askAi } from "@ftaip/sdk";
const response = await askAi(client, {
prompt: "Summarise the key facts of this matter",
firm_id: "firm-uuid",
matter_id: "matter-uuid",
load_matter_facts: true,
});Ask AI (with session token)
import { askAiWithSession } from "@ftaip/sdk";
const response = await askAiWithSession(client, session.session_token, {
prompt: "Summarise the key facts of this matter",
});Submit Result
import { submitResult } from "@ftaip/sdk";
const response = await submitResult(client, session.session_token, {
stamp_duty: 5000,
});Token Exchange Flow
┌─────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ Admin Panel │ │ AI Paralegal API │ │ Your App │
└────────┬────────┘ └──────────┬───────────┘ └────────┬─────────┘
│ │ │
│ Generate exchange token│ │
│────────────────────────>│ │
│ { exchange_token } │ │
│<────────────────────────│ │
│ │ │
│ Load iframe ?token=xxx │ │
│─────────────────────────────────────────────────────>
│ │ │
│ │ POST /token/exchange │
│ │ X-API-KEY + token │
│ │<──────────────────────────│
│ │ { session_token, ... } │
│ │──────────────────────────>│
│ │ │
│ │ POST /ai/ask │
│ │ Bearer {session_token} │
│ │<──────────────────────────│
│ │ { data: ... } │
│ │──────────────────────────>│API Reference
| Category | Exports |
|----------|---------|
| Hooks | useSession, useAskMatterAI, useSubmitResult, useLLM, useOCR, useDocs, useFiles, useCollections, useTemplate, useStorage, useContractReview, useHealth, and more |
| Components | SessionGate, DevToolsProvider, DevToolsPanel |
| Classes | AiParalegalClient, ApiError |
| Functions | exchangeToken, verifyToken, askAi, askAiWithSession, submitResult, checkHealth, and capability-specific functions (e.g. extractText, uploadFiles) |
| Types | SessionContext, AiParalegalClientConfig, AskAiRequest, and many capability-specific types |
See the tables below for detailed hook and utility documentation.
Error Handling
API failures throw ApiError, which extends Error with structured fields for programmatic handling:
import { useSession, ApiError } from "@ftaip/sdk";
const { error } = useSession({});
// Check error type
if (ApiError.is(error)) {
console.error(error.code, error.status, error.details);
if (error.code === "rate_limit_exceeded") {
// Retry with backoff
}
}Properties:
| Property | Type | Description |
|----------|------|-------------|
| code | string | Machine-readable error code (e.g. invalid_token, rate_limit_exceeded) |
| message | string | Human-readable error message |
| status | number | HTTP status code |
| details | Record<string, string[]> \| null | Optional validation or field-level details |
Use ApiError.is(error) as a type guard in catch blocks.
Troubleshooting
| Issue | Solution |
|-------|----------|
| "No exchange token provided" | Ensure the host passes ?token=...&baseUrl=...&apiKey=... in the iframe URL, or set devToken, baseUrl, and optionally apiKey for local dev |
| "Token verification failed" / 401 | Dev token may be expired (30 days). Generate a new one from Admin → SDK Applications → App Preview |
| "apiKey is required" | Use API key for token exchange; dev token flow does not require it for verify, but you may need it for other flows |
| CORS errors | Ensure the host allows your dev origin (e.g. http://localhost:5173) in CORS config |
| Changes to .env not reflected | Restart the Vite dev server; env vars are read at startup |
Security
- Exchange tokens are single-use and expire in 5 minutes
- Session tokens are scoped to one firm/matter and expire in 1 hour
- The API key is stored as a private field and never exposed publicly on the client instance
postMessageis scoped to the host origin — never uses wildcard (*)baseUrlis validated to be a proper HTTP/HTTPS URL- Session tokens are the recommended auth method for browser-based apps
Requirements
- React 18+ (peer dependency)
- A running AI Paralegal instance with an SDK application configured in the admin panel
