InnoAI API Guide
InnoAI is a private AI inference API. It is compatible with the OpenAI API, so existing OpenAI SDKs and tools work by changing the base URL and the API key.
Base URL: https://api.innoai.net/v1
All requests and responses are JSON over HTTPS, except audio uploads (multipart form data) and generated audio (binary).
Quick start
curl https://api.innoai.net/v1/chat/completions \
-H "Authorization: Bearer $INNOAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "fast",
"messages": [{"role": "user", "content": "Say hello in five words."}]
}'
Authentication
Every API request needs an API key in the Authorization header:
Authorization: Bearer ina_...
- Keys start with
ina_and are 76 characters long. - Getting a key: sign in at https://api.innoai.net, open My dashboard, and use Create a key. You can give a key a name and an optional expiry in days.
- Shown once: the full key is displayed only at creation. InnoAI stores only a hash and cannot show it again. If you lose a key, revoke it and create a new one.
- Revocation: revoke a key from the dashboard at any time. Revoked, disabled and expired keys are rejected immediately with
401. - A key can be narrowed by your administrator to specific models or lower limits. A model the key may not use returns
404as if it did not exist. - Keep keys secret: never put them in client-side code or public repositories.
OpenAI compatibility
Use the official OpenAI SDKs with the InnoAI base URL.
Python
from openai import OpenAI
client = OpenAI(base_url="https://api.innoai.net/v1", api_key="ina_...")
resp = client.chat.completions.create(
model="fast",
messages=[{"role": "user", "content": "Summarise the water cycle in one sentence."}],
)
print(resp.choices[0].message.content)
Node.js
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.innoai.net/v1", apiKey: process.env.INNOAI_API_KEY });
const resp = await client.chat.completions.create({
model: "fast",
messages: [{ role: "user", content: "Summarise the water cycle in one sentence." }],
});
console.log(resp.choices[0].message.content);
Responses follow the OpenAI formats. Some responses carry additional fields (for example stop_reason or token_ids); ignore fields you do not use. The model field in a response is always the model name you requested. system_fingerprint is always null.
Models
| Model | Type | Best for | Thinking | Max output tokens |
|---|---|---|---|---|
fast | chat | Quick answers, extraction, drafting, high volume | Off | 32,768 |
deep | chat | Harder problems that benefit from step-by-step reasoning | On | 131,072 |
Both models use the same request format. Choosing the model is how you choose whether the model thinks first: deep returns its reasoning in a separate reasoning field and reports reasoning tokens in usage.
Your plan may set a lower maximum output (see Limits).
List models
GET /v1/models returns the models your key may use, with their capabilities and prices.
curl https://api.innoai.net/v1/models -H "Authorization: Bearer $INNOAI_API_KEY"
{
"object": "list",
"data": [
{
"id": "deep", "object": "model", "owned_by": "innoai", "type": "chat",
"served_model": "InnoAI Deep 2026-09",
"description": "Harder problems that benefit from step-by-step reasoning",
"context_tokens": 262144,
"max_output_tokens": 131072,
"max_request_bytes": 20971520,
"modalities": {"input": ["text"], "output": ["text"]},
"reasoning": true,
"unit": "tokens",
"pricing": {
"usd": {"input_per_1m": "10.000000", "output_per_1m": "20.000000", "reasoning_per_1m": "20.000000", "per_request": "0.000000"},
"credits": {"input_per_1k": 2.0, "output_per_1k": 6.0, "reasoning_per_1k": 6.0, "per_request": 0.0}
}
}
]
}
| Field | Meaning |
|---|---|
type | Model type; decides which endpoint accepts it (see Endpoints) |
served_model | Public label of the model that actually answers. Also sent as the x-served-model response header; quote it with x-request-id on receipts |
context_tokens | Context window in tokens, null if not published |
max_output_tokens | Largest max_tokens you can get: the model's cap, narrowed by your plan |
max_request_bytes | Request body limit for this model |
modalities | Input and output modalities (text, audio, embedding) |
reasoning | Whether the model thinks before answering (reasoning tokens are billed at their own rate) |
unit | What usage is metered in: tokens, audio seconds or characters |
pricing.usd | Pay-as-you-go prices in USD, as 6-decimal strings. Keys depend on the type: per 1M tokens (input_per_1m, output_per_1m, reasoning_per_1m), audio_per_minute for speech-to-text, chars_per_1m for text-to-speech, plus per_request |
pricing.credits | Subscription-plan credit prices per 1K tokens / per audio second / per 1K characters, plus per_request |
The list only contains models your key may use, so two keys can see different lists.
Endpoints
| Method | Path | Model type | Status |
|---|---|---|---|
| POST | /v1/chat/completions | chat | Available (fast, deep) |
| POST | /v1/completions | chat or completion | Available (fast, deep) |
| GET | /v1/models | – | Available |
| POST | /v1/embeddings | embedding | Coming soon |
| POST | /v1/rerank | rerank | Coming soon |
| POST | /v1/decide | decision | Coming soon |
| POST | /v1/entities | entity extraction | Coming soon |
| POST | /v1/classify | classification | Coming soon |
| POST | /v1/guard | safety guard | Coming soon |
| POST | /v1/audio/transcriptions | speech-to-text | Coming soon |
| POST | /v1/audio/speech | text-to-speech | Coming soon |
Each endpoint accepts only models of its type. Sending a chat model to /v1/embeddings, for example, returns 400 with "model does not support this endpoint". "Coming soon" endpoints are live in the API but no model of that type is offered yet; they appear in /v1/models when one is.
Every request body must be a JSON object with a model field (for audio uploads, a model form field). The maximum request size is 20 MB.
Chat completions
POST /v1/chat/completions
Request
{
"model": "fast",
"messages": [
{"role": "system", "content": "You are concise."},
{"role": "user", "content": "Say hello in five words."}
],
"max_tokens": 40
}
Response (fast)
{
"id": "chatcmpl-01a13230-1197-7034-898d-cba9d33c008a",
"object": "chat.completion",
"created": 1791843373,
"model": "fast",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Greetings to you, my friend.", "reasoning": null},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 8,
"total_tokens": 23,
"completion_tokens_details": {"reasoning_tokens": 0}
}
}
Response (deep): the thinking is in message.reasoning, the answer in message.content. Reasoning tokens are included in completion_tokens and broken out in completion_tokens_details.reasoning_tokens.
{
"id": "chatcmpl-01a13230-129d-74b1-8711-e2ba20f89ce2",
"object": "chat.completion",
"model": "deep",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "12 × 12 = **144**", "reasoning": "12 × 12 = 144"},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 16,
"completion_tokens": 26,
"total_tokens": 42,
"completion_tokens_details": {"reasoning_tokens": 11}
}
}
Streaming
Set "stream": true to receive server-sent events. With deep, reasoning arrives first as delta.reasoning, then the answer as delta.content. The last event before data: [DONE] always carries usage (with an empty choices list): InnoAI turns on stream_options.include_usage for every streamed request.
curl -N https://api.innoai.net/v1/chat/completions \
-H "Authorization: Bearer $INNOAI_API_KEY" -H "Content-Type: application/json" \
-d '{"model": "deep", "stream": true, "messages": [{"role": "user", "content": "What is 2+2?"}]}'
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"deep","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"deep","choices":[{"index":0,"delta":{"reasoning":"Simple"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"deep","choices":[{"index":0,"delta":{"content":"2 + 2 = **4**"},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"deep","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":14,"total_tokens":28,"completion_tokens_details":{"reasoning_tokens":3}}}
data: [DONE]
Python:
stream = client.chat.completions.create(model="deep", stream=True,
messages=[{"role": "user", "content": "What is 2+2?"}])
for chunk in stream:
if chunk.choices:
delta = chunk.choices[0].delta
print(getattr(delta, "reasoning", None) or delta.content or "", end="")
elif chunk.usage:
print("\n", chunk.usage)
Completions
POST /v1/completions takes a prompt instead of messages and works with fast and deep.
{"model": "fast", "prompt": "The capital of France is", "max_tokens": 8}
{
"id": "cmpl-01a13230-141f-72de-9409-93faa476b80d",
"object": "text_completion",
"model": "fast",
"choices": [{"index": 0, "text": " one of the most popular tourist destinations in", "finish_reason": "length"}],
"usage": {"prompt_tokens": 5, "completion_tokens": 8, "total_tokens": 13}
}
Embeddings, rerank, decide, entities, classify, guard
Coming soon. These endpoints accept a JSON object with a model of the matching type; the request and response formats will be documented here when a model is available. Embeddings will follow the OpenAI embeddings format.
Audio transcriptions (speech-to-text)
Coming soon. POST /v1/audio/transcriptions will take multipart/form-data with the audio file and a model field, as in the OpenAI API. Usage is metered in seconds of audio.
Audio speech (text-to-speech)
Coming soon. POST /v1/audio/speech will take JSON with model and input (the text), as in the OpenAI API, and stream back audio. Usage is metered in characters of input.
Parameters InnoAI sets for you
- Model name. You always use the InnoAI model name (
fast,deep); responses echo it. - Thinking. Thinking is set by the model you choose: off for
fast, on fordeep. Any thinking setting you send is overridden. - Maximum output.
max_tokensandmax_completion_tokensare clamped to the model's maximum (32,768 forfast, 131,072 fordeep) and to your plan's maximum, if it has one. If you send neither,max_tokensis set to the applicable maximum. Setmax_tokensyourself to keep responses (and costs) bounded. - Streaming usage. Streamed requests always get a final usage event (see Streaming).
Limits
Limits come from your plan (and any narrower settings your administrator applies to your account or key). The dashboard shows your current limits and usage.
| Limit | How it works | When exceeded |
|---|---|---|
| Requests per minute | Rolling one-minute window | 429 rate_limit_error, with Retry-After |
| Tokens per minute / per day | Input + output tokens, counted when a request completes | 429 rate_limit_error, with Retry-After |
| Concurrent requests | Your requests in flight at once | 429 rate_limit_error "too many concurrent requests", Retry-After: 1 |
| Max output tokens | Output is clamped (see above) | Not an error |
| Session budget | Credits per session. A session starts with your first request and lasts 5 hours; the next request after it ends starts a new one | 429 usage_limit_error, limit: "session" |
| Weekly budget | Credits used in the last 7 days (rolling, hourly granularity) | 429 usage_limit_error, limit: "weekly" |
| Monthly cap | Credits per calendar month in your account's time zone | 429 usage_limit_error, limit: "monthly" |
| Daily / monthly spend cap (pay-as-you-go) | USD spent today / this calendar month, if your account sets a cap | 429 usage_limit_error, limit: "daily_spend" / "monthly_spend" |
| Prepaid balance (pay-as-you-go) | Balance must be above zero (or your account's minimum) | 402 insufficient_balance |
| Model capacity | Shared capacity across all customers. When a model is busy, your request waits in a queue (currently up to 10 seconds) | 429 capacity_error, Retry-After: 5 |
Usage-limit errors include resets_at (UTC, RFC 3339) and a Retry-After header with the seconds until then:
{"error": {"message": "session usage limit reached for your plan (resets at the time in resets_at)",
"type": "usage_limit_error", "limit": "session", "resets_at": "2026-10-13T03:16:13Z"}}
Budgets are checked before a request starts and charged when it finishes, so a request that starts within budget always completes; the counters can briefly go past the limit by the cost of requests already in flight.
Response headers
| Header | When | Meaning |
|---|---|---|
x-request-id | Requests that reached a model | Unique request id. Quote it when contacting support. |
x-served-model | Requests that reached a model | Public label of the model that produced the response (same as served_model in /v1/models). The body's model field still echoes the name you requested. |
x-ratelimit-session-limit / -remaining / -reset | Plans with a session budget | Session budget in credits, credits remaining, and when the session ends (UTC). |
x-ratelimit-weekly-limit / -remaining / -reset | Plans with a weekly budget | Weekly budget, remaining, and when credit is next returned (UTC; the current time if nothing has been used). |
x-ratelimit-monthly-limit / -remaining / -reset | Plans with a monthly cap | Monthly cap, remaining, and when the month resets (UTC). |
x-balance-remaining-usd | Pay-as-you-go accounts | Available balance in USD (6 decimals) when the request was admitted, before its own cost. |
Retry-After | 429 responses | Seconds to wait before retrying. |
Credit values in headers have up to 3 decimals, e.g. x-ratelimit-session-remaining: 49999.961.
Billing and usage
Each account is on one of two billing types.
Subscription plans have a flat price and credit budgets (session, weekly and/or monthly). Each request costs credits based on what it used:
- Chat and completion models: a rate per 1,000 input tokens, per 1,000 output tokens, and per 1,000 reasoning tokens (reasoning tokens are charged at the reasoning rate instead of the output rate).
- Speech-to-text: per second of audio. Text-to-speech: per 1,000 characters of input.
- Some models may also have a per-request charge.
deep costs more credits per token than fast.
Pay-as-you-go accounts have a prepaid USD balance. Each completed request is debited at the model's USD prices (per 1M input, output and reasoning tokens; per minute of audio; per 1M characters; per request), exact to $0.000001. Requests are refused with 402 when the balance runs out; the request that crosses zero may take the balance slightly below zero by at most its own cost. Your administrator adds credit; the dashboard shows your balance, spend this month, cost per model and recent charges.
How usage is measured: tokens are the counts reported by the model in usage. If a stream ends before usage is reported (for example, you disconnect), the request is still charged: input is estimated from the request size and output as one token per event received. Requests rejected before reaching a model (400, 401, 402, 404, 413, 429) are not charged, and neither are requests the model rejects with an error. A stream that breaks partway is charged for what was produced.
Errors
Errors from InnoAI have this shape:
{"error": {"message": "rate limit exceeded", "type": "rate_limit_error"}}
Messages never repeat any part of your request.
| Status | type | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request_error | Body is not a JSON object, model is missing, or the model does not support this endpoint | Fix the request |
| 401 | authentication_error | Missing, invalid, revoked, disabled or expired key | Check the key; create a new one if needed |
| 402 | insufficient_balance | Pay-as-you-go balance used up. Includes balance_usd | Ask your administrator to add credit |
| 403 | billing_not_allowed | The key is restricted to a billing source your account is not set up for | Ask your administrator |
| 404 | not_found_error | Unknown model, or not allowed for this key | Use a model from GET /v1/models |
| 404 | not_found_error | Unknown endpoint path ("unknown endpoint") | Check the path against the Endpoints table |
| 413 | invalid_request_error | Request body too large for this model | Send a smaller body |
| 429 | rate_limit_error | Requests/tokens per minute, tokens per day, or concurrency exceeded | Wait Retry-After seconds, then retry |
| 429 | usage_limit_error | Plan budget or spend cap reached; limit is session, weekly, monthly, daily_spend or monthly_spend | Wait until resets_at, or change plan |
| 429 | capacity_error | The model is at capacity right now | Retry after Retry-After (5 s) with backoff |
| 500 | api_error | Internal error | Retry with backoff |
| 502 | api_error | Model unavailable | Retry with backoff |
| 504 | api_error | Model timed out (no data for 300 seconds) | Retry; prefer streaming for long outputs |
If the model itself rejects a request (for example, an invalid message format), its error is passed through with its own status code; these follow the OpenAI error format. Internal detail (hosts, paths, stack traces) is removed; a backend fault that would only expose its internals is reported as "The model rejected the request (HTTP nnn)". If you close the connection before a response finishes, InnoAI records the request as client-closed (status 499 in your usage) and charges what was used up to that point.
Privacy
- No content logging or storage. InnoAI does not log or store the content of requests or responses: no prompts, messages, completions, audio or text-to-speech input. Content passes through memory only, for the duration of the request.
- Content-free usage records. For billing and limits InnoAI records per request: time, account, user, key, model, status, whether it streamed, token counts (input, output, reasoning), request size in bytes, duration and cost. Never content.
- Keys are stored only as hashes.
- Usage records are retained according to your account's retention setting; aggregated hourly totals are kept for reporting.
Best practices
- Retry with backoff. Retry
429,500,502and504with exponential backoff and jitter (e.g. 1 s, 2 s, 4 s, up to about 30 s). Always honourRetry-Afterwhen present. Do not retry400,401,402,404or413unchanged. Forusage_limit_error, wait untilresets_at. - Stream long responses, especially with
deep. You see output immediately and long generations are not cut off by the 300-second idle timeout. - Set timeouts. Use a connect timeout of about 10 s and a read timeout of at least 300 s for non-streaming
deeprequests. The OpenAI SDKs accept atimeoutoption. - Bound output with
max_tokensto control latency and cost. - Respect concurrency. Keep in-flight requests at or below your plan's concurrency; queue the rest client-side.
- Watch the headers.
x-ratelimit-*-remainingandx-balance-remaining-usdtell you when you are close to a limit. - Log
x-request-idwith your own request logs to make support quick.
Changelog
-
2026-10-16 —
GET /v1/modelsnow carries capabilities and prices (served_model,context_tokens,max_output_tokens,modalities,reasoning,pricing). Newx-served-modelresponse header. Unknown paths return a JSON404 not_found_error. -
2026-10-12 — Pay-as-you-go billing: prepaid USD balance,
402 insufficient_balance,x-balance-remaining-usd, daily/monthly spend caps.system_fingerprintis now alwaysnull. -
2026-10-12 —
deepmodel (reasoning on, 131,072 max output). Plans with session, weekly and monthly credit budgets andx-ratelimit-*headers. Shared model capacity with queueing andcapacity_error. -
Initial release — OpenAI-compatible API with
fast.