TypeScript

A typed helper for creating and polling OptimAI Search from server-side TypeScript.

type SearchMode = "search" | "agent"

type SearchRequest = {
  query: string
  search_mode?: SearchMode
}

type Search = {
  id: string
  status: string
  result?: {
    answer?: string
    citations?: Array<{ title?: string; url: string; snippet?: string }>
  }
}

const baseUrl = "https://api-onchain.optimai.network/external/v1"

export async function searchOptimAI(
  input: SearchRequest,
  idempotencyKey: string,
): Promise<Search> {
  const apiKey = process.env.OPTIMAI_API_KEY

  if (!apiKey) throw new Error("OPTIMAI_API_KEY is required")

  const createdResponse = await fetch(`${baseUrl}/search`, {
    method: "POST",
    headers: {
      "X-API-Key": apiKey,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(input),
  })

  if (!createdResponse.ok) {
    throw new Error(
      `Search creation failed: ${createdResponse.status} ${await createdResponse.text()}`,
    )
  }

  const created = (await createdResponse.json()) as Search

  while (true) {
    const response = await fetch(`${baseUrl}/search/${created.id}`, {
      headers: { "X-API-Key": apiKey },
    })

    if (!response.ok) {
      throw new Error(`Search polling failed: ${response.status} ${await response.text()}`)
    }

    const search = (await response.json()) as Search
    if (search.status === "completed") return search
    if (["failed", "cancelled"].includes(search.status)) {
      throw new Error(`Search ended with status ${search.status}`)
    }

    await new Promise((resolve) => setTimeout(resolve, 2_000))
  }
}

Omit search_mode for Standard Search (1 credit), or set it to "agent" for longer multi-step web, social, and crypto research (2 credits). Agent mode is text-only. Use a different idempotency key when changing the request or mode.