Skip to main content

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

PropTypeDefaultDescription
apiKeystringAPI key (okra_...). Required.
baseUrlstringhttps://api.okrapdf.comOverride 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

FieldTypeDescription
statusDocumentStatusCurrent processing status (phase, page counts).
pagesPage[]Extracted page content and entities.
isLoadingbooleanTrue while initial fetch is in progress.
errorError | nullError 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

FieldTypeDescription
messagesChatMessage[]Chat history ({ role, content }).
send(query: string) => voidSend a new message.
isStreamingbooleanTrue while the assistant is responding.
errorError | nullError 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

FieldTypeDescription
dataT | nullExtracted data matching the schema, or null before extraction.
metaStructuredOutputMeta | nullConfidence score, model info, duration.
isLoadingbooleanTrue while extraction is running.
errorError | nullError if extraction failed.
extract(query: string) => voidTrigger 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>
  );
}