Use the fetchWithPayment helper configured in the getting-started guides so 402 invoices are
resolved automatically.
1. Submit the PDF by URL
const baseUrl = process.env.HORIZON_BASE_URL ?? 'https://api.horizon.new/v1';
const pdfResponse = await fetchWithPayment(`${baseUrl}/extract/pdf`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sourceUrl: 'https://cdn.example.com/handbooks/agent-playbook.pdf',
sourceName: 'Agent Playbook 2025',
options: { segmentLength: 1500, language: 'en', ocr: true },
webhookUrl: 'https://example.com/webhooks/horizon/extraction',
}),
});
const pdfJob = await pdfResponse.json();
console.log('pdf job', pdfJob.jobId);
Upload instead of linking (optional)
import FormData from 'form-data';
import { createReadStream } from 'node:fs';
const form = new FormData();
form.append('file', createReadStream('./assets/agent-playbook.pdf'));
form.append('sourceName', 'Agent Playbook 2025');
const uploadResponse = await fetchWithPayment(`${baseUrl}/extract/pdf`, {
method: 'POST',
headers: form.getHeaders(),
body: form,
});
const uploadJob = await uploadResponse.json();
2. Handle synchronous completion
if (pdfJob.status === 'completed' && pdfJob.result) {
console.log('Inline chunks', pdfJob.result.chunks.length);
}
3. Poll the job
const status = await fetchWithPayment(pdfJob.statusUrl).then((res) => res.json());
if (status.state === 'processing') {
await new Promise((resolve) => setTimeout(resolve, 3000));
}
if (status.state !== 'succeeded') {
throw new Error(`PDF extraction failed: ${status.error?.code ?? 'unknown'}`);
}
console.log('chunks', status.result.chunks.length);
4. Apply the output
- Persist normalized chunks to your knowledge store.
- Store
status.result.sources if you need traceability back to page numbers.
- Drive
/search filters by adding your own annotations when you persist the extraction.