Start
Base URL and core concepts.
Base URL
https://api.nuluai.com/v1
Auth
Bearer sk_wat_...
Model
nulu-5-ultra
OpenAI compatibility
Endpoints designed for OpenAI-compatible clients.
The gateway exposes OpenAI-compatible endpoints for model listing and chat completions. You can use it as a custom provider in SDKs and tools like opencode with `baseURL: https://api.nuluai.com/v1`.
Public model IDs are lowercase and case-sensitive. Use exactly `nulu-5-pro`, `nulu-5-ultra`, `nulu-image-1.5`, `nulu-image-3` in OpenAI-compatible clients, opencode, and external apps.
| Endpoint | Status | Notes |
|---|---|---|
| GET /v1/models | Supported | Returns public IDs, not real provider model names. |
| POST /v1/chat/completions | Supported | OpenAI chat completions compatible format. |
| POST /v1/images/generations | Supported | Image generation with `nulu-image-3` (JSON). |
| POST /v1/images/edits | Supported | Editing/variations via `multipart/form-data` with image + prompt. |
| stream: true | Supported | Forwards provider SSE and stores the sanitized response. |
Authentication
Every request requires a Nulu API key.
Authorization: Bearer sk_wat_your_keyCreate keys in the `API Key` dashboard section. Full secrets are shown once and then stored hashed.
Models
List of public models available to your users.
curl https://api.nuluai.com/v1/models \
-H "Authorization: Bearer sk_wat_your_key"Copy IDs exactly as shown here.
| Public ID | Name | Capabilities | Thinking |
|---|---|---|---|
nulu-5-pro | Nulu 5 Pro | Chat, tools | Optional |
nulu-5-ultra | Nulu 5 Ultra | Chat, tools, vision, advanced reasoning | Optional |
nulu-image-1.5 | Nulu Image 1.5 (Legacy) | Image generation and editing | Unavailable |
nulu-image-3 | Nulu Image 3 | Image generation and editing | Unavailable |
Chat completions
Send messages and receive model responses.
cURL
curl https://api.nuluai.com/v1/chat/completions \
-H "Authorization: Bearer sk_wat_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "nulu-5-ultra",
"messages": [
{"role": "system", "content": "Answer helpfully and concisely."},
{"role": "user", "content": "Explain what a managed AI API is."}
],
"temperature": 0.7,
"max_tokens": 500
}'OpenAI SDK
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk_wat_your_key",
baseURL: "https://api.nuluai.com/v1"
});
const response = await client.chat.completions.create({
model: "nulu-5-ultra",
messages: [{ role: "user", content: "Say hello in English" }]
});The `model` field must be one of your public model IDs configured in Admin > Models. Nulu API privately maps it to the real upstream model.
Vision: Nulu 5 Ultra accepts images (JPEG, PNG, GIF, WebP; billed as input tokens). It also supports tools and configurable reasoning with `low`, `medium`, `high`, and `max`.
Images
Generate and edit images with Nulu Image 3.
Use the public ID `nulu-image-3`. The platform routes internally to the image engine — do not send third-party model names.
Generation: `POST /v1/images/generations` with JSON (`model`, `prompt`, `size`, `quality`, `n`). Editing: `POST /v1/images/edits` with `multipart/form-data` (`image`, `prompt`, optional `mask`). Responses follow the OpenAI format: `data[].url` or `data[].b64_json`.
Nulu Image 3 per-image price (generate or edit): $0.027294–$0.051177. A flat per-image price, independent of size and quality.
Nulu Image 1.5 (legacy) is billed from measured tokens, roughly $0.009505–$0.045356 per image depending on size and quality.
Nulu Image 1.5 token rates
| Size | Quality | Generate | Edit |
|---|---|---|---|
| 1024×1024 | Medium | $0.009567 | $0.009567 |
| 1024×1024 | Low | $0.031368 | $0.031368 |
| 1024×1024 | High | $0.031436 | $0.031436 |
| 1536×1024 | Low | $0.045268 | $0.045268 |
| 1536×1024 | Medium | $0.045356 | $0.045356 |
| 1536×1024 | High | $0.009505 | $0.009505 |
| 2048×2048 | Low | $0.031436 | $0.031436 |
| 2048×2048 | Medium | $0.031436 | $0.031436 |
| 2048×2048 | High | $0.031436 | $0.031436 |
Generate image
curl https://api.nuluai.com/v1/images/generations \
-H "Authorization: Bearer sk_wat_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "nulu-image-3",
"prompt": "Modern wooden chair in a bright studio",
"size": "1024x1024",
"quality": "medium",
"n": 1
}'Edit image
curl https://api.nuluai.com/v1/images/edits \
-H "Authorization: Bearer sk_wat_your_key" \
-F "model=nulu-image-3" \
-F "prompt=Same chair in vivid red" \
-F "[email protected]" \
-F "size=1024x1024"OpenAI SDK
import OpenAI from "openai";
import fs from "node:fs";
const client = new OpenAI({
apiKey: "sk_wat_your_key",
baseURL: "https://api.nuluai.com/v1"
});
// Generate
const generated = await client.images.generate({
model: "nulu-image-3",
prompt: "Minimal kitchen product on a white background",
size: "1024x1024"
});
// Edit (multipart)
const edited = await client.images.edit({
model: "nulu-image-3",
image: fs.createReadStream("product-photo.png"),
prompt: "Same scene with a red finish"
});| Field | Generation | Editing |
|---|---|---|
| model | Yes — `nulu-image-3` | Yes — `nulu-image-3` |
| prompt | Yes | Yes |
| image | No | Yes — PNG/JPEG/WebP file |
| mask | No | Optional — transparent PNG |
| size | Optional | Optional |
| quality | Optional (`low`, `medium`, `high`) | Optional |
| n | Optional (1–10) | Optional (1–10) |
Thinking
Thinking is an option the user enables per request.
By default Nulu API sends `thinking: { type: "disabled" }` so users control when they pay for reasoning. To enable it, send `reasoning_effort: "low"`, `"medium"`, `"high"`, or `"max"`; the level also turns thinking on automatically. You can also send `thinking: { type: "enabled" }` without a level to use the provider default.
Thinking uses the same per-token rates as the model. There is no separate multiplier, but it can cost more because `reasoning_content` increases billed output tokens.
Enable thinking
const response = await client.chat.completions.create({
model: "nulu-5-ultra",
messages: [{ role: "user", content: "Solve this problem step by step" }],
reasoning_effort: "high", // "low" | "medium" | "high" | "max"
extra_body: {
thinking: { type: "enabled" }
}
});Disable thinking
const response = await client.chat.completions.create({
model: "nulu-5-ultra",
messages: [{ role: "user", content: "Answer directly" }],
extra_body: {
thinking: { type: "disabled" }
}
});JSON mode
Structured output as valid JSON.
Send `response_format: { type: "json_object" }`, include the word JSON in the prompt, and provide an example of the expected format. Set enough `max_tokens` to avoid truncation.
const response = await client.chat.completions.create({
model: "nulu-5-ultra",
response_format: { type: "json_object" },
messages: [
{ role: "system", content: "Return valid JSON only." },
{ role: "user", content: "Return a json object with question and answer." }
]
});Tool calls
Allows the model to request external functions defined by your app.
The model does not execute functions. It returns `tool_calls`; your app runs the function, appends a `role: "tool"` message with `tool_call_id`, then calls the model again.
In thinking mode with tool calls, preserve and send back the assistant message `reasoning_content` in later turns to avoid provider errors.
const response = await client.chat.completions.create({
model: "nulu-5-ultra",
messages: [{ role: "user", content: "What is the weather in Caracas?" }],
tools: [{
type: "function",
function: {
name: "get_weather",
description: "Get weather for a location",
parameters: {
type: "object",
properties: { location: { type: "string" } },
required: ["location"]
}
}
}]
});Multi-round chat
The API is stateless: send the full conversation history each time.
Append the previous assistant response to `messages`, then append the new user message and make another call.
const messages = [
{ role: "user", content: "What is the highest mountain?" }
];
const first = await client.chat.completions.create({ model: "nulu-5-ultra", messages });
messages.push(first.choices[0].message);
messages.push({ role: "user", content: "What is the second?" });
const second = await client.chat.completions.create({ model: "nulu-5-ultra", messages });Context cache
Provider context caching is enabled by default.
No code changes are required. If a request reuses a persisted prefix, part of the input can count as a cache hit and is charged at the cached-input rate.
The `usage` response can include `prompt_cache_hit_tokens` or `prompt_tokens_details.cached_tokens`; Nulu API uses those tokens to apply cached-input pricing.
Supported parameters
Fields compatible with the chat completions API.
| Field | Required | Description |
|---|---|---|
| model | Yes | Public model ID. |
| messages | Yes | Messages with `role` and `content`. |
| temperature | No | Controls randomness. Typical range: 0 to 2. |
| top_p | No | Nucleus sampling. Use this or temperature for simple control. |
| max_tokens | No | Maximum generated token limit. |
| stream | No | If `true`, returns OpenAI-compatible SSE events. |
| thinking | No | Use `{ type: "enabled" }` to enable reasoning per request; disabled by default. |
| reasoning_effort | No | Accepts `low`, `medium`, `high`, or `max`. Sending it turns thinking on automatically. |
| response_format | No | Use `{ type: "json_object" }` for JSON mode. |
| tools/tool_choice | No | Tool calling when supported by the provider/model. |
Usage, tokens, and storage
How usage is measured and what is stored.
Nulu API reads token usage when the provider returns it. If usage is missing, it estimates tokens as a fallback.
Billing
Prepaid balance, pricing, and deductions.
Chat models deduct balance by tokens (input, cached input, and output). Nulu Image 1.5 deducts per image based on size and quality; generation and editing use the same rate.
Nulu 5 Ultra pricing (per 1M tokens)
| Type | Off-peak | Peak |
|---|---|---|
| Input — cache miss | $0.348 | $0.696 |
| Input — cache hit | $0.00696 | $0.01392 |
| Output | $1.392 | $2.784 |
Nulu 5 Pro pricing (per 1M tokens)
| Type | Off-peak | Peak |
|---|---|---|
| Input — cache miss | $1.5312 | $3.0624 |
| Input — cache hit | $0.05104 | $0.10208 |
| Output | $4.5936 | $9.1872 |
Peak hours are Monday–Friday, 01:00–04:00 and 06:00–10:00 UTC. Weekends, Chinese public holidays and all other hours are off-peak. During peak hours the per-token rate doubles (2×). Applies to Nulu 5 Ultra and Nulu 5 Pro.
Billing example (Nulu 5 Ultra)
A request with 1M uncached input tokens + 1M output tokens costs $0.348 + $1.392 = $1.740 off-peak, and $0.696 + $2.784 = $3.480 during peak.
If those 1M input tokens were already cached: $0.00696 + $1.392 = $1.399 off-peak, and $0.01392 + $2.784 = $2.798 during peak.
Errors
Main status codes the API can return.
| Code | Meaning |
|---|---|
| 400 | Invalid JSON, unsupported streaming, or invalid parameters. |
| 401 | Invalid or revoked API key. |
| 402 | Insufficient balance. A minimum of $0.50 is required to use the API. |
| 404 | Public model unavailable. |
| 502 | Provider request failed. |
opencode custom provider
Example configuration as an OpenAI-compatible provider.
{
"provider": {
"world-app-technologies-api": {
"npm": "@ai-sdk/openai-compatible",
"name": "Nulu API",
"options": {
"baseURL": "https://api.nuluai.com/v1",
"apiKey": "sk_wat_your_key"
},
"models": {
"nulu-5-pro": {
"name": "Nulu 5 Pro",
"reasoning": true,
"tool_call": true,
"interleaved": { "field": "reasoning_content" },
"limit": { "context": 1000000, "output": 384000 },
"variants": {
"low": { "reasoningEffort": "low" },
"medium": { "reasoningEffort": "medium" },
"high": { "reasoningEffort": "high" },
"max": { "reasoningEffort": "max" }
}
},
"nulu-5-ultra": {
"name": "Nulu 5 Ultra",
"reasoning": true,
"tool_call": true,
"interleaved": { "field": "reasoning_content" },
"limit": { "context": 1000000, "output": 384000 },
"variants": {
"low": { "reasoningEffort": "low" },
"medium": { "reasoningEffort": "medium" },
"high": { "reasoningEffort": "high" },
"max": { "reasoningEffort": "max" }
}
},
"nulu-image-1.5": {
"name": "Nulu Image 1.5 (Legacy)"
},
"nulu-image-3": {
"name": "Nulu Image 3"
}
}
}
}
}After creating a key, point `baseURL` to `https://api.nuluai.com/v1`. The model must be one of the public IDs listed above. Reasoning models include `low`, `medium`, `high`, and `max` variants: pick them in the model selector or cycle through them with the `variant_cycle` keybind.

