Skip to main content
Use the fetchWithPayment helper from your wallet setup so each request satisfies Coinbase x402 challenges automatically.

1. Request the image job

const baseUrl = process.env.HORIZON_BASE_URL ?? 'https://api.horizon.new/v1';

const imagesResponse = await fetchWithPayment(`${baseUrl}/generate/images`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    prompt: 'Dreamlike neon city skyline with floating gardens above the bay.',
    count: 2,
    aspectRatio: '16:9',
    webhookUrl: 'https://example.com/webhooks/horizon/generation',
  }),
});

const imageJob = await imagesResponse.json();
console.log('image job', imageJob.jobId, imageJob.statusUrl);

2. Handle synchronous completion

if (imageJob.status === 'completed' && imageJob.result) {
  console.log('Inline artifacts', imageJob.result.artifacts);
  // Persist or display immediately without polling.
}

3. Poll until finished

const loadStatus = async (statusUrl: string) => {
  let status;
  do {
    status = await fetchWithPayment(statusUrl).then((res) => res.json());
    if (status.state === 'processing') {
      await new Promise((resolve) => setTimeout(resolve, 2000));
    }
  } while (status.state === 'processing');

  if (status.state !== 'succeeded') {
    throw new Error(`Image job failed: ${status.error?.code ?? 'unknown'}`);
  }

  return status.result;
};

const artifacts = imageJob.result?.artifacts ?? (await loadStatus(imageJob.statusUrl)).artifacts;

4. Publish assets

  • Store signed URLs from artifacts in your media system for reuse across channels.
  • Track campaign or variant data in your own system to group results for experimentation.
  • Prefer registering a reusable webhook once you move to production to avoid per-job URLs.