Skip to main content

Install

npm install @okrapdf/sdk
@okrapdf/sdk/react is a subpath export of the SDK package, not a separate npm package.

OkraProvider

Wrap a subtree with OkraProvider and provide either a documentId, a url, or a file.
'use client';

import { OkraProvider } from '@okrapdf/sdk/react';

export function DocumentRoot({ documentId, children }) {
  return (
    <OkraProvider
      apiKey={process.env.NEXT_PUBLIC_OKRA_PUBLISHABLE_KEY}
      documentId={documentId}
    >
      {children}
    </OkraProvider>
  );
}
Use publishable browser keys or a server-side boundary for browser apps. Do not expose okra_sk_ secret keys in client-side bundles.

Status and pages

import { useOkraDocument, useDocumentStatus, usePages } from '@okrapdf/sdk/react';

function DocumentPanel() {
  const { session, documentId, isReady } = useOkraDocument();
  const status = useDocumentStatus(session);
  const pages = usePages(session, { enabled: isReady });

  if (status.error) return <p>{status.error.message}</p>;
  if (!documentId) return <p>Resolving document...</p>;

  return (
    <section>
      <p>{status.data?.phase ?? 'loading'}</p>
      {pages.data.map((page) => (
        <article key={page.pageNumber ?? page.page}>
          <h2>Page {page.pageNumber ?? page.page}</h2>
          <pre>{page.markdown ?? page.content}</pre>
        </article>
      ))}
    </section>
  );
}

Chat

import { useChat, useOkraDocument } from '@okrapdf/sdk/react';

function ChatPanel() {
  const { session } = useOkraDocument();
  const chat = useChat({ session, stream: true });

  return (
    <form onSubmit={chat.handleSubmit}>
      {chat.messages.map((message) => (
        <p key={message.id}>
          <strong>{message.role}</strong>: {message.content}
        </p>
      ))}
      <input
        value={chat.input}
        onChange={chat.handleInputChange}
        placeholder="Ask about this document"
      />
      <button disabled={chat.isLoading || !session}>Send</button>
    </form>
  );
}

Structured extraction

The React package does not ship a separate useStructuredOutput hook. Call session.prompt(...) with a schema from your component or wrap it in your app’s own state hook.
import { useState } from 'react';
import { z } from 'zod';
import { useOkraDocument } from '@okrapdf/sdk/react';

const Invoice = z.object({
  vendor: z.string(),
  total: z.number(),
});

function InvoiceFields() {
  const { session } = useOkraDocument();
  const [data, setData] = useState<z.infer<typeof Invoice> | null>(null);

  async function extract() {
    if (!session) return;
    const result = await session.prompt('Extract invoice fields', {
      schema: Invoice,
    });
    setData(result.data ?? null);
  }

  return (
    <section>
      <button onClick={extract} disabled={!session}>Extract</button>
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : null}
    </section>
  );
}

DocumentAgent realtime context

For realtime DocumentAgent state and methods, use createOkraContext(...). This requires the agents React package in your app.
import { createOkraContext } from '@okrapdf/sdk/react';

const okra = createOkraContext({
  host: 'https://api.okrapdf.com',
  agent: 'DocumentAgent',
  authEndpoint: '/api/okra/document-token',
});

export function LiveDocument({ documentId }) {
  const document = okra.useDocument(documentId);
  const phase = okra.useDocumentSlice(documentId, (state) => state.phase);

  return <p>{phase ?? document.state?.phase ?? 'connecting'}</p>;
}