Python SDK
Install the official Python SDK and use chat, web search, agents, and semantic search with HiNow.
Updated on Aug 09, 2026
The official Python SDK for the HiNow API. Requires Python 3.8 or newer and uses httpx under the hood, with both synchronous and asynchronous clients.
The API speaks the OpenAI protocol, and the SDK follows the same format. If you have already integrated with OpenAI, the call design is what you are familiar with.
pip install hinow-aiuv add hinow-aipoetry add hinow-aiKeep the 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 the code.
export HINOW_API_KEY="hi_sua_chave_aqui"import os
from hinow_ai import Hinow
# The key comes from HINOW_API_KEY when you pass nothing.
client = Hinow(api_key=os.environ["HINOW_API_KEY"])
response = client.chat.completions.create(
model="hinow/higenesis",
messages=[{"role": "user", "content": "Explain what an embedding is in one sentence."}],
max_tokens=120,
temperature=0,
)
print(response.choices[0].message.content)
print("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 does not make it clear that the prefix is missing. This applies to all: hinow/himax, hinow/hinova, hinow/higenesis.
With stream=True, the return value becomes a generator. Each chunk arrives in chunk.choices[0].delta.content, typed the same as the non-stream response.
from hinow_ai import Hinow
client = Hinow()
for chunk in client.chat.completions.create(
model="hinow/hinova",
messages=[{"role": "user", "content": "List three uses of an LLM, one per line."}],
stream=True,
):
print(chunk.choices[0].delta.content or "", end="", flush=True)
print()Responds immediately, 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 that type actually returns.
from hinow_ai import Hinow
client = Hinow()
# The shape of the answer follows `type`: news carries date and source,
# places carries phone and coordinates, autocomplete carries suggestions.
web = client.tools.search("plataforma de IA brasileira", country="br", lang="pt-br")
print(f"{web['total_results']} results · US$ {web['cost']}")
for r in web["results"][:3]:
print(f"{r['position']}. {r['title']}")
print(f" {r['url']}")
news = client.tools.search("artificial intelligence", type="news", country="br")
print(f"\n{news['results'][0]['source']} — {news['results'][0]['date']}")type | What comes in each result |
|---|---|
search, scholar, patents | position, title, url, snippet |
news | the above plus source, date, image_url |
images | link, image_url, thumbnail_url, width, height |
videos | channel, duration, date, thumbnail_url |
places | address, category, phone, website, rating, coordinates |
shopping | price, delivery, rating, source |
autocomplete | suggestions, an array of strings — there is no results |
Scans one or more websites for emails, phone numbers, and social profiles, returning the page where each contact appeared. This runs as a job because the scan takes time.
from hinow_ai import Hinow
client = Hinow()
# The crawl runs as a job. This method waits and raises if it fails,
# so "result" always exists when it returns.
job = client.tools.website_contacts_and_wait(
["https://teclia.com"],
max_depth=1,
max_links_per_page=5,
on_poll=lambda j: print("…", j["status"]),
)
print(f"cost US$ {job['cost']} · cached: {job['cached']}")
for contact in job["result"]["items"]:
print(f"{contact['type']}: {contact['value']} ({contact['sourceUrl']})")Running the same crawl twice is not charged again
The job returns with cached: true when the result came from the cache. If you want to control the cycle yourself, use tools.website_contacts() and track it with tools.jobs.retrieve(job['job_id']) — note that the field is job_id, not id.
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.
import json
from hinow_ai import Hinow
client = Hinow()
assistant = 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": 'ex.: "A-1001"'}},
"required": ["order_id"],
},
},
}],
)
thread = client.beta.threads.create()
client.beta.threads.messages.create(thread["id"], content="What is the status of order A-1001?")
run = client.beta.threads.runs.create_and_poll(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":
chamadas = run["required_action"]["submit_tool_outputs"]["tool_calls"]
run = client.beta.threads.runs.submit_tool_outputs(
thread["id"], run["id"],
tool_outputs=[
{"tool_call_id": c["id"], "output": json.dumps({"id": "A-1001", "status": "delivered"})}
for c in chamadas
],
)
run = client.beta.threads.runs.poll(thread["id"], run["id"])
messages = client.beta.threads.messages.list(thread["id"], order="desc", limit=1)
print(messages["data"][0]["content"][0]["text"]["value"])
client.beta.assistants.delete(assistant["id"])
client.beta.threads.delete(thread["id"])requires_action is not an error
It is the run handing control back to you to execute a function. That is why poll returns in this state instead of continuing to spin: read the required_action, call submit_tool_outputs, and resume tracking.
Upload files, group them into a vector store, and search by meaning. Indexing is asynchronous — searching before it finishes returns zero results, without an error.
import time
from hinow_ai import Hinow
client = Hinow()
text = b"Free shipping on orders over $200. Standard delivery takes 5 business days."
file = client.files.create(text, filename="shipping-policy.txt", purpose="assistants")
base = client.vector_stores.create(name="Politicas")
anexo = client.vector_stores.files.create(base["id"], file_id=file["id"])
# Indexing is asynchronous. Searching before it finishes returns nothing,
# with no error to say why.
while anexo["status"] == "in_progress":
time.sleep(1)
anexo = client.vector_stores.files.retrieve(base["id"], file["id"])
# The per-store filter is called rag_id. Passing vector_store_id raises no error:
# the search sweeps every document on the account.
hits = client.rag.search("qual o prazo de entrega?", rag_id=base["id"], top_k=2)
for a in hits["results"]:
print(f"{a['score']:.2f} {a['source']}: {a['text'][:60]}")
client.vector_stores.delete(base["id"])
client.files.delete(file["id"])The base filter is called rag_id
Passing vector_store_id does not raise an error: the search simply iterates over 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.
Each failure becomes its own class, so you can handle them by type instead of comparing message strings.
from hinow_ai import Hinow, AuthenticationError, RateLimitError, InsufficientBalanceError
client = Hinow(api_key="hi_invalid_key")
try:
client.get_balance()
except AuthenticationError as e:
print("invalid or revoked key:", e)
except RateLimitError:
print("rate limited; wait and try again")
except InsufficientBalanceError:
print("out of credit")| Resource | Purpose |
|---|---|
client.chat.completions | Chat, streaming, function calling, JSON mode |
embeddings | Vectors for semantic search |
images · audio · video | Generation |
models | Catalog, pricing, and features for each model |
tools | Web search and website contacts |
files | Document upload |
vector_stores | Searchable knowledge bases |
rag | Semantic search on your documents |
beta.assistants · beta.threads | Server-executed agents |
get_balance() | Account balance |
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,
});Up to version 1.0.7, the SDK packaged 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 with 2.0, everything goes at the root level, as the API expects. Your code doesn't change — but calls that silently ignored a limit now respect it, so review prompts that relied on the old behavior.
Two other fixes in the same version: file upload didn't work because the client fixed Content-Type: application/json, which broke multipart; and streaming chunks are now typed, so it's chunk.choices[0].delta.content instead of dictionary access.
Choosing between HiMax, HiNova, and HiGenesis
What each model does well, how much it costs, and how to write the prompt for each.

