Make sure your integration already wraps requests with fetchWithPayment so Coinbase x402
challenges are paid automatically.
const baseUrl = process.env.HORIZON_BASE_URL ?? 'https://api.horizon.new/v1';
const sheetResponse = await fetchWithPayment(`${baseUrl}/extract/spreadsheet`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sourceUrl: 'https://cdn.example.com/reports/sales-q1-2025.xlsx',
sourceName: 'Sales Q1 2025',
options: {
sheet: 'Summary',
headerRow: 2,
dateFormat: 'yyyy-MM-dd',
},
webhookUrl: 'https://example.com/webhooks/horizon/extraction',
}),
});
const sheetJob = await sheetResponse.json();
console.log('sheet job', sheetJob.jobId);
2. Handle synchronous completion
if (sheetJob.status === 'completed' && sheetJob.result) {
console.log('Inline tables', sheetJob.result.tables?.[0]?.rows.length ?? 0);
}
3. Poll for completion
const status = await fetchWithPayment(sheetJob.statusUrl).then((res) => res.json());
if (status.state === 'processing') {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
if (status.state !== 'succeeded') {
throw new Error(`Spreadsheet extraction failed: ${status.error?.code ?? 'unknown'}`);
}
console.log('rows extracted', status.result.tables?.[0]?.rows.length ?? 0);
4. Use the tables
- Upsert rows into your analytics warehouse or CRM.
- Attach your own annotations to the resulting records for discovery via
/search.
- Compare consecutive exports by storing
status.result.version markers.