Documentation Index
Fetch the complete documentation index at: https://docs.okrapdf.com/llms.txt
Use this file to discover all available pages before exploring further.
Install
npm install @okrapdf/react @okrapdf/runtime
@okrapdf/react is a peer of @okrapdf/runtime — install both.
OkraProvider
Wrap your app (or a subtree) with OkraProvider to supply the API key to all hooks.
import { OkraProvider } from '@okrapdf/react';
export default function App({ children }) {
return (
<OkraProvider apiKey={process.env.NEXT_PUBLIC_OKRA_API_KEY}>
{children}
</OkraProvider>
);
}
Options
| Prop | Type | Default | Description |
|---|
apiKey | string | — | API key (okra_...). Required. |
baseUrl | string | https://api.okrapdf.com | Override for self-hosted or dev. |
useDocument
Fetch document status and pages.
import { useDocument } from '@okrapdf/react';
function DocumentViewer({ id }: { id: string }) {
const { status, pages, isLoading, error } = useDocument(id);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<p>Phase: {status.phase} ({status.pagesCompleted}/{status.pagesTotal} pages)</p>
{pages.map((p) => (
<div key={p.page}>
<h3>Page {p.page}</h3>
<pre>{p.content}</pre>
</div>
))}
</div>
);
}
Returns
| Field | Type | Description |
|---|
status | DocumentStatus | Current processing status (phase, page counts). |
pages | Page[] | Extracted page content and entities. |
isLoading | boolean | True while initial fetch is in progress. |
error | Error | null | Error if the fetch failed. |
The hook polls automatically while the document is still processing, then stops once extraction is complete.
useChat
Stream chat messages with a document.
import { useChat } from '@okrapdf/react';
function ChatPanel({ id }: { id: string }) {
const { messages, send, isStreaming } = useChat(id);
return (
<div>
{messages.map((m, i) => (
<div key={i} className={m.role === 'user' ? 'text-right' : ''}>
<p>{m.content}</p>
</div>
))}
<form onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem('q') as HTMLInputElement;
send(input.value);
input.value = '';
}}>
<input name="q" placeholder="Ask about this document..." disabled={isStreaming} />
<button type="submit" disabled={isStreaming}>Send</button>
</form>
</div>
);
}
Returns
| Field | Type | Description |
|---|
messages | ChatMessage[] | Chat history ({ role, content }). |
send | (query: string) => void | Send a new message. |
isStreaming | boolean | True while the assistant is responding. |
error | Error | null | Error if the stream failed. |
useStructuredOutput
Extract typed data from a document using a Zod schema.
import { useStructuredOutput } from '@okrapdf/react';
import { Invoice } from '@okrapdf/schemas';
function InvoiceExtractor({ id }: { id: string }) {
const { data, meta, isLoading, error, extract } = useStructuredOutput(id, Invoice);
return (
<div>
<button onClick={() => extract('Extract all invoice fields')} disabled={isLoading}>
Extract
</button>
{data && (
<div>
<p>Vendor: {data.vendor}</p>
<p>Total: ${data.total}</p>
<p>Confidence: {(meta?.confidence ?? 0) * 100}%</p>
<table>
<thead><tr><th>Item</th><th>Amount</th></tr></thead>
<tbody>
{data.lineItems.map((item, i) => (
<tr key={i}><td>{item.description}</td><td>${item.amount}</td></tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
Returns
| Field | Type | Description |
|---|
data | T | null | Extracted data matching the schema, or null before extraction. |
meta | StructuredOutputMeta | null | Confidence score, model info, duration. |
isLoading | boolean | True while extraction is running. |
error | Error | null | Error if extraction failed. |
extract | (query: string) => void | Trigger extraction with a natural-language prompt. |
Full example
Server-side upload, client-side rendering with hooks.
// app/api/upload/route.ts (server)
import { createOkra } from '@okrapdf/runtime';
const okra = createOkra({ apiKey: process.env.OKRA_API_KEY });
export async function POST(req: Request) {
const form = await req.formData();
const file = form.get('file') as File;
const session = await okra.sessions.create(file, { wait: true });
return Response.json({ id: session.id });
}
// components/DocumentView.tsx (client)
'use client';
import { OkraProvider, useDocument, useChat, useStructuredOutput } from '@okrapdf/react';
import { Invoice } from '@okrapdf/schemas';
function DocumentView({ id }: { id: string }) {
const { status, pages } = useDocument(id);
const { messages, send, isStreaming } = useChat(id);
const { data, extract, isLoading } = useStructuredOutput(id, Invoice);
return (
<div className="grid grid-cols-2 gap-4">
{/* Left: pages */}
<div>
<p>{status.phase} — {status.pagesCompleted} pages</p>
{pages.map((p) => <pre key={p.page}>{p.content}</pre>)}
</div>
{/* Right: chat + extraction */}
<div>
<div>
{messages.map((m, i) => <p key={i}><b>{m.role}:</b> {m.content}</p>)}
<input
onKeyDown={(e) => e.key === 'Enter' && send(e.currentTarget.value)}
placeholder="Ask..."
disabled={isStreaming}
/>
</div>
<button onClick={() => extract('Extract invoice fields')} disabled={isLoading}>
Extract Invoice
</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
</div>
);
}
export default function Wrapper({ id }: { id: string }) {
return (
<OkraProvider apiKey={process.env.NEXT_PUBLIC_OKRA_API_KEY!}>
<DocumentView id={id} />
</OkraProvider>
);
}