Skip to content

TypeScript SDK

Install the official TypeScript SDK and use chat, web search, agents, and semantic search with HiNow.

Updated on Aug 09, 2026

The official TypeScript SDK for the HiNow API. No dependencies, built for Node 18 or newer, and also runs on Deno and Bun.

The API speaks the OpenAI protocol, and the SDK follows the same format. If you've integrated with OpenAI before, the call structure will look familiar.

Installation

terminalbash
npm install hinow-ai

Keep your key in HINOW_API_KEY and the SDK will find it automatically. Passing apiKey in the constructor also works, but avoid leaving the value in your code.

terminalbash
export HINOW_API_KEY="hi_sua_chave_aqui"

The first call

primeira-chamada.tstypescript
import { Hinow } from 'hinow-ai';

// The key comes from the HINOW_API_KEY environment variable when you pass nothing.
const client = new Hinow({ apiKey: process.env.HINOW_API_KEY });

const response = await client.chat.completions.create({
  model: 'hinow/higenesis',
  messages: [{ role: 'user', content: 'Explain what an embedding is in one sentence.' }],
  max_tokens: 120,
  temperature: 0,
});

console.log(response.choices[0].message.content);
console.log('tokens:', response.usage.total_tokens);

The hinow/ prefix is part of the model name

Passing himax instead of hinow/himax returns 404 model_not_found, and the message doesn't clearly indicate that the prefix is missing. This applies to all models: hinow/himax, hinow/hinova, hinow/higenesis.

Showing the response as it arrives

With stream: true, the return value becomes an async iterable. In a chat interface, this changes the perception of speed more than switching models does.

streaming.tstypescript
import { Hinow } from 'hinow-ai';

const client = new Hinow();

const stream = await client.chat.completions.create({
  model: 'hinow/hinova',
  messages: [{ role: 'user', content: 'List three uses of an LLM, one per line.' }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}

Responds instantly, without a job to track, at $0.005 per call. The return type follows the type, so the compiler only lets you access the fields that specific type actually returns.

busca-web.tstypescript
import { Hinow } from 'hinow-ai';

const client = new Hinow();

// The return type follows `type`: `news` gives you date and source,
// em `places` tem telefone e coordenadas, em `autocomplete` tem suggestions.
const web = await client.tools.search({
  query: 'plataforma de IA brasileira',
  country: 'br',
  lang: 'pt-br',
});

console.log(`${web.total_results} results · US$ ${web.cost}`);
for (const r of web.results.slice(0, 3)) {
  console.log(`${r.position}. ${r.title}`);
  console.log(`   ${r.url}`);
}

const news = await client.tools.search({ query: 'artificial intelligence', type: 'news' });
console.log(`\n${news.results[0].source} — ${news.results[0].date}`);
typeWhat comes in each result
search, scholar, patentsposition, title, url, snippet
newsthe above plus source, date, image_url
imageslink, image_url, thumbnail_url, width, height
videoschannel, duration, date, thumbnail_url
placesaddress, category, phone, website, rating, coordinates
shoppingprice, delivery, rating, source
autocompletesuggestions, an array of strings — no results field

Website contacts

Scans one or more sites for emails, phone numbers, and social profiles, returning the page where each contact was found. This runs as a job because scanning takes time.

contatos-site.tstypescript
import { Hinow } from 'hinow-ai';

const client = new Hinow();

// The crawl runs as a job. This method waits and throws if the job fails,
// so `result` always exists when it returns.
const job = await client.tools.websiteContactsAndWait(
  { websites: ['https://teclia.com'], maxDepth: 1, maxLinksPerPage: 5 },
  { onPoll: (j) => console.log('…', j.status) },
);

console.log(`cost US$ ${job.cost} · cached: ${job.cached}`);
for (const c of job.result!.items) {
  console.log(`${c.type}: ${c.value}  (${c.sourceUrl})`);
}

Repeating an identical execution doesn't charge again

The job returns with cached: true when the result came from the cache. If you want to control the lifecycle yourself, use tools.websiteContacts() and track it with tools.jobs.retrieve(job.job_id) — note that the field is job_id, not id.

Agents

Assistants, threads, and runs in the same format as the OpenAI API. The model decides when to call your functions; the run pauses, you execute, and return the result.

agente.tstypescript
import { Hinow } from 'hinow-ai';

const client = new Hinow();

const assistant = await client.beta.assistants.create({
  model: 'hinow/hinova',
  name: 'Order support',
  instructions: 'You look up order status. Always use the tool. Answer in one sentence.',
  tools: [{
    type: 'function',
    function: {
      name: 'get_order',
      description: 'Lookup um order pelo identificador.',
      parameters: {
        type: 'object',
        properties: { order_id: { type: 'string', description: 'Identificador, ex.: "A-1001".' } },
        required: ['order_id'],
      },
    },
  }],
});

const thread = await client.beta.threads.create();
await client.beta.threads.messages.create(thread.id, {
  role: 'user',
  content: 'What is the status of order A-1001?',
});

let run = await client.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant.id });

// `requires_action` is not an error: it is the run handing control back to you.
if (run.status === 'requires_action') {
  const calls = run.required_action!.submit_tool_outputs.tool_calls;

  run = await client.beta.threads.runs.submitToolOutputs(thread.id, run.id, {
    tool_outputs: calls.map((call) => ({
      tool_call_id: call.id,
      output: JSON.stringify({ id: 'A-1001', status: 'delivered', total: 349.9 }),
    })),
  });
  run = await client.beta.threads.runs.poll(thread.id, run.id);
}

const messages = await client.beta.threads.messages.list(thread.id, { order: 'desc', limit: 1 });
console.log(messages.data[0].content[0].text?.value);

await client.beta.assistants.del(assistant.id);
await client.beta.threads.del(thread.id);

requires_action is not an error

It is the run handing control back to you to execute a function. That's why poll returns in this state instead of continuing to spin: read the required_action, call submitToolOutputs, and resume tracking.

Upload files, bundle them into a vector store, and search by meaning. Indexing is asynchronous — searching before it finishes returns zero results, without error.

conhecimento.tstypescript
import { Hinow } from 'hinow-ai';

const client = new Hinow();

const text = 'Free shipping on orders over $200. Standard delivery takes 5 business days.';

const file = await client.files.create({
  file: new Blob([text], { type: 'text/plain' }),
  filename: 'shipping-policy.txt',
  purpose: 'assistants',
});

const store = await client.vectorStores.create({ name: 'Políticas' });
let anexo = await client.vectorStores.files.create(store.id, { file_id: file.id });

// Indexing is asynchronous: the attachment comes back as `in_progress`. Searching before
// it finishes returns nothing, with no error at all.
while (anexo.status === 'in_progress') {
  await new Promise((r) => setTimeout(r, 1000));
  anexo = await client.vectorStores.files.retrieve(store.id, file.id);
}

// The parameter that narrows the search to one store is `rag_id`. Passing
// `vector_store_id` raises no error: the search sweeps the whole account.
const hits = await client.rag.search({
  query: 'qual o prazo de entrega?',
  rag_id: store.id,
  top_k: 2,
});

for (const h of hits.results) {
  console.log(`${h.score.toFixed(2)}  ${h.source}: ${h.text.slice(0, 60)}`);
}

await client.vectorStores.del(store.id);
await client.files.del(file.id);

The base filter is called rag_id

Passing vector_store_id does not throw an error: the search simply scans all documents in the account instead of the intended base. This is the kind of detail that makes it seem like RAG is returning garbage.

Errors

Each failure becomes its own class, so you can handle them by type instead of comparing message strings.

erros.tstypescript
import { Hinow, AuthenticationError, RateLimitError, InsufficientBalanceError } from 'hinow-ai';

const client = new Hinow({ apiKey: 'hi_invalid_key' });

try {
  await client.getBalance();
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.log('invalid or revoked key:', error.message);
  } else if (error instanceof RateLimitError) {
    console.log('rate limited; wait and try again');
  } else if (error instanceof InsufficientBalanceError) {
    console.log('out of credit');
  } else {
    throw error;
  }
}

Everything the client exposes

ResourcePurpose
chat.completionsChat, streaming, function calling, JSON mode
embeddingsVectors for semantic search
images · audio · videoGeneration
modelsCatalog, pricing, and features for each model
toolsWeb search and website contacts
filesDocument upload
vectorStoresSearchable knowledge bases
ragSemantic search on your documents
beta.assistants · beta.threadsServer-executed agents
getBalance()Account balance

Configuration

cliente.tstypescript
const client = new Hinow({
  apiKey: process.env.HINOW_API_KEY,  // ou deixe em branco e use a variável
  baseURL: 'https://api.hinow.ai',    // ou HINOW_BASE_URL
  timeout: 120_000,                   // milissegundos
  maxRetries: 3,
});

Coming from version 1.x

Up to version 1.0.7, the SDK wrapped temperature, max_tokens, top_p, and response_format inside a parameters object before sending. The API accepts this format and ignores it, so these options never took effect: requesting max_tokens: 10 returned the full response.

Starting from 2.0, everything goes at the root level, as the API expects. Your code doesn't change — but calls that silently ignored a limit will now respect it, so review prompts that relied on the old behavior.

Choosing between HiMax, HiNova, and HiGenesis

What each model does well, how much it costs, and how to write the prompt for each one.

Was this page helpful?