# LLM FAQs
Source: https://docs.novita.ai/guides/LLM-FAQ



## Voucher Usage Guide

### What types of voucher does Novita provide?

Novita currently offers the following types of vouchers to help users experience the platform's services:

* **New User Voucher**: Available to first-time registered users for trying out services of various types of models.
* **Referral Voucher**: Earned by inviting others to register and complete tasks on the platform.

## Rate Limits for Model Usage

### What are the RPM limits for different users?

RPM (Requests Per Minute) limits vary based on a user's verification level and account status. For detailed limits, please refer to the official documentation:

<Tip>
  👉 For further assistance, please [book a call with our sales team](https://meet.brevo.com/novita-ai/contact-sales)
</Tip>

## RPM Adjustment and Upgrade Policy

### Can users apply for increased RPM limits?

Yes. Novita allows flexible RPM upgrades based on usage needs. Rules are listed as below:

* **DeepSeek models**: The platform will strive to accommodate reasonable scaling needs.
* **Other models**: RPM increases are evaluated based on model cost and actual user behavior, subject to capacity availability.

### **Application process:**

User → Customer Manager / Tech Support → Product Team Review & Approval

### **What happens if actual usage falls short of the committed RPM?**

If the user's actual RPM remains below the committed level for one consecutive week, the platform will adjust the rate limits policy as:

* Reduce the limit to the peak RPM in the past week, or
* Revert to the model's default rate limit (whichever is lower).

### **Is self-service RPM upgrade supported?**

Yes. Novita plans to launch an **RPM Upgrade Package**, allowing users to manage and increase RPM limits independently, without manual approval.

For further assistance, please [book a call with our sales team](https://meet.brevo.com/novita-ai/contact-sales).

## Billing

### What is the billing priority when using the Coding Plan?

The system deducts from your **Coding Plan quota first**. Only after the Coding Plan quota is fully exhausted will charges be applied to your API account balance.

### How are the 50M tokens in the Coding Plan calculated?

Tokens are calculated based on base-rate equivalent token counts. Different models carry varying pricing weights proportional to their cost ratios. Both input tokens and output tokens count toward your total usage.

### How are cached tokens billed?

Cached read tokens are billed at a reduced rate, approximately **1/10th of the standard input token price** (exact rates vary by model). Cached write operations are billed at the normal input token rate.

## Thinking Mode

Thinking (reasoning) models write out a chain of thought before they answer. That improves accuracy on hard problems but adds latency and output tokens. On Novita, reasoning models fall into three groups:

* **Always on**: the model always reasons and this cannot be turned off, for example `zai-org/glm-5.3`.
* **Switchable (hybrid)**: the model reasons by default and you can turn it off per request, for example `moonshotai/kimi-k2.5` and `qwen/qwen3.8-flash`.
* **Adjustable depth**: instead of an on/off switch, the model lets you pick how much it reasons. `zai-org/glm-5.3` supports the levels low, high and max (default max).

The model page on [novita.ai/models](https://novita.ai/models) states which group a model belongs to and the parameters it accepts.

### How do I turn thinking off for a request?

For switchable models, add `"enable_thinking": false` to the request body. The model answers directly and no reasoning tokens are produced or billed.

```json theme={"system"}
{
  "model": "moonshotai/kimi-k2.5",
  "messages": [
    { "role": "user", "content": "What is the capital of France?" }
  ],
  "enable_thinking": false
}
```

Always-on models ignore this parameter. If your use case cannot afford reasoning latency, pick a switchable model or a non-reasoning model instead.

### Where does the reasoning appear in the response?

Reasoning is returned next to the answer, not mixed into it. Set `"separate_reasoning": true` and read `message.reasoning_content` (or `delta.reasoning_content` when streaming); the final answer stays in `content`. Models that support interleaved thinking may also return a structured `reasoning_details` array. See [Reasoning Models](/guides/llm-reasoning) for complete code samples.

### Why is `content` empty with `finish_reason: "length"`?

`max_tokens` covers reasoning tokens as well as visible output. If the budget is too small, the model spends all of it on reasoning and returns an empty `content`. Raise `max_tokens`, or lower the reasoning depth or turn thinking off on models that allow it.

### Are reasoning tokens billed?

Yes. Reasoning tokens are output tokens and are billed at the model's output price, whether or not you read them. Turning thinking off on switchable models is the most direct way to cut cost and latency on simple requests.

### Do I need to send reasoning back in multi-turn conversations?

Normally no. Keep only `content` in the assistant turns of your message history; the model does not need its earlier reasoning to continue. The exception is agentic tool-calling with interleaved-thinking models, where the previous turn's reasoning must be passed back between tool calls. See [Interleaved Thinking Support](/guides/llm-interleaved-thinking).

## Feature Support

### Is the `top_logprobs` parameter supported?

The `top_logprobs` parameter is currently **not supported**.

### Is structured output (`response_format`) supported?

Currently, only `"type": "json_object"` is supported. `"type": "json_schema"` (with a strict schema definition) is not yet available.

## Troubleshooting

### Why am I receiving 403 errors when calling a model?

Common causes include:

1. **Insufficient account balance** — top up your account and retry.
2. **Model requires whitelist access** — certain models are restricted. See [How do I apply for model whitelist access?](#how-do-i-apply-for-model-whitelist-access) below.
3. **Plan restriction** — the model may not be included in your current subscription plan.

### Why am I receiving 400 errors?

Common causes include:

* Input length exceeds the model's context window limit.
* Incorrect parameter types or formatting.
* Missing required fields in the request body.

Please provide your `trace_id` to our support team for detailed investigation.

### What should I do if the API returns a "server overload" error?

This indicates temporary resource pressure on the platform. Retry your request after a brief delay (we recommend exponential backoff). If the issue persists, <a href="https://discord.gg/YyPRAzwp7P">contact us</a> for status confirmation.

## Model Access

### How do I apply for model whitelist access?

Contact our support team via <a href="https://discord.gg/YyPRAzwp7P">Discord</a> or the in-platform chat with your account information and the specific model name(s) you require. Our team will review and process your request.


# AI Gradio 
Source: https://docs.novita.ai/guides/ai-gradio

Explore ai-gradio and Novita AI to build interactive AI demos effortlessly with powerful large language models.

AI-gradio simplifies AI development with its intuitive Python package, built on Gradio's framework. Pairing it with Novita AI unlocks powerful capabilities through cutting-edge large language models, which deliver exceptional value through Novita AI's reliable APIs, optimized performance, and cost-effective pricing—all within AI-gradio's user-friendly interface.

This step-by-step guide will access Novita AI's LLMs API on ai-gradio including methods to install ai-gradio and use Novita AI's LLMs in ai-gradio.

## How to Install ai-gradio

```python theme={"system"}
# Install core package
pip install ai-gradio

# Install with specific provider support
pip install 'ai-gradio[openai]'     # OpenAI support
pip install 'ai-gradio[gemini]'     # Google Gemini support
pip install 'ai-gradio[anthropic]'  # Anthropic Claude support
pip install 'ai-gradio[groq]'       # Groq support

# Install all providers
pip install 'ai-gradio[all]'
```

## How to use Novita AI's LLMs in ai-gradio

### Step 1: Set up environment variables.

* Set NOVITA\_API\_KEY to your Novita API key.

```python theme={"system"}
export NOVITA_API_KEY=<your api key>
```

### Step 2: Modify the app.py entry file.

```python theme={"system"}
import gradio as gr
import ai_gradio
gr.load(
    name='novita:deepseek/deepseek-v4.1-flash',
    src=ai_gradio.registry,
).launch()
```

### Step 3: Run the app.py file with python.


# AI Search FAQs
Source: https://docs.novita.ai/guides/ai-search-faq



## Getting Started

### What is AI Search?

AI Search is a single Novita gateway that gives your application live access to search the web. You call Novita with one API key and choose a search provider per request, instead of signing up with each provider separately. See the [Quickstart](/guides/ai-search-quickstart) for the full picture.

### Which providers are supported?

Currently **Exa** and **Tavily**. Each is exposed as a passthrough endpoint that mirrors the provider's own request and response format. See the [Quickstart](/guides/ai-search-quickstart) for the endpoint list.

### Do I need a separate Exa or Tavily account?

No. Your Novita API key authenticates every AI Search request. You don't manage upstream provider keys or accounts.

## Authentication & Setup

### How do I authenticate?

Use your Novita API key as a bearer token: `Authorization: Bearer <api_key>`. The same key works for every provider. To create or manage keys, see [API Key Management](/api-reference/basic-authentication).

### Is there a Novita SDK for AI Search?

No — and you don't need one. AI Search is an integration, not a wrapped product: each endpoint is a passthrough that forwards your request to the provider (Exa or Tavily) in that provider's native format. That means you have two solid options:

* **Use the provider's own SDK.** If the SDK lets you set a custom base URL, point it at the matching gateway route and pass your Novita key. For example, the Exa Python SDK accepts `base_url="https://api.novita.ai/v3/exa"`. See the [Quickstart](/guides/ai-search-quickstart#bring-your-own-provider-sdk) for a full example.
* **Call the REST endpoints directly.** Works with any HTTP client and needs no SDK at all. The [Quickstart](/guides/ai-search-quickstart) shows curl, Python, and JavaScript examples.

If an SDK doesn't expose a base-URL setting, use the REST path instead.

## Can I switch providers without changing my code structure?

Mostly. Authentication, host, and the request flow stay identical — you change the route (e.g. `/v3/tavily/search` → `/v3/exa/search`) and align the body to that provider's field names (for example, Tavily's `max_results` vs Exa's `numResults`).

## Search Behavior

### How fresh are the results?

Results reflect live web content. For time-sensitive questions, constrain by date to keep stale pages out: Tavily exposes `time_range` and `start_date`/`end_date`; Exa exposes `startPublishedDate`/`endPublishedDate`.

### How do I restrict results to trusted sites?

Use domain filters — `include_domains`/`exclude_domains` on Tavily, `includeDomains`/`excludeDomains` on Exa — to keep retrieval within sources you trust.

### What's the difference between search, extract, and crawl?

* **Search** discovers relevant pages from a query.
* **Extract / Contents** pulls clean text from URLs you already have.
* **Crawl / Map** follows links to gather or list many pages from a site.

See [Web-Grounded Answers](/guides/ai-search-grounded-answers#search-vs-extract-vs-crawl) for guidance on which to use.

### Can AI Search generate an answer, not just links?

Yes. Tavily Search returns an LLM answer when you set `include_answer`, and Exa offers a dedicated [Answer](/api-reference/model-apis-exa-answer) endpoint. For full control over wording and citations, retrieve results and synthesize with the [LLM API](/guides/llm-api) instead — see [Web-Grounded Answers](/guides/ai-search-grounded-answers).

## Limits & Troubleshooting

### What are the rate limits?

AI Search requests are subject to both Novita platform limits and each upstream provider's own limits, which depend on the provider tier tied to your account. When you exceed a limit, the API returns `429 Too Many Requests` — back off and retry with exponential delay. These limits are separate from the [LLM API](/guides/llm-api) rate limits; a search call doesn't draw down your LLM quota.

### Why am I getting a 400 error?

A parameter likely doesn't match the provider's schema. Confirm you're using that provider's field names — a common cause is sending Tavily's `max_results` to an Exa route (which expects `numResults`).

### Where can I get more help?

For integration questions or higher limits, [book a call with our team](https://meet.brevo.com/novita-ai/contact-sales).


# Web-Grounded Answers
Source: https://docs.novita.ai/guides/ai-search-grounded-answers



A model alone can't answer questions about events after its training cutoff, or cite a source. The fix is *grounding*: retrieve relevant web content at request time and pass it to the model as context. This guide shows the pattern using Novita [AI Search](/guides/ai-search-quickstart) for retrieval and the [LLM API](/guides/llm-api) for synthesis — all behind one API key.

## What is RAG?

Retrieval-augmented generation (RAG) is the pattern behind every grounded answer here: instead of relying on what the model memorized during training, you *retrieve* relevant documents at request time and *augment* the prompt with them, so the model *generates* its answer from that fresh, verifiable context.

That retrieval step is exactly what AI Search provides. The web becomes your knowledge base, and you don't have to build or maintain a vector database to get started — a search call returns the passages, and the LLM does the rest. When you later want retrieval over your *own* private documents, the same three-step shape applies; you just swap web search for a vector store.

RAG buys you three things a bare model can't offer:

* **Freshness** — answers reflect content published after the model's training cutoff.
* **Attribution** — every claim can point back to a source URL the user can check.
* **Reduced hallucination** — grounding the model in retrieved text keeps it from inventing facts.

## How grounding works

A single RAG turn follows three steps:

1. **Search** (retrieve) the web for pages relevant to the question.
2. **Extract** (augment) the content you need — snippets or full page text — into the prompt.
3. **Synthesize** (generate) an answer by passing that content to an LLM, asking it to cite sources.

You can do this with two calls (search + LLM) or even one, since both Exa and Tavily can synthesize an answer for you. Choose based on how much control you need over the final wording and citations.

## Let the provider write the answer

The fastest path. Tavily's Search returns an LLM-generated answer when you set `include_answer`, and Exa offers a dedicated [Answer](/api-reference/model-apis-exa-answer) endpoint. One call, no orchestration.

<CodeGroup>
  ```bash Tavily theme={"system"}
  curl -X POST 'https://api.novita.ai/v3/tavily/search' \
    -H "Authorization: Bearer ${NOVITA_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "query": "What changed in the latest Python release?",
      "include_answer": "advanced",
      "max_results": 5
    }'
  ```

  ```bash Exa theme={"system"}
  curl -X POST 'https://api.novita.ai/v3/exa/answer' \
    -H "Authorization: Bearer ${NOVITA_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "query": "What changed in the latest Python release?",
      "text": true
    }'
  ```
</CodeGroup>

Use this when you want a good answer with minimal code. Reach for the retrieve-then-synthesize approach below when you need control over the model, tone, output format, or how citations are presented.

## Retrieve, then synthesize with an LLM

This gives you full control. Search for sources, then hand the results to the [LLM API](/guides/llm-api) with instructions to answer using only that context and to cite each claim.

```python theme={"system"}
import os
import requests
from openai import OpenAI

NOVITA_KEY = os.environ["NOVITA_API_KEY"]
QUESTION = "What changed in the latest Python release?"

# 1. Retrieve relevant web content.
search = requests.post(
    "https://api.novita.ai/v3/tavily/search",
    headers={"Authorization": f"Bearer {NOVITA_KEY}"},
    json={
        "query": QUESTION,
        "max_results": 5,
        "include_raw_content": "text",
    },
).json()

# 2. Build a compact, numbered context block the model can cite.
sources = []
for i, r in enumerate(search["results"], start=1):
    body = (r.get("raw_content") or r.get("content") or "")[:1500]
    sources.append(f"[{i}] {r['title']} ({r['url']})\n{body}")
context = "\n\n".join(sources)

# 3. Synthesize a grounded, cited answer.
client = OpenAI(base_url="https://api.novita.ai/openai", api_key=NOVITA_KEY)
completion = client.chat.completions.create(
    model="deepseek/deepseek-v4.1-flash",
    messages=[
        {
            "role": "system",
            "content": (
                "Answer using ONLY the provided sources. "
                "Cite claims with [n] referring to the source number. "
                "If the sources don't contain the answer, say so."
            ),
        },
        {"role": "user", "content": f"Question: {QUESTION}\n\nSources:\n{context}"},
    ],
)
print(completion.choices[0].message.content)
```

The same flow works with Exa Search — swap the route to `/v3/exa/search` and read `text` from each result instead of `raw_content`.

## Tuning retrieval quality

The quality of a grounded answer depends far more on *what you retrieve* than on the model. A few high-impact knobs:

* **Freshness.** For time-sensitive questions, constrain by date. Tavily exposes `time_range` and `start_date`/`end_date`; Exa exposes `startPublishedDate`/`endPublishedDate`. This keeps stale pages out of the context.
* **Source trust.** Use `include_domains`/`excludeDomains` to restrict retrieval to sources you trust (official docs, reputable publishers) and to keep out low-quality sites.
* **Topic hints.** Tavily's `topic` (`news`, `finance`, `general`) and Exa's `category` steer the engine toward the right kind of result.
* **Right-sizing context.** Cap extracted text (Tavily `chunks_per_source`, Exa `maxCharacters`) so you pass enough signal without blowing the model's context window or your token budget.

## Search vs. extract vs. crawl

Pick the retrieval primitive that matches your need:

* **Search** — you have a question and need to *discover* relevant pages. The default starting point.
* **Extract / Contents** — you already know the URLs and want clean text from them. Good for grounding on a fixed set of documents. See [Tavily Extract](/api-reference/model-apis-tavily-extract) and [Exa Contents](/api-reference/model-apis-exa-contents).
* **Crawl / Map** — you want to ingest a whole site or section, following links. Good for building a knowledge base offline. See [Tavily Crawl](/api-reference/model-apis-tavily-crawl) and [Tavily Map](/api-reference/model-apis-tavily-map).

<Tip>
  For a fuller agent that searches iteratively and reasons over many sources, see the [DeepSearcher integration](/guides/deepsearcher).
</Tip>

## Taking it to production

* **Handle empty results.** If search returns nothing relevant, have the model say it can't answer rather than inventing one. The system prompt above does this.
* **Cache when you can.** Identical queries return similar results; caching retrieval cuts cost and latency.
* **Mind rate limits.** Retrieval and LLM calls are limited separately — a search request doesn't draw down your LLM quota. Back off on `429` for either. See [LLM rate limits](/guides/llm-rate-limits) for the LLM side.
* **Keep keys server-side.** Your Novita key authenticates every call here — never ship it in client-side code.


# Quickstart
Source: https://docs.novita.ai/guides/ai-search-quickstart



AI Search gives your application live access to the web through a single Novita gateway. Instead of signing up with each search provider, managing separate keys, and learning different auth schemes, you call Novita with one API key and pick the search engine you want per request.

This guide takes you from zero to a working web search in a few minutes. You'll authenticate with a single Novita API key, run a search, and see how to switch providers without changing your setup.

## Before you start

1. A Novita account and an API key. See [API Key Management](/api-reference/basic-authentication) to create one.
2. Export your key so the examples below can use it. `NOVITA_API_KEY` is just your Novita API key from step 1 — the same key works for every provider:

```bash theme={"system"}
export NOVITA_API_KEY="<Your API Key>"
```

All AI Search endpoints share the same base URL and authentication:

* **Base URL:** `https://api.novita.ai`
* **Auth header:** `Authorization: Bearer <api_key>`
* **Content type:** `application/json`

## Your first search

The example below runs an Exa search. Every request is just a route plus a JSON body.

<CodeGroup>
  ```bash curl theme={"system"}
  curl -X POST 'https://api.novita.ai/v3/exa/search' \
    -H "Authorization: Bearer ${NOVITA_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "query": "latest open-source LLM releases",
      "numResults": 5
    }'
  ```

  ```python Python theme={"system"}
  import os
  import requests

  resp = requests.post(
      "https://api.novita.ai/v3/exa/search",
      headers={
          "Authorization": f"Bearer {os.environ['NOVITA_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "query": "latest open-source LLM releases",
          "numResults": 5,
      },
  )
  resp.raise_for_status()

  for result in resp.json()["results"]:
      print(result["title"], "-", result["url"])
  ```

  ```javascript JavaScript theme={"system"}
  const resp = await fetch("https://api.novita.ai/v3/exa/search", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NOVITA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: "latest open-source LLM releases",
      numResults: 5,
    }),
  });

  const data = await resp.json();
  for (const result of data.results) {
    console.log(`${result.title} - ${result.url}`);
  }
  ```
</CodeGroup>

## Switching search engines

Switching search engines means changing the **route** and the **request body** to match the provider — your key, host, and auth header stay the same. Here's the same intent expressed for Tavily:

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/tavily/search' \
  -H "Authorization: Bearer ${NOVITA_API_KEY}" \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "latest open-source LLM releases",
    "max_results": 5
  }'
```

Note the small differences: Exa uses `numResults`, Tavily uses `max_results`. Each provider's full parameter list lives in its API reference.

## Bring your own provider SDK

AI Search is a passthrough integration, so a provider's official SDK works as long as it lets you override the base URL — point it at the matching gateway route and pass your Novita key. The Exa Python SDK, for example, takes a `base_url`:

```python theme={"system"}
import os
from exa_py import Exa

exa = Exa(
    api_key=os.environ["NOVITA_API_KEY"],
    base_url="https://api.novita.ai/v3/exa",
)

results = exa.search("latest open-source LLM releases", num_results=5)
for r in results.results:
    print(r.title, "-", r.url)
```

<Note>
  If an SDK doesn't expose a base-URL setting, just call the REST endpoints directly with any HTTP client, as shown above — that path always works.
</Note>

## How it works

Novita exposes each provider as a passthrough endpoint. Your request is authenticated with your Novita API key, then forwarded to the upstream provider using the provider's own request and response format.

* **One credential.** Use your existing Novita API key as `Bearer <api_key>` for every provider.
* **Provider-native bodies.** Request and response shapes match the upstream provider, so the provider's own examples and request formats carry over — just point the base URL at Novita.
* **Swap engines per request.** Switching providers means changing the route and the body, not your auth or your account setup.

This is useful whenever a model needs information it wasn't trained on: current events, fast-moving documentation, prices, or any fact that lives on the open web. Pair it with the [LLM API](/guides/llm-api) to build retrieval-augmented generation (RAG), research agents, and web-grounded assistants.

## Supported providers

<CardGroup>
  <Card title="Exa" icon="brain">
    Neural and semantic web search with built-in content extraction and answer synthesis. Strong for research, finding pages by meaning, and structured output.
  </Card>

  <Card title="Tavily" icon="zap">
    Fast, LLM-optimized search and retrieval with site crawling and mapping. Strong for news and finance topics, extraction pipelines, and low-latency lookups.
  </Card>
</CardGroup>

## Capabilities at a glance

| Capability | What it does | Exa | Tavily |
| - | - | :-: | :-: |
| Search | Find relevant pages from a query | [Search](/api-reference/model-apis-exa-search) | [Search](/api-reference/model-apis-tavily-search) |
| Content extraction | Pull clean text/metadata from URLs | [Contents](/api-reference/model-apis-exa-contents) | [Extract](/api-reference/model-apis-tavily-extract) |
| Answer synthesis | Generate a cited answer from results | [Answer](/api-reference/model-apis-exa-answer) | via `include_answer` |
| Site crawling | Follow links to gather many pages | — | [Crawl](/api-reference/model-apis-tavily-crawl) |
| Site mapping | Discover a site's reachable URLs | — | [Map](/api-reference/model-apis-tavily-map) |

## Endpoint reference

| Provider | Endpoint | Route |
| - | - | - |
| Exa | [Search](/api-reference/model-apis-exa-search) | `POST /v3/exa/search` |
| Exa | [Contents](/api-reference/model-apis-exa-contents) | `POST /v3/exa/contents` |
| Exa | [Answer](/api-reference/model-apis-exa-answer) | `POST /v3/exa/answer` |
| Tavily | [Search](/api-reference/model-apis-tavily-search) | `POST /v3/tavily/search` |
| Tavily | [Extract](/api-reference/model-apis-tavily-extract) | `POST /v3/tavily/extract` |
| Tavily | [Crawl](/api-reference/model-apis-tavily-crawl) | `POST /v3/tavily/crawl` |
| Tavily | [Map](/api-reference/model-apis-tavily-map) | `POST /v3/tavily/map` |

## Troubleshooting

* **400 Bad Request** — a parameter doesn't match the provider's schema. Check that you're using that provider's field names (for example, `numResults` vs `max_results`).
* **429 Too Many Requests** — you've hit a rate limit. Back off and retry, or reduce request volume.

For the full list, see the Errors section on any AI Search [API reference](/api-reference/model-apis-tavily-search) page.

## Where to go next

<Card title="Web-Grounded Answers & RAG" icon="link" href="/guides/ai-search-grounded-answers">
  Feed search results into the LLM API to build retrieval-augmented generation (RAG) pipelines with cited answers.
</Card>


# AnythingLLM
Source: https://docs.novita.ai/guides/anythingllm

Step-by-step guide to set up AnythingLLM with Novita AI models for secure, document-based AI applications.

With AnythingLLM, you gain seamless access to a comprehensive suite of Novita AI LLM models, including DeepSeek, Llama, Qwen, and more, along with vector database solutions. The integration enables you to effortlessly build and deploy AI-powered applications that handle complex language tasks while ensuring privacy and control.

This guide will walk you through installing and integrating AnythingLLM, connecting Novita AI’s models, enabling web search functionality, and setting up a knowledge base for dynamic and intelligent document interactions.

## **What is AnythingLLM?**

AnythingLLM is a full-stack application designed to give you the flexibility of using both commercial LLMs and popular open-source LLMs, along with vector database solutions. You can run this application locally or host it remotely, offering a secure, private environment to interact intelligently with your documents.

By integrating these resources, AnythingLLM allows you to build a highly functional AI-powered chat interface that handles complex language tasks without compromising privacy or control.

## Key Features

* **Thread-like Structure Workspace**: Workspaces act as threads, organizing related documents in a contained space. Each Workspace isolates documents from others, preserving a unique context for each project or topic.
* **Flexibility for Personal and Businesses:** Install AnythingLLM and its full suite of tools as a single application on your personal desktop. And Fine-grained permissioning and access control for organization is also built-in.
* **Security:** AnythingLLM only talks to the services you connect to and can run fully on your machine without internet connectivity.

## Prerequisites

Before you begin, make sure you have the following:

* Novita AI LLM API Key:
  * Visit [Novita AI’s website](https://novita.ai/) and create an account.
  * After logging in, go to the [Key Management](https://novita.ai/settings/key-management) page to generate your API Key. This key is required to connect Novita AI’s models to AnythingLLM.

    <Frame>
      ![Novita AI key management](https://mintlify.s3.us-west-1.amazonaws.com/novitaai/images/third-party/dify-1.png)
    </Frame>
* **AnythingLLM Software**:Download and install [AnythingLLM software](https://anythingllm.com/desktop) from the official website.

<Frame>
  <img alt="AnythingLLM Software" />
</Frame>

* **A Working Knowledge Base** (Optional):If you plan to use your own documents for knowledge augmentation, prepare them for upload.

## How to install AnythingLLM on Linux locally?

For Linux users, you can install AnythingLLM by running this command on terminal:

curl -fsSL

[<u>https://s3.us-west-1.amazonaws.com/public.useanything.com/latest/installer.sh</u>](https://s3.us-west-1.amazonaws.com/public.useanything.com/latest/installer.sh) | sh

This will download the latest version of AnythingLLM’s AppImage, unpack it, and then supply a symlink to seamlessly run AnythingLLM. This script will unpack the app in `$HOME/AnythingLLMDesktop`.

You can start the app at any time by running `./AnythingLLMDesktop/start`. This will boot the app with full logging.

## Integration Steps

### 1. Connect Novita AI to AnythingLLM

To connect Novita AI’s models with **AnythingLLM**, follow these steps:

* In the application, click on the **🔧 Settings** icon located in the lower-left corner.

  <Frame>
    <img alt="Open AnythingLLM setting" />
  </Frame>
* Go to the **LLM API Providers** section, **and then** select **Novita AI** from the dropdown menu.

  <Frame>
    <img alt="LLM API Providers" />
  </Frame>
* In the **Novita API Key** field, **paste your Novita AI API Key** (the one you generated earlier).
* Click **Save** to complete the integration.

Now, AnythingLLM will have access to all Novita AI’s models, allowing you to use them in your applications.

### 2. Enable Web Search

AnythingLLM allows you to search the web in real-time by enabling web search capabilities. Follow these steps to set up web search functionality:

* **Navigate to Agent Skills**: In the settings or main interface, locate the **Agent Skills** section.
* **Enable Scrape Websites and Web Search**: Toggle the option to turn on both **Scrape Websites** and **Web Search** capabilities.
* **Choose Web Search Providers**: You can choose from the following recommended search providers:
  * **DuckDuckGo**: A free and privacy-focused web search using DuckDuckGo's HTML interface.
  * **Google Search Engine**: Powered by a custom Google Search Engine. It’s free for up to 100 queries per day.
  * **Bing Search**: Powered by the Bing Search API. It’s free for up to 1000 queries per month.

<Frame>
  <img alt="Enable Web Search" />
</Frame>

### 3. Create a Knowledge Base

Next, you’ll want to build a knowledge base for your assistant to use. Follow these steps:

* Click the Upload icon of the Workspace.

  <Frame>
    <img alt="Click the Upload icon of the Workspace" />
  </Frame>
* Upload local documents or website links.
* Select the file(s) or webpage you wish to upload to the workspace.
* Once the file is selected, click **Move to Workspace** to transfer the document(s) to the AnythingLLM workspace.
* Save your files and click **Save and Embed** to finalize the process.
* Your knowledge base is now set up and ready for use.

  <Frame>
    <img alt="Save and embed in AnythingLLM" />
  </Frame>

### 4. Try Asking Questions

In AnythingLLM, ask questions using **@agent**.

The model will respond based on your documents and search results, with citations or references from the files.

<Frame>
  <img alt="Try Asking Questions" />
</Frame>


# Browser Use
Source: https://docs.novita.ai/guides/browseruse

Easily enhance your browsing experience by integrating Novita AI with Browser Use for intelligent web interactions.

Browser Use is an open-source library that empowers LLMs to directly control web browsers, revolutionizing web interaction with advanced automation. By integrating Novita AI's powerful LLMs and tools, Browser Use enables seamless browsing, content generation, and task automation for an optimized user experience.

This tutorial will show you how to integrate the Novita AI API with Browser Use to automate browser interactions.

## **How to Use Browser Use with Novita AI**

### **Prerequisites**

* Python 3.11 or higher
* A Novita AI API key

### **Installation**

Step 1: Install Browser Use using pip:

```bash theme={"system"}
pip install browser-use
```

Step 2: Install Playwright (required for browser automation):

```bash theme={"system"}
playwright install chromium
```

### **Obtaining Novita AI LLM API Key**

* Create an account: Visit [Novita AI’s website](https://novita.ai/) and sign up for an account.
* Generate your API Key: After logging in, navigate to the [Key Management](https://novita.ai/settings/key-management) page to generate your API key. This key is essential to connect Novita AI’s models to Cursor.

  <Frame>
    ![Novita AI key management](https://mintlify.s3.us-west-1.amazonaws.com/novitaai/images/third-party/dify-1.png)
  </Frame>

### **Environment Setup**

Create a `.env` file in your project root and add your Novita API key:

```bash theme={"system"}
NOVITA_API_KEY=your_api_key_here
```

### Basic Implementation

* Here's a complete example of using Browser Use with Novita AI's API:

```python theme={"system"}
"""
Web automation using Novita AI and Browser Use
"""

import asyncio
import os

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import SecretStr

from browser_use import Agent

# Load environment variables
load_dotenv()

api_key = os.getenv('NOVITA_API_KEY', '')
if not api_key:
    raise ValueError('NOVITA_API_KEY is not set')


async def run_search():
    agent = Agent(
        task=(
            '1. Go to https://www.reddit.com/r/LocalLLaMA '
            "2. Search for 'browser use' in the search bar "
            '3. Click on first result '
            '4. Return the first comment'
        ),
        llm=ChatOpenAI(
            base_url='https://api.novita.ai/openai',
            model='deepseek/deepseek-v4.1-flash',
            api_key=SecretStr(api_key),
        ),
        use_vision=False,
    )

    await agent.run()


if __name__ == '__main__':
    asyncio.run(run_search())
```

### **Creating Your Own Tasks**

* You can customize the `task` parameter to perform a wide variety of web tasks:

```python theme={"system"}
task="Compare the price of gpt-4o and DeepSeek-V3"
```

* For more complex tasks, you might want to enable vision capabilities:

```python theme={"system"}
agent = Agent(
    task="Find and summarize the latest news about AI on TechCrunch",
    llm=ChatOpenAI(
        base_url='https://api.novita.ai/openai',
        model='deepseek/deepseek-v4.1-flash',
        api_key=SecretStr(api_key),
    ),
    use_vision=True,
)
```


# ChatBox
Source: https://docs.novita.ai/guides/chatbox

Integrate Novita AI with Chatbox to streamline your LLM experience across devices with powerful models and a seamless chat interface.

With the Novita AI & Chatbox integration, you gain comprehensive access to Novita AI’s powerful suite of LLMs, including GLM 4.5, Deepseek, Qwen, and more.

This guide will walk you through integrating Novita AI’s LLMs with the ChatBox platform.

## What is ChatBox

ChatBox is an open-source, cross-platform AI chat application designed to interact with a variety of LLMs. It supports a wide range of LLM providers including OpenAI, Claude, Google Gemini, and Novita AI. With features like multi-language support, advanced prompt management, image generation via DALL·E 3, and collaboration tools, Chatbox enables developers and users alike to create and manage AI workflows efficiently.

## Integration Steps

### Step 1 Open Settings

Open the Chatbox app and find the **Settings** at the bottom left.

<img alt="opensettings.png" />

### Step 2 Locate Novita AI

The Settings page defaults to the Model Providers tab. Scroll through the model list to find Novita AI.

<img alt="locatenovita.png" />

### Step 3 Model Selection

Select Novita AI to view the model selection section, where several default models are available, including models supported by Novita such as deepseek-v3-0324, deepseek-r1-0528, glm 4.5, and more. For the complete list of available models, visit [https://novita.ai/models](https://novita.ai/models).

<img alt="modelselection.png" />

Click **Fetch** to open the model list. From there, you can add models to the default list or remove existing ones as needed.

<img alt="fetch.png" />

### Step 4 Check API Key

Here, you can enter your API key. After entering it, click **Check**. If you see the message **“Connection successful”**, it means you're all set to start using it.

<img alt="apikey.png" />

### Step 5 Start Using

Start a new chat. Enter "Hello" and receive a reply. You should see that the response is provided by Novita AI.

<img alt="startnewchat.png" />

## Embedded Model Use

### Step 1 Locate Knowledge Base

Click **"Settings"**, then **"Knowledge Base"**. If there’s nothing there yet, you’ll be prompted to create a new one (the screenshot shows one already created).

<img alt="locatekb.png" />

### Step 2 Create Knowledge Base

When creating a new knowledge base, remember to select your desired **embedding model**, then click **"Create"**.

<img alt="createkb.png" />

### Step 3 Start Using

Return to the **chat page**, and click the **knowledge base icon** below the input box. Then, select the knowledge base you just created. Now start your conversation with Novita AI LLM on ChatBox.

<img alt="startkb.png" />


# Claude Code
Source: https://docs.novita.ai/guides/claude-code



Claude Code is an AI-powered coding assistance published by Anthropic that provides a terminal interface, allowing developers to delegate complex programming tasks directly from the terminal to Claude Code for completion.

Now, Novita provides [Anthropic SDK compatible LLM API services](/guides/llm-anthropic-compatibility), enabling you to easily use Novita LLM models in Claude Code to complete tasks. Please refer to the guide below to complete the integration process.

<Tip>
  Prefer one command? [Novita Connect](/guides/novita-connect) starts Claude Code on Novita models with `novi claude`, without changing your configuration.
</Tip>

## Quick Start

### 1. Install Claude Code

<Warning>
  Before installing Claude Code, please ensure your local environment has [Node.js 18 or higher](https://nodejs.org/en/download/) installed.
</Warning>

To install Claude Code, run the following command:

<CodeGroup>
  ```bash Bash icon=terminal theme={"system"}
  npm install -g @anthropic-ai/claude-code
  ```
</CodeGroup>

### 2. Start your first session

<Tip>
  import  from '/snippets/setup-api-key-guide.mdx';

  <SetupApiKeyGuide />
</Tip>

<Tip>
  Please find the list of models currently available for use in Claude Code [here](/guides/llm-anthropic-compatibility#supported-models).
</Tip>

Open the terminal and set up environment variables as follows:

<CodeGroup>
  ```bash Bash icon=terminal theme={"system"}
  # Set the Anthropic SDK compatible API endpoint provided by Novita.
  export ANTHROPIC_BASE_URL="https://api.novita.ai/anthropic"
  export ANTHROPIC_AUTH_TOKEN="<Novita API Key>"
  # Set the model provided by Novita.
  export ANTHROPIC_MODEL="moonshotai/kimi-k2-instruct"
  export ANTHROPIC_SMALL_FAST_MODEL="moonshotai/kimi-k2-instruct"
  ```
</CodeGroup>

Next, navigate to your project directory and start Claude Code. You will see the Claude Code prompt inside a new interactive session:

<CodeGroup>
  ```bash Bash icon=terminal theme={"system"}
  cd <your-project-directory>
  claude .
  ```
</CodeGroup>

<Frame>
  <img />
</Frame>

### 3. Build a web game from scratch

Input your task description, then press `Enter` to start this task.

```bash Bash icon=terminal theme={"system"}
> Create a ping-pong web game. Use only HTML, CSS, and JavaScript, try to create some novel content, and the final output should be a single HTML file.
```

<Frame>
  <img />
</Frame>

Claude Code will analyze your requirements, create a multi-step plan, and automatically begin executing the tasks.

<Frame>
  <img />
</Frame>

After completing each task, Claude Code will mark it as complete and proceed to plan and explain the details of the next task.

<Frame>
  <img />
</Frame>

### 4. Task Results and Preview

After all tasks are completed, you will see the following messages in the terminal:

<Frame>
  <img />
</Frame>

At this point, you can open the `gravity-pong.html` file in your browser to view and play the game.

<Frame>
  <img />
</Frame>

### 5. Use Git with Claude Code

Claude Code makes Git operations conversational:

```bash Bash icon=terminal theme={"system"}
> what files have I changed?
```

```bash Bash icon=terminal theme={"system"}
> commit my changes with a descriptive message
```

You can also prompt for more complex Git operations:

```bash Bash icon=terminal theme={"system"}
> create a new branch called feature/quickstart
```

```bash Bash icon=terminal theme={"system"}
> show me the last 5 commits
```

```bash Bash icon=terminal theme={"system"}
> help me resolve merge conflicts
```

### 6. Improve the game

As we can see, this game needs improvement: the orbs' position overlaps with the paddle's position, which affects the gaming experience. Next, we will reposition the orbs to the top-right corner and add game restart functionality.

```bash Bash icon=terminal theme={"system"}
> Position the orbs in the top-right corner and support game restart functionality.
```

<Frame>
  <img />
</Frame>

This is the game preview after the improvement:

<Frame>
  <img />
</Frame>

## Try More Workflows

For reference, the following provides some prompt examples for different workflows:

* Code Refactoring

```bash Bash icon=terminal theme={"system"}
> Please refactor the current project using Next.js framework.
```

* Write Unit Tests

```bash Bash icon=terminal theme={"system"}
> Please write some unit tests for the pricing policy in the project.
```

* Update Documentation

```bash Bash icon=terminal theme={"system"}
> Please update the installation dependencies section in the README.
```

* Code Review

```bash Bash icon=terminal theme={"system"}
> Please review the changes and provide optimization suggestions.
```

## Common Commands

| Command | Description | Example |
| :- | :- | :- |
| `claude` | Start interactive mode | `claude` |
| `claude "task description"` | Run a one-time task | `claude "fix the build error"` |
| `claude -p "query"` | Run one-off query, then exit | `claude -p "explain this function"` |
| `claude -c` | Continue most recent conversation | `claude -c` |
| `claude -r` | Resume a previous conversation | `claude -r` |
| `claude commit` | Create a Git commit | `claude commit` |
| `/clear` | Clear conversation history | `> /clear` |
| `/help` | View available commands | `> /help` |
| `exit` or Ctrl+C | Exit Claude Code | `> exit` |


# CodeCompanion
Source: https://docs.novita.ai/guides/codecompanion

Supercharge Your Neovim Workflow with Novita AI and CodeCompanion.nvim.

CodeCompanion.nvim is a lightweight yet powerful Neovim plugin that connects advanced language models (LLMs) directly to your editor, enabling developers to work smarter and faster. With built-in support for Novita AI’s state-of-the-art models, this integration transforms your workflow by offering intelligent code suggestions, automated debugging, and streamlined refactoring tools.

In this comprehensive guide, we’ll walk you through the step-by-step process of setting up Novita AI with CodeCompanion.nvim. Learn how to optimize your Neovim setup and unlock the full power of AI-assisted coding for faster, smarter development.

## How to Leverage Novita AI with CodeCompanion.nvim

You can find the GitHub repository of CodeCompanion.nvim here: [olimorris/codecompanion.nvim](https://github.com/olimorris/codecompanion.nvim).

### Step 1: Generate Your Novita AI API Key

1. [Log in](https://novita.ai/user/login) to your Novita AI account.
2. Access the [Key Management Page](https://novita.ai/settings/key-management).
3. Create a new API key and copy it for later use.

### Step 2: Select a Model

1. Visit the[ Novita AI Model Library](https://novita.ai/models).
2. Choose a model that suits your needs (e.g., `meta-llama/llama-3.1-8b-instruct`).
3. Note down the model name.

### Step 3: Configure CodeCompanion

<Warning>
  We assume you have already installed the Neovim, and if not, you can install it by referring to the [Neovim Installation Guide](https://github.com/neovim/neovim/blob/master/INSTALL.md).
</Warning>

1. Open your [Neovim](https://neovim.io/) configuration file (`init.lua` or equivalent).

<Tip>
  The Neovim configuration file is typically located at: `~/.config/nvim/init.lua` (for macOS or Linux). And you can create this directory and file if they don't exist yet.
</Tip>

2. Install the CodeCompanion plugin.

You can install the plugin by referring to the [CodeCompanion.nvim Installation Guide](https://codecompanion.olimorris.dev/installation.html).

3. Add the following setup for CodeCompanion in your Neovim configuration file:

```lua theme={"system"}
require("codecompanion").setup({
    adapters = {
        novita = {
            base_url = "https://api.novita.ai/openai",
            api_key = "<YOUR_API_KEY>", -- Replace with your actual API key
            model = "meta-llama/llama-3.1-8b-instruct" -- Replace with your chosen model
        }
    }
})
```

4. Save and reload Neovim.

### Step 4: Verify Integration

1. Run the following command in Neovim:

```vim theme={"system"}
:checkhealth codecompanion
```

2. Ensure all dependencies are installed and configured correctly.

### Step 5: Test Novita AI Features

1. Open a code file in Neovim and start using CodeCompanion.nvim features such as inline suggestions or chat-based assistance.
2. For example:
   * Use shortcuts to trigger intelligent code completions.
   * Start a chat session by invoking `:CodeCompanionChat` for interactive problem-solving.


# Codex
Source: https://docs.novita.ai/guides/codex

Connect Codex to Novita's OpenAI-compatible LLM API endpoint.

Codex is an AI coding tool that runs in your terminal. You can connect Codex to Novita's OpenAI-compatible LLM API endpoint and use Novita models for code understanding, generation, refactoring, and debugging tasks.

<Warning>
  Codex currently supports the Responses API only. Configure `wire_api = "responses"` as shown below, and do not switch this integration to Chat Completions mode.
</Warning>

<Tip>
  Prefer one command? [Novita Connect](/guides/novita-connect) starts Codex on Novita models with `novi codex`, without editing `config.toml`.
</Tip>

## Prerequisites

Before you begin, prepare the following values:

* **API key**: Create or copy an API key from [Key Management](https://novita.ai/settings/key-management).
* **Base URL**: `https://api.novita.ai/openai/v1`
* **Model ID**: Copy the model ID you want to use from the Model Hub, such as `deepseek/deepseek-v4.1-flash`.

<Tip>
  Keep your API key private. Use it only in your local Codex configuration and never commit it to a Git repository.
</Tip>

## Install Codex

Before installing Codex, make sure [Node.js 18 or higher](https://nodejs.org/en/download/) is installed on your machine.

On macOS, if you use Homebrew, you can install Node.js with:

```bash theme={"system"}
brew install node
```

Check your Node.js and npm versions:

```bash theme={"system"}
node -v
npm -v
```

Install the Codex CLI:

```bash theme={"system"}
npm install -g @openai/codex
```

Check the Codex version:

```bash theme={"system"}
codex --version
```

## Configure your API key

Open your shell configuration file, for example `~/.zshrc`:

```bash theme={"system"}
nano ~/.zshrc
```

Add the following environment variable:

```bash theme={"system"}
export NOVITA_API_KEY="<your-novita-api-key>"
```

Reload your shell configuration and confirm the variable is available:

```bash theme={"system"}
source ~/.zshrc
echo $NOVITA_API_KEY
```

## Configure Codex

Open the Codex configuration file:

```bash theme={"system"}
mkdir -p ~/.codex
nano ~/.codex/config.toml
```

Add the following configuration, and replace `model` with the Novita model ID you want to use:

```toml theme={"system"}
model_provider = "novita"
model = "deepseek/deepseek-v4.1-flash"

[model_providers]
[model_providers.novita]
name = "Novita"
wire_api = "responses"
requires_openai_auth = true
base_url = "https://api.novita.ai/openai/v1"
env_key = "NOVITA_API_KEY"
```

## Start Codex

Go to your project directory and start Codex:

```bash theme={"system"}
cd <your-project-directory>
codex
```

You can now enter a coding task, for example:

```text theme={"system"}
Read this project and summarize the main modules.
```

If Codex returns an authentication or model error, check that:

* `NOVITA_API_KEY` is available in the current terminal session.
* `model` exactly matches the Model ID in Novita's Model Hub.
* `base_url` is set to `https://api.novita.ai/openai/v1`.


# Command Code
Source: https://docs.novita.ai/guides/command-code

Connect Command Code to Novita's OpenAI-compatible LLM API endpoint.

Command Code is an AI coding agent CLI that runs in your terminal. It supports Bring Your Own Key (BYOK) providers, so you can connect it to Novita's OpenAI-compatible LLM API endpoint and use Novita models for coding, debugging, refactoring, and project Q\&A.

<Tip>
  BYOK in Command Code is in beta. Provider configuration may change; check [Command Code's BYOK docs](https://commandcode.ai/docs/byok) if a step below doesn't match what you see.
</Tip>

## Prerequisites

Before you begin, prepare the following values:

* **API key**: Create or copy an API key from [Key Management](https://novita.ai/settings/key-management).
* **Base URL**: `https://api.novita.ai/openai/v1`
* **Model ID**: Copy the model ID you want to use from the Model Hub, such as `deepseek/deepseek-v4.1-flash`.

<Tip>
  Keep your API key private. Use it only in your local Command Code configuration and never commit it to a Git repository.
</Tip>

## Install Command Code

Command Code requires Node.js 22 or newer.

```bash theme={"system"}
node -v
```

Install Command Code globally with npm:

```bash theme={"system"}
npm i -g command-code@latest
```

Check the version:

```bash theme={"system"}
cmd --version
```

## Configure your API key

Open your shell configuration file, for example `~/.zshrc`:

```bash theme={"system"}
nano ~/.zshrc
```

Add the following environment variable:

```bash theme={"system"}
export NOVITA_API_KEY="<your-novita-api-key>"
```

Reload your shell configuration and confirm the variable is available:

```bash theme={"system"}
source ~/.zshrc
echo $NOVITA_API_KEY
```

## Add Novita as a BYOK provider

Command Code stores BYOK provider configuration in `~/.commandcode/providers.json`. Novita is already in Command Code's bundled provider registry, so you can connect it in a few steps through the interactive `/connect` menu, or add it by editing the file directly.

### Option 1: Interactive setup

In a Command Code session, run:

```text theme={"system"}
/connect
```

<Frame>
  <img />
</Frame>

Select **(BYOK) Add your own provider**:

<Frame>
  <img />
</Frame>

Type `novita` to find the bundled `novita-ai` provider entry (base URL `https://api.novita.ai/openai`) and select it:

<Frame>
  <img />
</Frame>

Paste your Novita API key when prompted. Command Code stores it in `~/.commandcode/auth.json`, never in `providers.json`:

<Frame>
  <img />
</Frame>

Novita's models now appear under `/model`. Pick one and start coding.

### Option 2: Edit providers.json directly

Open `~/.commandcode/providers.json` and add a `novita` entry:

```json theme={"system"}
// ~/.commandcode/providers.json
{
  "provider": {
    "novita": {
      "name": "Novita",
      "baseURL": "https://api.novita.ai/openai/v1",
      "apiKey": "$NOVITA_API_KEY",
      "models": { "deepseek/deepseek-v4.1-flash": {} }
    }
  }
}
```

<Tip>
  Novita's `/openai/v1` endpoint is OpenAI Chat Completions compatible, so you can omit the `api` field (it defaults to `openai-completions`). Add `contextWindow` per model if you want Command Code to show the correct context length instead of its 200K default; check the value for your model in the [Model Hub](https://novita.ai/models).
</Tip>

Changes to `providers.json` apply live. Reopen `/connect` or `/model` in Command Code to see the `novita` provider and its models.

## Start Command Code

Go to your project directory and start Command Code:

```bash theme={"system"}
cd <your-project-directory>
cmd
```

Open the model menu and select a Novita model:

```text theme={"system"}
/model
```

Send a test prompt:

```text theme={"system"}
In one sentence, tell me what programming tasks you can help with.
```

If the request fails, check that:

* `NOVITA_API_KEY` is available in the current terminal session, or the key is set directly in `providers.json`.
* The model ID exactly matches the Model ID in Novita's Model Hub.
* `baseURL` is set to `https://api.novita.ai/openai/v1`.


# Continue
Source: https://docs.novita.ai/guides/continue

Discover how Continue and Novita AI join forces to supercharge your software development workflow.

The powerful integration of Continue and Novita AI brings AI-powered development directly into your coding environment. This can help you access intelligent code suggestions, automated testing, and real-time pair programming without switching contexts. Novita AI excels at deploying AI models instantly through our intuitive API, scales effortlessly with our cost-effective GPU infrastructure, and maintains peak performance throughout your development process.

Our step-by-step guide walks you through Novita AI implementation on Continue, covering methods of using Continue.dev and integrating Novita AI with Continue using an API Key.

## How to use Continue.dev?

### VS Code

Step 1: Click `Install `to add the Continue extension from Visual Studio Marketplace.

Step 2: Click`Install`again in VS Code to complete the Continue extension setup.

Step 3: Move the Continue logo to your right sidebar for enhanced workflow access.

<Frame>
  <img alt="How to use Continue.dev-VS Code" />
</Frame>

### JetBrains

Step 1: Access IDE settings by pressing Ctrl + Alt + S in your JetBrains environment.

Step 2: Navigate to plugins and search Continue in the JetBrains marketplace.

Step 3: Click `Install`and find the Continue logo on your right toolbar.

<Frame>
  <img alt="How to use Continue.dev-JetBrains" />
</Frame>

## How to Integrate Novita AI with Continue Using an API Key

### Step 1: Open VS Code

<Frame>
  <img alt="How to Integrate Novita AI with Continue Using an API Key-Open VS Code" />
</Frame>

### Step 2: Search Continue

* Navigate to extensions and search Continue in the top search bar.

  <Frame>
    <img alt="How to Integrate Novita AI with Continue Using an API Key-Search Continue" />
  </Frame>

### Step 3: Install Continue

* Install the Continue extension by selecting the first result.

  <Frame>
    <img alt="How to Integrate Novita AI with Continue Using an API Key-Install Continue" />
  </Frame>

### Step 4: Click Continue

* Click the Continue icon on your left sidebar after installation completes.

  <Frame>
    <img alt="How to Integrate Novita AI with Continue Using an API Key-Click Continue" />
  </Frame>

### Step 5: Add your Chat Model (e.g. Novita AI)

* Select Novita AI from the provider menu in each marked location.

  <Frame>
    <img alt="How to Integrate Novita AI with Continue Using an API Key-Add your Chat Model 1" />
  </Frame>

### Step 6: Enter the API key from Novita AI and Get Connected

* Copy your Novita AI API Key from the user avatar section for authentication.

<Frame>
  <img alt="How to Integrate Novita AI with Continue Using an API Key- Enter the API key from Novita AI and Get Connected" />
</Frame>


# Cursor
Source: https://docs.novita.ai/guides/cursor

Learn how to integrate your Novita AI API keys with Cursor to unlock powerful AI models for programming. Get step-by-step instructions for seamless setup.

This guide will walk you through the steps needed to integrate Novita AI's models with Cursor. Using your own API keys, you can leverage Novita AI's large language models (LLMs) for custom AI messages in Cursor. This integration ensures that you can run AI-powered conversations and interactions while keeping full control over your API usage and cost.

## What is Cursor?

Cursor is a code editor built for programming with AI. It integrates with multiple large language models (LLMs) and allows you to input your own API keys, giving you full control over AI usage and costs. Whether you're coding or interacting with AI, Cursor streamlines the experience with features like smart autocomplete, auto-suggestions, and multi-model support, all within your development environment.

## Prerequisites

Before you begin the integration, ensure you have the following:

### **Novita AI LLM API Key**

* **Create an account**: Visit [Novita AI's website](https://novita.ai/) and sign up for an account.
* **Generate your API Key**: After logging in, navigate to the [Key Management](https://novita.ai/settings/key-management) page to generate your API key. This key is essential to connect Novita AI's models to Cursor.

  <Frame>
    ![Novita AI key management](https://mintlify.s3.us-west-1.amazonaws.com/novitaai/images/third-party/dify-1.png)
  </Frame>
* **Select a Model Name**: You'll need to copy the model name you want to use from Novita AI's [Model Library](https://novita.ai/models/llm/deepseek-deepseek-v4.1-flash). Some available models include:
  * `deepseek/deepseek-v4-pro`
  * `zai-org/glm-5.2`
  * `deepseek/deepseek-v4.1-flash`

    <Frame>
      <img alt="" />
    </Frame>

### **Download the Cursor App**

* Go to the official [Cursor website](https://cursor.com/) and download the Cursor app.
* Download Cursor App from [official website](https://cursor.com/)

## Integration Steps

### Connect Novita AI to Cursor

* Open the **Cursor App** and go to **Settings**.
* Navigate to the **Models** section.
* Uncheck all the other models that are pre-configured in Cursor.
* In the **Model Name** field, paste the model name you copied from the **Novita AI Model Library** (e.g., `deepseek/deepseek-v4.1-flash`).
* Enter your **Novita AI API key** in the designated field.
* Click the **Verify** button to ensure your API key is correct. Once validated, the API key will be activated.
* In the **Open AI Base URL** field, override the default URL with the Novita AI endpoint:[`https://api.novita.ai/openai`](https://api.novita.ai/openai)

By following these steps, you'll link your Novita AI API key with the Cursor app, enabling you to use Novita AI's models through CursorStart a Chat with AI in Cursor.

### Start a Chat with AI in Cursor

To open the **Chat** interface, either:

* Click on **Toggle AI Pane**, or
* Press the keyboard shortcut **Ctrl + Alt + B** to start a new chat.

You can now send prompts and interact with the models you've added.

<Frame>
  <img alt="Start a Chat with AI in Cursor" />
</Frame>

## Notes on Cursor Features

* **Tab Completion, Apply from Chat, and Composer**: These features require specialized models and will not work with custom API keys. If you wish to use these specific features, consider switching to the default models provided by Cursor.


# DeepSearcher
Source: https://docs.novita.ai/guides/deepsearcher

Easily access module support from Novita AI on DeepSearcher to build advanced search applications.

[DeepSearcher](https://github.com/zilliztech/deep-searcher) is an open-source solution designed to transform private data search and reasoning by integrating cutting-edge large language models (LLMs) with vector databases such as Milvus. With support for LLMs and embedding models from Novita AI, this powerful configuration delivers unmatched accuracy and efficiency in private data search.

This step-by-step guide will walk you through how to quickly and easily configure Novita AI with DeepSearcher.

## How to use DeepSearcher with Novita

Step 1: Follow the example in `examples/basic_example.py` .

<Frame>
  <img alt="Step1followtheexample Jpe" />
</Frame>

Step 2: Add the following code below the line `config = Configuration()` :

```python theme={"system"}
config.set_provider_config("llm", "NOVITA", {"model": "deepseek/deepseek-r1-turbo"})
config.set_provider_config("embedding", "NovitaEmbedding", {"model": "baai/bge-m3"})
```

<Frame>
  <img alt="Step2addcodebelowunderthelineconfig=configuration Jpe" />
</Frame>

Step 3: Run `examples/basic_example.py` to execute the integration.

You'll get the following results:

<Frame>
  <img alt="Output1 Jpe" />
</Frame>

<Frame>
  <img alt="Output2 Jpe" />
</Frame>

<Frame>
  <img alt="Output3 Jpe" />
</Frame>

<Frame>
  <img alt="Output4 Jpe" />
</Frame>

<Frame>
  <img alt="Output5 Jpe" />
</Frame>


# Dify
Source: https://docs.novita.ai/guides/dify

Learn how to integrate Novita AI’s DeepSeek LLMs with Dify to build intelligent, multi-turn AI applications for smarter, context-aware conversations.

With the Novita AI & Dify integration, you gain seamless access to a comprehensive suite of Novita AI LLM models, including DeepSeek, Llama, Qwen, and more, enabling you to effortlessly build and deploy advanced AI applications tailored to your needs.

This guide will walk you through integrating Novita AI’s DeepSeek R1 model with the Dify platform, enabling you to create AI applications with advanced multi-turn reasoning capabilities. DeepSeek ensures your AI applications understand the context and can hold natural, dynamic conversations, making interactions feel human-like.

## What is Dify?

**Dify** is an open-source platform that simplifies the development of generative AI applications. Whether you’re building a chatbot, knowledge assistant, or other AI-powered tools, Dify makes it easy to integrate advanced language models like **Novita AI’s DeepSeek** and deploy them quickly, with minimal coding.

### Key Features of Dify:

* **Visual Development**: Dify’s drag-and-drop interface allows you to quickly create and deploy applications without extensive coding, reducing development time.
* **Knowledge Base Augmentation**: Enhance AI responses using **Retrieval-Augmented Generation (RAG)**. This feature connects your AI to internal documents or specialized data for accurate, contextual, and informative answers.
* **Workflow Expansion**: Integrate sophisticated logic into your AI apps with **functional nodes**. You can also connect third-party platforms for additional functionality.
* **Data Insights**: Track important performance metrics such as conversations, engagement, and response quality. Dify also integrates with specialized analytics platforms to monitor and improve AI performance.

## Prerequisites

Before you begin, make sure you have:

* **Novita AI LLM API Key**:
  * Visit [Novita AI’s website](https://novita.ai) and create an account.
  * After logging in, go to the [**Key Management**](https://novita.ai/settings/key-management) page to generate your **API Key**. This key is required to connect Novita AI’s models to Dify.

    <Frame>
      ![Novita AI key management](https://mintlify.s3.us-west-1.amazonaws.com/novitaai/images/third-party/dify-1.png)
    </Frame>
* **Dify Account**:
  * Sign up for a Dify account at [Dify.ai](https://dify.ai) to start building AI applications.

## Integration Steps

### 1. Connect Novita AI to Dify

To connect Novita AI’s models with Dify:

* Log in to your Dify account.
* Click on your profile icon or name in the top-right corner and select **Settings**.
* In the **Model Providers** section, find **Novita AI** in the list.
* Paste your **Novita AI API Key** into the provided field and click **Save**.

With this integration, you’ll now have access to **DeepSeek R1** and other Novita AI models directly in Dify.

### 2. Create a DeepSeek AI Application

Once the integration is complete, you can create an application powered by DeepSeek R1:

* From the Dify homepage, click **Create Blank App** in the left sidebar.
* Choose **Chatbot** as the application type.

  <Frame>
    <img alt="Choose Chatbot as the application type" />
  </Frame>
* Give your app a name (e.g., “DeepSeek R1 Bot”) and click **Create**.

  <Frame>
    <img alt="Give your app a name and click Create." />
  </Frame>
* From the **Model** dropdown, select **Novita AI DeepSeek R1**.

  <Frame>
    <img alt="From the Model dropdown, select Novita AI DeepSeek R1" />
  </Frame>

### 3. Enable Knowledge Base for Enhanced Text Analysis

To improve your AI’s response accuracy, augment it with a **knowledge base**. Using **Retrieval-Augmented Generation (RAG)**, your AI will be able to access documents and generate more contextually relevant responses.

#### Step 1: Create a Knowledge Base

* In Dify, go to the **Knowledge Base** section and click **Create Knowledage**.

  <Frame>
    <img alt="go to the Knowledge Base section and click Create Knowledage." />
  </Frame>
* Upload documents (e.g., guides, FAQs, manuals) that provide relevant information for your AI to use.

  <Frame>
    <img alt="Upload documents " />
  </Frame>
* Use **Parent-Child Segmentation Mode** to maintain document hierarchy and context, ensuring DeepSeek processes the content correctly and understands relationships between sections.

  <Frame>
    <img alt="Use Parent-Child Segmentation Mode " />
  </Frame>

#### Step 2: Integrate the Knowledge Base into Your AI App

* In your chatbot’s **Context Settings**, click the option to **Add Knowledge Base**.

  <Frame>
    <img alt="Add Knowledge Base" />
  </Frame>
* Choose the documents you uploaded and integrate them into your app’s context to improve its responses.

#### Step 3: Share Your AI Application

Once your AI app is ready, you can share or embed it on external platforms:

<Frame>
  <img alt="Share Your AI Application" />
</Frame>

* **Public Link**: Generate a public link for others to access your AI application.
* **Embed on Websites**: Embed your app directly onto your website using Dify’s provided embed code.

### 4. Enhance AI Capabilities with Workflow-based Applications

If you need more than just a chatbot, Dify supports **workflow-based applications**. This allows you to add custom business logic and extend your AI’s capabilities by using functional nodes.

* Choose **Workflow** as the application type.
* Use **drag-and-drop nodes** to define your app’s behavior based on conditions or actions.
* Integrate external APIs (e.g., Google Search, databases) to provide richer data for your AI to process, enabling more insightful and automated responses.

  <Frame>
    <img alt="Workflow-based Applications" />
  </Frame>

Integrating Novita AI’s DeepSeek R1 with Dify provides a robust platform for creating advanced AI applications. With DeepSeek’s multi-turn reasoning, your AI App can have more dynamic, context-aware conversations, making it highly effective for building chatbots, knowledge assistants, and more.


# DocsGPT
Source: https://docs.novita.ai/guides/docsgpt

Seamlessly integrate Novita AI with DocsGPT to unlock powerful AI models for enhanced workflows.

DocsGPT simplifies documentation with AI-powered assistance. Integrating it with Novita AI enhances its performance, offering faster processing, scalable resources, and advanced model support for improved productivity.

This guide walks you through how to use DocsGPT with Novita AI based on the OpenAl APl, offering a way to query your content and get served customized answers.

## **How to use DocsGPT**

### **Prerequisites:**

**Docker:** Ensure you have [Docker ](https://docs.docker.com/engine/install/)installed and running on your system.

### **Launching DocsGPT (macOS and Linux)**

For macOS and Linux users, the easiest way to launch DocsGPT is using the provided `setup.sh` script. This script automates the configuration process and offers several setup options.

Step 1: Download the DocsGPT repository

* First, you need to download the DocsGPT repository to your local machine. You can do this using Git:

```bash theme={"system"}
git clone https://github.com/arc53/DocsGPT.git
cd DocsGPT
```

Step 2: Run the `setup.sh` script

* Navigate to the DocsGPT directory in your terminal and execute the `setup.sh` script:

```bash theme={"system"}
./setup.sh
```

This interactive script will guide you through setting up DocsGPT. It offers four options: using the public API, running locally, connecting to a local inference engine, or using a cloud API provider. The script will automatically configure your `.env` file and handle necessary downloads and installations based on your chosen option.

### **Launching DocsGPT (Windows)**

For Windows users, please refer to the [Docker Deployment documentation](https://docs.docsgpt.cloud/Deploying/Docker-Deploying) for detailed step-by-step instructions on setting up DocsGPT using Docker.

## **How to Integrate Novita AI API with DocsGPT**

Step 1: Log in to[ Novita AI ](https://novita.ai)and create an [API Key](https://novita.ai/settings/key-management)

<Frame>
  <img alt="images/3-1LogintoNovitaAI.png" />
</Frame>

<Frame>
  <img alt="images/3-2CreateanAPIKey.png" />
</Frame>

Step 2: Select option4 and connect cloud API provider in your terminal

<Frame>
  <img alt="images/2SelectOption4andConnectCloudAPIProvider.png" />
</Frame>

Step 3: Choose option7 Novita and enter the API key you just created

<Frame>
  <img alt="images/4ChooseOption7NovitaandEntertheAPIKeyYouJustCreated.png" />
</Frame>

Step 4: Wait for the startup process to complete

<Frame>
  <img alt="images/5WaitfortheStartupProcesstoComplete.png" />
</Frame>

Step 5: Access DocsGPT in your browser

* Once the setup is complete and Docker containers are running, navigate to [http://localhost:5173/](http://localhost:5173) in your web browser to access the DocsGPT web application.

<Frame>
  <img alt="images/6AccesstheApplicationathttp-localhost-5173.jpeg" />
</Frame>

Step 6: Stopping DocsGPT

* To stop DocsGPT, simply open a new terminal in the `DocsGPT` directory and run:

```bash theme={"system"}
docker compose -f deployment/docker-compose.yaml down
```

* (or the specific `docker compose` command shown at the end of the `setup.sh` execution, which may include optional compose files depending on your choices).


# Common Error Codes
Source: https://docs.novita.ai/guides/error



This document summarizes the most common error codes returned by the Novita API platform, along with definitions, causes, and recommended solutions to help users troubleshoot efficiently.

***

## Error Code 400

**Description**: Invalid request parameters.\
**Solution**:\
Review the error message details and check whether the parameter formats, field names, or value ranges comply with the API documentation.

***

## Error Code 401

**Description**: API Key is missing or incorrect.\
**Common causes**:

* **Missing key**: no `Authorization` header, or the header is not in the form `Authorization: Bearer <API Key>`.
* **Wrong or malformed key**: a typo, extra whitespace, or a value that is not a valid `sk_` key.
* **Deleted key**: the key was deleted in [Key Management](https://novita.ai/settings/key-management), so it no longer authenticates.

**Solution**:

* Ensure the API Key is provided in the request and uses the `Bearer` scheme;
* Verify that the API Key is correct and still exists in Key Management;
* If using environment variables or config files, confirm they are being read correctly during execution. See [API Keys](/guides/llm-api-keys) for how to store and load a key.

***

## Error Code 403

**Description**: Access denied due to insufficient permissions.

### Model access denied (`model_access_denied`)

The API key is valid, but it is not allowed to call the requested model. The key's [model access](/guides/llm-model-access) is restricted, and the requested model is outside its range.

**Solution**:

* Call a model that is within the key's access range, or
* Ask your team admin to adjust the key's [model access](/guides/llm-model-access) to include the model.

### Model requires identity verification

Some models require identity verification before your account can access them.

**Solution**:

* Verify that your account associated with the API Key has permission to access the requested model;
* Log in to the console and check your account’s verification status;
* If not verified, complete identity verification first;
* Alternatively, use an API Key from an already verified account.

***

## Error Code 429

**Description**: Rate limit exceeded (Too Many Requests).\
**Solution**:

* Check if the limit is due to **TPM** (tokens per minute) or **RPM** (requests per minute);
* Refer to the official Rate Limits documentation;
* To raise your rate limit, contact support or use a verified account.

***

## Error Code 503 / 504

**Description**: Backend timeout or service unavailable, often caused by high system load or throttling.

### Possible Causes:

* GPU or CPU overload on model service nodes;
* Long generation time on non-streaming requests exceeds gateway timeout;
* Failures in downstream services (e.g., Redis, model engine);
* Traffic shaping module activated surge protection and returned 503.

### Recommended Solutions:

**For API Users**:

* **Enable retry mechanism**: Use exponential backoff to prevent repeated overload;
* **Switch to streaming mode**: Streaming responses return tokens as they’re generated, lowering latency and timeout risk;
* **Optimize client settings**: Ensure `client_timeout` and `proxy_timeout` exceed 60 seconds;
* **Avoid peak periods**: For high concurrency scenarios, retry during off-peak hours.

**For Platform Ops**:

* Enhance monitoring and auto-scaling of model services;
* Adjust gateway-level `proxy_read_timeout` appropriately;
* Implement fine-grained throttling rules (e.g., priority queues, core-business prioritization);
* Use Prometheus + Alertmanager to trigger alerts on 503/504 spikes.

***

## Error Code 500

**Description**: Internal server error—typically caused by backend exceptions or model engine crashes.\
**Solution**:

* These issues usually require platform-side resolution. Contact support to investigate logs and system resources;
* Optionally, try switching models or falling back to a less resource-intensive configuration.

***

## Other Errors

For undefined or undocumented errors:

* First, refer to the `message` field in the API response;
* Next, check request logs or console traces;
* Finally, contact Novita support or submit a ticket for further assistance.


# Helicone
Source: https://docs.novita.ai/guides/helicone

Get Started with Helicone Monitoring for Novita AI: Step-by-Step LLM Integration Guide.

Novita AI's integration with Helicone's observability platform marks a significant advancement in LLM development. This combination provides developers with a streamlined inference platform and comprehensive monitoring capabilities, transforming how teams work with Large Language Models.

By leveraging Helicone's monitoring tools through Novita AI's infrastructure, developers gain access to robust analytics and performance insights. This guide will walk you through setting up Helicone AI Gateway, obtaining Novita AI API Key, and integrating Novita AI API with Helicone.

## **Setting Up Helicone AI Gateway**

Getting started with [Helicone AI Gateway ](https://www.helicone.ai/)only requires four steps: creating an account, generating an API key, picking your preferred integration method, and sending your first request.

### **Step1: Create an Account**

* After account creation, you can move forward with the setup process.

### **Step 2: Generate an API Key**

* Go to Settings: Locate and select your organization name in the upper-left corner, then select `Settings` from the dropdown.

  <Frame>
    <img alt="Step 2- Generate an API Key-Go to Settings-page" />
  </Frame>
* Select the API Key Tab.

  <Frame>
    <img alt="Step 2- Generate an API Key-Select the API Key Tab-page" />
  </Frame>
* Generate New Key: Click on `Generate new key`. During the API key creation process, you can enable `read` and `write` permissions. Write keys enable access to Helicone through our proxy service, feedback or any other Helicone service when calling  `POST` or using our gateway.

  <Frame>
    <img alt="Step 2- Generate an API Key-Select the API Key Tab-page" />
  </Frame>

### **Step 3: Pick Your Preferred Integration Method**

* Choose your provider from the options below to view specific instructions.

### **Step 4: Send Your First Request**

* Upon receiving your requests, you will see them in the `Requests` tab.
* Your new request will be immediately visible on the `Dashboard`.

## **Obtaining Novita AI API Key**

To obtain your [Novita AI API key](https://novita.ai/), simply log into your account, navigate to the LLM API key management section, and add the necessary credits to begin.

### **Step 1: Go to Novita AI and Log in**

* Novita AI offers multiple login options: use your Google or GitHub credentials for instant account creation, or register directly with your email address.

  <Frame>
    <img alt="" />
  </Frame>

### **Step 2: Manage Novita AI LLM API Key**

Novita AI ensures secure API access by using Bearer authentication, where your API key is included in the header format "Authorization: Bearer \{\{API Key}}".

* Your first login generates a default key automatically - access all keys through the "Key Management" section in settings.
* Additional keys can be created using the “+ Add New Key” function.

  <Frame>
    <img alt="" />
  </Frame>

## **How to Integrate Novita AI API with Helicone**

The Novita AI-Helicone integration requires three simple steps: accessing both platform accounts, configuring your API keys as environment variables (HELICONE\_API\_KEY and NOVITA\_API\_KEY), and updating the base URL with proper authentication headers.

### **Step 1: Log into your Novita AI Account and Helicone Account**

* Access your [Novita AI](https://novita.ai/) account or create one, then generate your API key directly from the dashboard.
* Sign in to [Helicone](https://www.helicone.ai/) (or create a new account) to obtain your API key.

### **Step 2: Set HELICONE\_API\_KEY and NOVITA\_API\_KEY as Environment Variables**

```bash theme={"system"}
$env:HELICONE_API_KEY="<your API key>"
$env:NOVITA_API_KEY="<your API key>"
```

### **Step 3: Modify the Base URL and Add Auth Headers**

* Update the following Novita AI URL with the Helicone Gateway URL.

[`https://api.novita.ai`](https://api.novita.ai) -> [`https://novita.helicone.ai`](https://novita.helicone.ai)

* Then add the following headers for authentication:

```bash theme={"system"}
* curl -H "Authorization: Bearer <your API key>"
```

**Example**

* Now you can use this simple fetch call to interact with any Novita AI model.
* ```bash theme={"system"}
  curl \
  --header 'Authorization: Bearer <NOVITA_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "model": "deepseek/deepseek-v4.1-flash",
    "messages": [
      {
        "role": "user",
        "content": "What language is mostly spoken in Singapore?"
      }
    ]
  }' \
  --url https://novita.helicone.ai/v3/chat/completions
  ```


# Hermes
Source: https://docs.novita.ai/guides/hermes

Connect Hermes to Novita's OpenAI-compatible LLM API endpoint.

Hermes is an agent tool from Nous Research. It can connect to a custom OpenAI-compatible endpoint, so you can use Novita LLM models inside Hermes for local agent workflows.

## Prerequisites

Before you begin, prepare the following values:

* **API key**: Create or copy an API key from [Key Management](https://novita.ai/settings/key-management).
* **Base URL**: `https://api.novita.ai/openai/v1`
* **Model ID**: Copy the model ID you want to use from the Model Hub, such as `deepseek/deepseek-v4.1-flash`.

<Tip>
  Hermes stores endpoint and API key settings locally. Do not commit any generated configuration that contains your API key.
</Tip>

## Install Hermes

Hermes provides terminal and desktop installation options. Choose the one that fits your workflow.

### Linux / macOS / WSL2

```bash theme={"system"}
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
```

### Windows PowerShell

```bash theme={"system"}
iex (irm https://hermes-agent.nousresearch.com/install.ps1)
```

### Desktop

You can also download Hermes Desktop from:

```text theme={"system"}
https://hermes-agent.nousresearch.com/desktop
```

## Configure the Novita endpoint

After installation, run the Hermes setup flow. The exact command may vary by Hermes version, so follow the terminal prompts from your installed version.

During setup, use the following choices and values:

1. Select **Custom endpoint** or the option that lets you enter a provider URL manually.
2. Set the Base URL to `https://api.novita.ai/openai/v1`.
3. Enter your Novita API key.
4. For API type, keep the default auto-detection option. If you need to choose manually, select the OpenAI-compatible or Chat Completions mode.
5. Select a model from the list, or enter a Novita model ID manually, such as `deepseek/deepseek-v4.1-flash`.
6. Set max tokens based on the model context length. If you are not sure, keep the default or auto-detected value.
7. Give the model configuration a recognizable name, such as `novita-deepseek-v4.1-flash`.
8. If you only want to run Hermes locally, choose the default Local runtime.
9. If you do not need channels such as Telegram, Slack, or Matrix, skip integration channels.

## Reload your shell

If Hermes asks you to reload your shell, run:

```bash theme={"system"}
source ~/.zshrc
```

## Start Hermes

Start Hermes from your terminal:

```bash theme={"system"}
hermes
```

Send a test message, for example:

```text theme={"system"}
Hello, briefly describe what coding tasks you can help with.
```

If the request fails, check that:

* Your API key is correct.
* The Base URL is `https://api.novita.ai/openai/v1`.
* The model ID matches the one shown in Novita's Model Hub.
* The selected API type is compatible with an OpenAI-compatible endpoint.


# Hugging Face
Source: https://docs.novita.ai/guides/huggingface

Get a simple guide to streamline AI deployment with Novita AI + Hugging Face. 

Novita AI's integration with Hugging Face Platform enables advanced serverless inference capabilities. This provides direct access to Hub model pages through optimized infrastructure, offering developers a streamlined setup experience. With full support for Hugging Face's JavaScript and Python SDKs, Novita AI simplifies model deployment and scaling without infrastructure management.

Our comprehensive guide walks you through Novita AI implementation on Hugging Face, covering both web interface and SDK integration methods.

## **Using Novita AI on Hugging Face in the Website UI**

### **Step 1: Configure API Keys**

* Access your account settings dashboard to configure your API keys.
* Input your Novita AI authentication credentials into the Hugging Face platform.

  <Frame>
    <img alt=" Configure API Keys in Hugging Face" />
  </Frame>

### **Step 2: Choose Inference API Modes**

* Custom Key Mode: Calls are sent directly to the inference provider, utilizing your own API key.
* HF-Routed Mode: In this mode, no provider token is required. Charges are applied to your Hugging Face account instead of the provider's account.

  <Frame>
    <img alt="Choose Inference API Modes" />
  </Frame>

### **Step 3: Explore Compatible Providers on Model Pages**

* Model pages display third-party inference providers compatible with the selected model (the ones that are compatible with the current model, sorted by user preference).

  <Frame>
    <img alt=" Explore Compatible Providers on Model Pages in Hugging Face" />
  </Frame>

## **Using Huggingface\_hub from Python by the Client SDKs**

### **Step 1: Install** [**Huggingface\_hub**](https://github.com/huggingface/huggingface_hub)

```python theme={"system"}
pip install huggingface_hub
```

### **Step 2: Call model API in Python**

```python theme={"system"}
from huggingface_hub import InferenceClient


client = InferenceClient(
    provider="novita",
    api_key="xxxxxxxxxxxxxxxxxxxxxxxx", # optional, required from 2nd calling, get from https://novita.ai/settings/key-management
)

# an example question
messages = [
    dict(
        role="user",
        content='Sally (a girl) has 3 brothers. Each brother has 2 sisters. How many sisters does Sally have?',
    ),
]
completion = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-R1",
    messages=messages,
    max_tokens=512,
)

print(completion.choices[0].message)
```

## **Using Huggingface\_hub from JS by the Client SDKs**

```json theme={"system"}
import { HfInference } from "@huggingface/inference";

const client = new HfInference("xxxxxxxxxxxxxxxxxxxxxxxx");

const chatCompletion = await client.chatCompletion({
    model: "deepseek-ai/DeepSeek-R1",
    messages: [
        {
            role: "user",
            content: "What is the capital of France?"
        }
    ],
    provider: "novita",
    max_tokens: 500
});

console.log(chatCompletion.choices[0].message)
```


# Build with Novita AI
Source: https://docs.novita.ai/guides/introduction

Access AI models, run code in agent sandboxes, or deploy on GPUs. Choose a task below and get to a working result quickly.

<CardGroup>
  <Card title="Call a model API" icon="code" href="#choose-api-path">
    Add hosted AI models to your application with an OpenAI-compatible API.
  </Card>

  <Card title="Run code in a Sandbox" icon="box" href="/guides/sandbox-your-first-agent-sandbox?utm_source=docs&utm_medium=introduction&utm_campaign=introduction_update&utm_content=sandbox">
    Give your agent an isolated environment to create a sandbox and run code.
  </Card>
</CardGroup>

<div />

## Choose how you want to call a model

<CardGroup>
  <Card title="Send your first request" icon="terminal" href="/guides/quickstart?utm_source=docs&utm_medium=introduction&utm_campaign=introduction_update&utm_content=llm-manual#manual-api-request">
    Create an API key, send a small LLM request, and verify the response yourself.
  </Card>

  <Card title="Use your agent" icon="wand-sparkles" href="/guides/quickstart?utm_source=docs&utm_medium=introduction&utm_campaign=introduction_update&utm_content=llm-agent#use-your-agent">
    Ask an agent to help connect to Novita AI, then verify the real API response.
  </Card>
</CardGroup>

## Before you run

* Sign in or [create a Novita account](https://novita.ai/user/register).
* Create or manage an [API key](https://novita.ai/settings/key-management).
* Confirm that your account has enough [credit](https://novita.ai/billing) for the product you want to use.

<Accordion title="Use Novita AI with your agent">
  **No install — paste this into your agent chat:**

  ```text theme={"system"}
  Read https://raw.githubusercontent.com/novitalabs/novita-skills/main/skills/novita-ai/SKILL.md and use Novita AI for this task.
  ```

  **Optional — install once if your agent supports skills:**

  ```bash theme={"system"}
  npx skills add novitalabs/novita-skills --skill novita-ai
  ```

  The `novita-ai` skill covers model APIs, GPUs, Agent Sandbox, integrations, and troubleshooting. Your agent still needs a valid API key and available credit when it makes a request.

  [View the skill on GitHub →](https://github.com/novitalabs/novita-skills)
</Accordion>

## Explore Novita AI

<CardGroup>
  <Card title="Model APIs" icon="box" href="/guides/model-apis-overview">
    Explore hosted models and API capabilities.
  </Card>

  <Card title="GPUs" icon="cpu" href="/guides/gpus-overview">
    Run and manage your own models on dedicated GPUs.
  </Card>

  <Card title="API Reference" icon="book-open" href="/api-reference/api-reference-overview">
    Look up endpoints, parameters, authentication, and responses.
  </Card>

  <Card title="API errors" icon="triangle-alert" href="/guides/error">
    Find causes and fixes for common API errors.
  </Card>

  <Card title="API key management" icon="key" href="https://novita.ai/settings/key-management">
    Create, store, and configure API keys.
  </Card>

  <Card title="Billing" icon="credit-card" href="https://novita.ai/billing">
    Check credit, usage, and payment settings.
  </Card>
</CardGroup>


# LangChain
Source: https://docs.novita.ai/guides/langchain

Integrate Novita AI with LangChain to build intelligent, language model-driven applications. Learn setup, API usage, and function calling workflows.

This guide will walk you through the process of integrating Novita AI with LangChain. You’ll be able to use Novita AI’s powerful language models with LangChain’s robust tools for building language model-driven applications.

## What is LangChain?

LangChain is a framework for developing applications powered by language models. It enables applications that:

* **Are context-aware**: LangChain connects a language model to sources of context (such as prompt instructions, few-shot examples, content to ground its response in, etc.).
* **Reason**: LangChain allows language models to reason—whether it’s determining how to answer based on provided context or deciding what actions to take.

With LangChain, you can build complex workflows, enhance model behavior with external knowledge, and create intelligent systems that can interact dynamically with users and data sources.

## Prerequisites

Before you start, make sure you have the following:

* **Novita AI LLM API Key**:
  * Visit [Novita AI’s website](https://novita.ai/) and create an account.
  * After logging in, go to the [**Key Management**](https://novita.ai/settings/key-management) page to generate your **API Key**. This key is required to connect Novita AI’s models to LangChain.

    <Frame>
      ![Novita AI key management](https://mintlify.s3.us-west-1.amazonaws.com/novitaai/images/third-party/dify-1.png)
    </Frame>
* A basic understanding of Node.js, JavaScript, and how to use environment variables.

## Integration

### Step 1: Set the API Key

For most environments, set the environment variable `NOVITA_API_KEY` as follows:

```bash theme={"system"}
export NOVITA_API_KEY="your-api-key"
```

Make sure to replace `your-api-key` with the actual key you got from Novita AI.

### Step 2: Install the Required Packages

To integrate Novita AI with LangChain, you need to install the `@langchain-community` package, which includes the Novita AI integration.

Choose one of the following commands to install the necessary packages:

**Using npm:**

```bash theme={"system"}
npm install @langchain/community @langchain/core
```

**Using yarn:**

```bash theme={"system"}
yarn add @langchain/community @langchain/core
```

**Using pnpm:**

```bash theme={"system"}
pnpm add @langchain/community @langchain/core
```

### Step 3: Instantiate the Novita AI Model

Once you’ve installed the necessary packages, you can instantiate the Novita AI model using the `ChatNovitaAI` class.

Here’s an example that demonstrates how to do that:

```javascript theme={"system"}
import { ChatNovitaAI } from "@langchain/community/chat_models/novita";

const llm = new ChatNovitaAI({
  model: "deepseek/deepseek-v4.1-flash", // You can choose the model you want to use
  temperature: 0, // Optional: Controls randomness. 0 is deterministic.
  // other parameters can be set here...
});
```

### Step 4: Invoke the Model for Chat Completion

Once the model is instantiated, you can use it to generate chat completions by invoking it with a message.

Here's an example of how to send a message and get a response:

```javascript theme={"system"}
const aiMsg = await llm.invoke([
  {
    role: "system",
    content: "You are a helpful assistant that translates English to French. Translate the user sentence.",
  },
  {
    role: "human",
    content: "I love programming.",
  },
]);

console.log(aiMsg.content); // The model’s response will be printed here
```

### Step 5: Chain Model with Prompt Templates

LangChain allows you to create powerful workflows by chaining models together with prompt templates. This can be especially useful when you want to reuse the same format for multiple inputs.

Here’s an example where we chain the Novita AI model with a custom prompt template for translating between languages:

```javascript theme={"system"}
import { ChatPromptTemplate } from "@langchain/core/prompts";

// Create a template for translating languages
const prompt = ChatPromptTemplate.fromMessages([
  [
    "system",
    "You are a helpful assistant that translates {input_language} to {output_language}.",
  ],
  ["human", "{input}"],
]);

// Chain the prompt with the model
const chain = prompt.pipe(llm);

// Invoke the chain with inputs for translation
const result = await chain.invoke({
  input_language: "English",
  output_language: "German",
  input: "I love programming.",
});

console.log(result.content); // The translated text will be printed here
```

### Step 6: Customize the Workflow

You can modify the temperature, add more messages, or tweak other parameters depending on your use case. LangChain is highly flexible, allowing you to design complex interactions by chaining multiple prompts, adding conditional logic, or working with different models.

## Function Calling with Novita AI and LangChain

To implement function calling (or tool usage) with Novita AI's LLM API, LangChain can serve as a convenient framework. In this example, we’ll create a simple math application that allows the model to perform addition and multiplication operations via function calls.

💡 While this guide uses LangChain for convenience, implementing function calling doesn’t require any specific framework. The key is designing the right prompts to make the model understand and correctly invoke functions. LangChain is used here simply to streamline the implementation.

### Prerequisites

First, install the required packages:

```bash theme={"system"}
pip install langchain-openai python-dotenv
```

### Setting Up the Environment

Create a `.env` file in your project root and add your Novita AI API key:

```
NOVITA_API_KEY=your_api_key_here
```

### Implementation Steps

1. **Define the Tools**

First, let’s create two simple mathematical tools using LangChain's `@tool` decorator:

```python theme={"system"}
from langchain_core.tools import tool

@tool
def multiply(x: float, y: float) -> float:
    """Multiply two numbers together."""
    return x * y

@tool
def add(x: int, y: int) -> int:
    """Add two numbers."""
    return x + y

tools = [multiply, add]
```

2. **Create the Tool Execution Function**

Next, implement a function to execute the tools:

```python theme={"system"}
from typing import Any, Dict, Optional, TypedDict
from langchain_core.runnables import RunnableConfig

class ToolCallRequest(TypedDict):
    name: str
    arguments: Dict[str, Any]

def invoke_tool(
    tool_call_request: ToolCallRequest, 
    config: Optional[RunnableConfig] = None
):
    """Execute the specified tool with given arguments."""
    tool_name_to_tool = {tool.name: tool for tool in tools}
    name = tool_call_request["name"]
    requested_tool = tool_name_to_tool[name]
    return requested_tool.invoke(tool_call_request["arguments"], config=config)
```

3. **Set Up the LangChain Pipeline**

Create a chain that uses Novita AI's LLM to select and prepare tool calls:

```python theme={"system"}
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import render_text_description
import os

def create_chain():
    """Create a chain that uses the specified LLM model to select and prepare tool calls."""
    model = ChatOpenAI(
        model="meta-llama/llama-3.3-70b-instruct",
        api_key=os.getenv("NOVITA_API_KEY"),
        base_url="https://api.novita.ai/openai",
    )
    
    rendered_tools = render_text_description(tools)
    system_prompt = f"""\
    You are an assistant that has access to the following set of tools. 
    Here are the names and descriptions for each tool:

    {rendered_tools}

    Given the user input, return the name and input of the tool to use. 
    Return your response as a JSON blob with 'name' and 'arguments' keys.

    The `arguments` should be a dictionary, with keys corresponding 
    to the argument names and the values corresponding to the requested values.
    """

    prompt = ChatPromptTemplate.from_messages(
        [("system", system_prompt), ("user", "{input}")]
    )

    return prompt | model | JsonOutputParser()
```

4. **Create the Main Processing Function**

Implement the main function that processes mathematical queries:

```python theme={"system"}
def process_math_query(query: str):
    """Process a mathematical query by using an LLM to select the appropriate tool and execute it."""
    chain = create_chain()
    message = chain.invoke({"input": query})
    result = invoke_tool(message, config=None)
    return message, result
```

5. **Usage Example**

Here’s how to use the implementation:

```python theme={"system"}
if __name__ == "__main__":
    message, result = process_math_query(
        "meta-llama/llama-3.3-70b-instruct", 
        "what's 3 plus 1132"
    )
    print(result)  # Output: 1135
```


# Langflow
Source: https://docs.novita.ai/guides/langflow

Six steps to achieve Novita AI's LLM API integration with Langflow, enabling faster application deployment and simplified workflows.

The integration of Langflow and Novita AI creates a powerful development environment that streamlines the creation of multi-agent and RAG applications. This combination leverages Langflow's intuitive visual flow builder for rapid prototyping while enabling seamless workflow design and testing capabilities. Novita AI enhances this ecosystem with its advanced Model APIs, featuring industry-leading language models including Llama, DeepSeek, and Mistral.

This step-by-step guide will help you access Novita AI LLM API on Langflow and start building AI-powered applications.

## **Accessing Novita AI LLM API on Langflow**

### Step 1: **Expand the "Models" Tag**

* Find and expand the "Models" section on Langflow’s sidebar.

  <Frame>
    <img alt="Expand the Models tag on Langflow" />
  </Frame>

### **Step 2: Drag Novita AI to Your Canvas**

* Select and position the "Novita AI "component from the sidebar onto the Langflow canvas area.

  <Frame>
    <img alt="Drag Novita AI to Your Canvas on Langflow" />
  </Frame>

### **Step 3: Create a Global Variable for the Novita AI API Key**

* Set up Langflow authentication instantly by adding your Novita AI API Key as a global variable.

  <Frame>
    <img alt="Create a Global Variable for the Novita AI API Key on Langflow" />
  </Frame>

### Step 4: **Get your Novita AI API Key**

* Generate Your API Keys Instantly on the [Key Management page](https://novita.ai/settings/key-management).

### Step 5: **Select a Model**

* Choose from Novita AI's powerful language models: DeepSeek, Llama, and Mistral and then select the perfect AI model for your project needs.

  <Frame>
    <img alt="Select a Model on Langflow" />
  </Frame>

### **Step 6: Start Building Your AI App**

* Create advanced AI functionality by connecting Novita AI components to your Langflow workflow system.


# Langfuse
Source: https://docs.novita.ai/guides/langfuse

Step-by-step guide to set up Langfuse with Novita AI to develop, monitor, evaluate, and debug AI applications.

With Langfuse, your team can collaboratively debug, analyze, and iterate on their LLM applications built with Novita AI. Its fully integrated features streamline the development workflow, enhancing efficiency and accelerating progress.

This guide shows you how to integrate Novita AI with Langfuse. Novita AI's API endpoints for chat, language and code are fully compatible with OpenAI's API. This allows us to use the Langfuse OpenAI drop-in replacement to trace all parts of your application.

# Prerequisites

Before you begin, make sure you have the following:

* Novita AI LLM API Key:
  * Visit Novita AI’s website to create an account.
  * Log in and go to the Key Management page to generate your API Key, which is essential for integrating Novita AI’s models with Langfuse.

    <Frame>
      <img alt="" />
    </Frame>
* Langfuse Account:
  * Sign up for a Langfuse account on the official [Langfuse](https://langfuse.com)website to start building powerful AI applications.

# Integration Steps

## Step 1: Install Dependencies

Ensure you have installed all the required Python packages:

```shell theme={"system"}
pip install openai langfuse
```

## Step 2: Set Up Environment Variables

```python theme={"system"}
import os

# Get keys for your project from the project settings page
# https://cloud.langfuse.com

os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-..." # DOCS EXAMPLE KEYS
os.environ["LANGFUSE_SECRET_KEY"] = "sk-..." # DOCS EXAMPLE KEYS
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # 🇪🇺 EU region
# os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com" # 🇺🇸 US region


# Get your Novita AI API key from the project settings page
os.environ["NOVITA_API_KEY"] = "..."
```

## Step 3: Langfuse OpenAI drop-in Replacement

In this step we use the native [OpenAI drop-in replacement](https://langfuse.com/docs/integrations/openai/python/get-started) by importing `from langfuse.openai import openai`.

To start using Novita AI with OpenAI's client libraries, pass in your Novita AI API key to the `api_key` option, and change the `base_url` to [`https://api.novita.ai/openai:`](https://api.novita.ai/openai)

```python theme={"system"}
# instead of import openai:
from langfuse.openai import openai

client = openai.OpenAI(
  api_key=os.environ.get("NOVITA_API_KEY"),
  base_url="https://api.novita.ai/openai",
)
```

**Note:** The OpenAI drop-in replacement is fully compatible with the [Langfuse Python SDK](https://langfuse.com/docs/observability/sdk/overview) and [`@observe()` instrumentation](https://langfuse.com/docs/observability/sdk/instrumentation) to trace all parts of your application.

## Step 4: Run An Example

The following code cell shows how to use the traced OpenAI client to call Novita AI's chat model. All API calls will be seamlessly traced by Langfuse.

```python theme={"system"}
client = openai.OpenAI(
  api_key=os.environ.get("NOVITA_API_KEY"),
  base_url="https://api.novita.ai/openai",
)

response = client.chat.completions.create(
  model="meta-llama/llama-3.3-70b-instruct",
  messages=[
    {"role": "system", "content": "Act like you are a helpful assistant."},
    {"role": "user", "content": "What are the famous attractions in San Francisco?"},
  ]
)

print(response.choices[0].message.content)

San Francisco, one of the most iconic cities in the world, is home to a plethora of famous attractions that cater to all interests and ages. Here are some of the most popular attractions in San Francisco:

1. **Golden Gate Bridge**: An engineering marvel and a symbol of San Francisco, the Golden Gate Bridge is a must-visit attraction. Take a walk or bike ride across the bridge for spectacular views of the city and the Bay.
2. **Alcatraz Island**: Explore the infamous former prison turned national park, which once housed notorious inmates like Al Capone. Take a ferry to the island and enjoy a guided tour of the prison and its surroundings.
3. **Fisherman's Wharf**: A bustling waterfront district, Fisherman's Wharf is famous for its seafood restaurants, street performers, and stunning views of the Bay Bridge and Alcatraz Island. Don't miss the sea lions at Pier 39!
4. **Chinatown**: San Francisco's Chinatown is one of the largest and oldest in the United States. Explore the colorful streets, try some authentic Chinese cuisine, and shop for unique souvenirs.
5. **Golden Gate Park**: A sprawling urban park that's home to several attractions, including the de Young Museum, the California Academy of Sciences, and the Japanese Tea Garden.
6. **Cable Cars**: A classic San Francisco experience, the cable cars offer a fun and historic way to explore the city. Take a ride on the Powell-Mason line to Fisherman's Wharf, or the Powell-Hyde line to Lombard Street.
7. **Lombard Street**: Known as the "crookedest street in the world," Lombard Street is a scenic and winding road that offers stunning views of the city.
8. **Union Square**: A vibrant public square in the heart of the city, Union Square is surrounded by shopping, dining, and entertainment options. Catch a show at the historic Curran Theatre or take a stroll through the square.
9. **The Painted Ladies**: A row of colorful Victorian houses on Alamo Square, the Painted Ladies are a iconic symbol of San Francisco's architecture. Take a photo in front of these stunning homes.
10. **The Exploratorium**: A museum of science, art, and human perception, the Exploratorium is a great place to learn and have fun. With interactive exhibits and stunning views of the Bay, it's a must-visit for families and science enthusiasts.
11. **Pier 39**: A popular shopping and dining destination, Pier 39 offers stunning views of the Bay Bridge, Alcatraz Island, and the sea lions that call the pier home.
12. **The de Young Museum**: A fine arts museum located in Golden Gate Park, the de Young Museum features a diverse collection of art and cultural exhibitions from around the world.

These are just a few of the many famous attractions in San Francisco. Whether you're interested in history, culture, science, or entertainment, San Francisco has something for everyone.
```

## Step 5: See Traces in Langfuse

After running the example model call, you can view the traces in Langfuse. These traces provide detailed information about your Novita AI API calls, including:

* Request parameters (model, messages, temperature, etc.)
* Response content
* Token usage statistics
* Latency metrics

[Public example trace link in Langfuse](https://cloud.langfuse.com/project/cm7ua5l6e05wlad07qr6ce2wn/traces/039cc8b2-dba0-479f-9cd6-63672bc08c71?timestamp=2025-03-06T02%3A15%3A15.184Z).


# LiteLLM
Source: https://docs.novita.ai/guides/litellm

Supercharge Your AI Applications with Novita AI and LiteLLM.

LiteLLM is an open-source Python library and proxy server that provides access, spend tracking, and fallbacks to over 100 LLMs through a unified interface in the OpenAI format. By leveraging Novita AI's cutting-edge models, the integration with LiteLLM empowers your AI applications with seamless model switching, dependable fallbacks, and intelligent request routing—all through a standardized completion API that ensures compatibility across multiple providers.

This guide will show you how to quickly get started with integrating Novita AI and LiteLLM, enabling you to set up this powerful combination and streamline your workflow with ease.

## **How to Integrate Novita AI with LiteLLM**

### Step 1: Install LiteLLM

* Install the LiteLLM library using pip to create a unified interface for working with different language models.

```bash theme={"system"}
pip install litellm
```

### Step 2: Set Up Your API Credentials

* Log in to [the key management page](https://novita.ai/settings/key-management) in Novita AI and click `Add New Key`to generate your API key.

  <img alt="Visitthekeymanagementpageandselect Add New Keytogenerateyour AP Ikey Pn" />

### Step 3: Structure Your Basic API Call

* Create a completion request to Novita AI's models through LiteLLM's standardized interface.

```python theme={"system"}
from litellm import completion
import os

## set ENV variables. Visit https://novita.ai/settings/key-management to get your API key
os.environ["NOVITA_API_KEY"] = "novita-api-key"

response = completion(
  model="novita/deepseek/deepseek-v4.1-flash",
  messages=[{ "content": "Hello, how are you?","role": "user"}]
)
```

### Step 4: Implement Streaming for Better User Experience

* Enable streaming mode for more interactive applications or when handling longer responses.

```python theme={"system"}
from litellm import completion
import os

## set ENV variables. Visit https://novita.ai/settings/key-management to get your API key
os.environ["NOVITA_API_KEY"] = "novita_api_key"

response = completion(
  model="novita/deepseek/deepseek-v4.1-flash",
  messages = [{ "content": "Hello, how are you?","role": "user"}],
  stream=True,
)
```


# LlamaIndex
Source: https://docs.novita.ai/guides/llamaindex

Effortlessly integrate Novita AI with LlamaIndex to build intelligent, data-powered applications.

Designed for optimal indexing and retrieval, LlamaIndex excels in delivering high efficiency for applications requiring precise and fast data access. By combining [Novita AI](https://novita.ai/) with LlamaIndex, you will unlock key benefits such as superior data retrieval accuracy, unmatched scalability, and cost-effective performance.

This guide will walk you through how to use LlamaIndex with Novita AI based on the OpenAl APl, offering smarter, scalable, and highly efficient AI solutions that drive innovation and deliver exceptional results for developers.

## **How to Integrate Novita AI API with LlamaIndex**

Step 1: Visit [Model Library](https://novita.ai/llm-api) on Novita AI and select a model of interest.

<Frame>
  <img alt="images/Step1VisitModelLibraryonNovitaAIandselectamodelofinterest.png" />
</Frame>

Step 2: Navigate to the demo page of the chosen model and click the `Code` button on the right.

<Frame>
  <img alt="images/Step2NavigatetothedemopageofthechosenmodelandclicktheCodebuttonontheright.png" />
</Frame>

Step 3: Copy the model’s name and make a note of it.

<Frame>
  <img alt="Copy the model name and make a note of it" />
</Frame>

Step 4: [Log in ](https://novita.ai/user/login)to the Novita platform.

<Frame>
  <img alt="images/Step4LogintotheNovitaplatform.png" />
</Frame>

Step 5: After logging in, go to the platform’s [settings page](https://novita.ai/settings).

<Frame>
  <img alt="Open the platform settings page after logging in" />
</Frame>

Step 6: Create a new [API key](https://novita.ai/settings/key-management) and copy it for service authentication.

<Frame>
  <img alt="images/Step6CreateanewAPIkeyandcopyitforserviceauthentication.png" />
</Frame>

Step 7: Install `llama_index` and related Python libraries by running:

<Frame>
  <img alt="images/Step7Installllama_indexandrelatedPythonlibrariesbyrunning.png" />
</Frame>

Step 8: Write Python code and set the model name and API key as parameters in the NovitaAI class.

<Frame>
  <img alt="images/Step8WritePythoncodeandsetthemodelnameandAPIkeyasparametersintheNovitaAIclass.png" />
</Frame>

Step 9: Run the code to get the output.

<Frame>
  <img alt="images/Step9Runthecodetogettheoutput.png" />
</Frame>

For more examples, refer to the documentation: [llama\_index/llama-index-integrations/llms/llama-index-llms-novita at main · run-llama/llama\_index](https://github.com/run-llama/llama_index/tree/main/llama-index-integrations/llms/llama-index-llms-novita).


# Anthropic SDK Compatibility
Source: https://docs.novita.ai/guides/llm-anthropic-compatibility



Novita AI provides a compatibility API that allows you to use the Anthropic SDK with Novita AI models. This is useful if you are already using the Anthropic SDK and want to switch to Novita AI models.

## Supported Models

Currently, only the following models are compatible with the Anthropic SDK:

<AnthropicCompatibilityModels />

## Quick Start Guide

This guide demonstrates how to use the Anthropic SDK with the Novita AI models step by step.

### 1. Install the Anthropic SDK

<CodeGroup>
  ```bash Python icon="python" theme={"system"}
  pip install anthropic
  ```

  ```bash TypeScript icon="js" theme={"system"}
  npm install @anthropic-ai/sdk
  ```
</CodeGroup>

### 2. Initialize the Client

The Anthropic SDKs are designed to pull the API key and base URL from the environmental variables: `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL`. Also, you can supply the parameters to the Anthropic client when initializing it.

<Tip>
  You can view and manage your API keys on the [settings page](https://novita.ai/settings/key-management?utm_source=getstarted).
</Tip>

* Using Environment Variables

<CodeGroup>
  ```bash Bash icon="terminal" theme={"system"}
  export ANTHROPIC_BASE_URL="https://api.novita.ai/anthropic"
  export ANTHROPIC_API_KEY="<YOUR_NOVITA_API_KEY>"
  ```
</CodeGroup>

* Set the parameters while initializing the Anthropic client

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import anthropic

  client = anthropic.Anthropic(
      base_url="https://api.novita.ai/anthropic",
      api_key="<YOUR_NOVITA_API_KEY>",
      # Rewrite header
      default_headers={
          "Content-Type": "application/json",
          "Authorization": "Bearer <YOUR_NOVITA_API_KEY>",
      }
  )
  ```

  ```typescript TypeScript icon="js" theme={"system"}
  import Anthropic from "@anthropic-ai/sdk";

  const anthropic = new Anthropic({
      baseURL: "https://api.novita.ai/anthropic",
      apiKey: "<YOUR_NOVITA_API_KEY>",
      // Rewrite header
      defaultHeaders: {
        "Content-Type": "application/json",
        Authorization: `Bearer <YOUR_NOVITA_API_KEY>`,
      }
  });
  ```
</CodeGroup>

### 3. Call the API

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import anthropic

  # Initialize the client, if you already set `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY` 
  # in the environment variables, you can omit the `api_key` and `base_url` parameters.
  client = anthropic.Anthropic(
      base_url="https://api.novita.ai/anthropic",
      api_key="<YOUR_NOVITA_API_KEY>",
      # Rewrite header
      default_headers={
          "Content-Type": "application/json",
          "Authorization": "Bearer <YOUR_NOVITA_API_KEY>",
      }
  )

  message = client.messages.create(
      model="moonshotai/kimi-k2-instruct",
      max_tokens=1000,
      temperature=1,
      system=[
          {
              "type": "text",
              "text": "You are a world-class poet. Respond only with short poems."
          }
      ],
      messages=[
          {
              "role": "user",
              "content": [
                  {
                      "type": "text",
                      "text": "Why is the ocean salty?"
                  }
              ]
          }
      ]
  )

  print(message.content)
  ```

  ```typescript TypeScript icon="js" theme={"system"}
  import Anthropic from "@anthropic-ai/sdk";

  // Initialize the client, if you already set `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY` 
  // in the environment variables, you can omit the `baseURL` and `apiKey` parameters.
  const anthropic = new Anthropic({
      baseURL: "https://api.novita.ai/anthropic",
      apiKey: "<YOUR_NOVITA_API_KEY>",
      // Rewrite header
      defaultHeaders: {
        "Content-Type": "application/json",
        Authorization: `Bearer <YOUR_NOVITA_API_KEY>`,
      }
  });

  const msg = await anthropic.messages.create({
    model: "moonshotai/kimi-k2-instruct",
    max_tokens: 1000,
    temperature: 1,
    system="You are a world-class poet. Respond only with short poems.",
    messages: [
      {
        role: "user",
        content: [
          {
            type: "text",
            text: "Why is the ocean salty?"
          }
        ]
      }
    ]
  });

  console.log(msg);
  ```
</CodeGroup>


# Large Language Models
Source: https://docs.novita.ai/guides/llm-api



## Model Capabilities

Large Language Models (LLMs) are types of AI model built using deep learning and natural language processing techniques. After being trained on massive amounts of text data, they can understand, generate, and process human language. LLMs have the following main capabilities:

* **Text Generation**

  LLMs can generate logically coherent content based on context and adjust output style as needed.
* **Language Understanding**

  LLMs can accurately understand the meaning of input text and support context-aware conversation.
* **Text Translation**

  LLMs are capable of cross-lingual understanding and generation, enabling translation between different languages.
* **Knowledge-based Q\&A**

  With extensive knowledge basis, LLMs can answer questions across domains such as culture, science, and history.
* **Code Understanding & Generation**

  LLMs can understand and generate code (Python, Java, C++ and so on), detect code errors, and offer suggestions.
* **Text Classification & Summarization**

  LLMs can understand complex sentences, categorize and extract information, and summarize key points.

## Model Selection

On the [LLM Service Page](https://novita.ai/models), you can browse the list of supported models, learn model basics and pricing. Click on a specific model, and you can open its detail page for an online demo. After trying different models with your task, you can compare and choose the most suitable one.

## API Integration

Novita AI provides fast, reliable APIs for open-source models, combining the ease and reliability of top-tier LLM APIs with the flexibility and cost-efficiency of open source LLMs.

* [ChatCompletion](https://platform.openai.com/docs/guides/text-generation/chat-completions-api): Supports both streaming and non-streaming modes.
* [Completion](https://platform.openai.com/docs/guides/text-generation/completions-api): Also supports streaming and non-streaming modes.

If you're already using OpenAI's `ChatCompletion` or `Completion` API, simply set your base URL to: api.novita.ai/openai , get and set your API key, update the model name as needed to start using LLM services.

<Tip>
  For how to create, store, and use an API key, see [API Keys](/guides/llm-api-keys).
</Tip>

## Code Examples

### Python

<CodeGroup>
  ```python ChatCompletion theme={"system"}
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.novita.ai/openai",
      api_key="<Your API Key>",
  )

  model = "deepseek/deepseek-v4.1-flash"
  stream = True  # or False
  max_tokens = 512

  chat_completion_res = client.chat.completions.create(
      model=model,
      messages=[
          {
              "role": "system",
              "content": "you are a professional AI helper.",
          },
          {
              "role": "user",
              "content": "Where can the example of GPU provided by novita ai be adapted?",
          }
      ],
      stream=stream,
      max_tokens=max_tokens,
  )

  if stream:
      for chunk in chat_completion_res:
          print(chunk.choices[0].delta.content or "", end="")
  else:
      print(chat_completion_res.choices[0].message.content)
  ```

  ```python Completion theme={"system"}
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.novita.ai/openai",
      api_key="<Your API Key>",
  )

  model = "deepseek/deepseek-v4.1-flash"
  stream = True  # or False
  max_tokens = 512

  completion_res = client.completions.create(
      model=model,
      prompt="Where can the example of GPU provided by novita ai be adapted?",
      stream=stream,
      max_tokens=max_tokens,
  )

  if stream:
      for chunk in completion_res:
          print(chunk.choices[0].text or "", end="")
  else:
      print(completion_res.choices[0].text)
  ```
</CodeGroup>

### Curl

<CodeGroup>
  ```bash ChatCompletion theme={"system"}
  export API_KEY="<Your API Key>"

  curl "https://api.novita.ai/openai/v1/chat/completions" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${API_KEY}" \
    -d '{
      "model": "deepseek/deepseek-v4.1-flash",
      "messages": [
          {
              "role": "system",
              "content": "you are a professional AI helper."
          },
         {
              "role": "user",
              "content": "Where can the example of GPU provided by novita ai be adapted?"
          }
      ],
      "max_tokens": 512
  }'
  ```

  ```bash Completion theme={"system"}
  export API_KEY="<Your API Key>"

  curl "https://api.novita.ai/openai/v1/completions" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${API_KEY}" \
    -d '{
      "model": "deepseek/deepseek-v4.1-flash",
      "prompt": "Where can the example of GPU provided by novita ai be adapted?",
      "max_tokens": 512
  }'
  ```
</CodeGroup>

## Key Parameters

### Base Parameters

`model`: The target LLM. See the full list on the [LLM Service Page](https://novita.ai/models).

### Messages

> ChatCompletion Only

`messages`: Inputs and outputs when interacting with LLM. Each message belongs to one character. Messages can help you get better outputs, you can try different methods to get better results.

* `content`: message content.
* `role`: Define the speaker:
  * `system`: Set the AI's role, telling model its role or behavior.
  * `user`: The user's input.
  * `assistant`: The AI's response. Users can give examples of output in advance, telling model how to answer current request.
* `name` (optional): separating message writers with same role.

### Prompt

> ChatCompletion Only

`prompt`: Instruction text for generating a completion written by user to LLM to tell the model their task clearly.

### Output Control

Different combination of parameters can help the model to generate more satisfying content.

**Diversity of Text**

> 'temperature' and 'top\_p' can control the diversity of generated text, we advice you to set only one of them. The bigger the value you set, The more diverse the text will be.  The smaller the value you set, The more accurate the text will be.

* `temperature`: Controls randomness. Higher = more creative.
* `top_p`: Nucleus sampling. Controls cumulative probability.
* `top_k`: Limits candidate token count.

**Repetition Penalties**

* `presence_penalty`: Controls repeated tokens of the texts. If one token has already existed in the text, penalty will come, this results in more token in the text.
* `frequency_penalty`: Control token frequency of the texts. Every time the same token exist in the text, penalty will come, which results in less same token in the future in the text.
* `repetition_penalty`: Penalizes or encourages repetition.

### Output Limits

* `max_tokens`: Max tokens in one response. Content after this value in the text will be truncated.
* `stream`: Whether to use streaming. For those with long outputs, we advice you to use streaming mode to prevent timeout error:
  * `true`: Streamed, partial results as they are generated.
  * `false`: Full result after processing.
* `stop`: Strings that will terminate generation when encountered.


# API Keys
Source: https://docs.novita.ai/guides/llm-api-keys



An API key authenticates requests to Novita AI. This page explains how to create and store a key, use it across environments, and configure its access controls.

Use this page to:

* Authenticate requests to Novita AI with a Bearer API key.
* Create an API key in the console and store it safely.
* Set the key as an environment variable on Linux, macOS, or Windows.
* Check how long a key stays valid and which key operations the OpenAPI supports.
* Configure model and network access policies for a key.

## Authentication

Novita AI uses Bearer authentication for API access. Send your API key in the `Authorization` request header:

```
Authorization: Bearer <API Key>
```

An example request:

```bash theme={"system"}
curl "https://api.novita.ai/openai/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $NOVITA_API_KEY" \
  -d '{
    "model": "deepseek/deepseek-v4.1-flash",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

## Create an API key

<Steps>
  <Step title="Open Key Management">
    Go to [Key Management](https://novita.ai/settings/key-management?utm_source=getstarted) in the console.
  </Step>

  <Step title="Create a new key">
    Select **Create API Key**, then give the key a name that reflects its purpose, such as `production` or `local-testing`.
  </Step>

  <Step title="Copy and store the key">
    The full key is shown only once, when it is created. Copy it immediately and store it in a secure location, such as a secrets manager or an environment variable. If you lose it, you cannot recover it; create a new key instead.
  </Step>
</Steps>

## Configure access controls

Access controls are optional and configured per API key:

* [Model Access for API Keys](/guides/llm-model-access) controls which models a key can call within your team's enabled model range.
* [Network Access for API Keys](/guides/llm-network-access) controls which source IPs can use a key for model invocation.

The dedicated guides explain policy behavior, permissions, configuration, and rejection errors.

## Key format and validity

* Every key begins with the `sk_` prefix.
* A key is shown in full only once, when it is created. After that, the console shows a masked form.
* Each account can create up to **10** API keys.

When you create a key, choose one of four expiration options: **Permanent / 90 days / 30 days / 24 hours**.

<Note>
  You set the expiration when you create the key. It **cannot be changed** afterward: you cannot renew a key or expire it immediately. **Permanent** keys remain valid indefinitely, so a leaked key remains usable for longer. Use permanent keys only when appropriate. Expiration is configured in the console only; the OpenAPI does not create keys or set, change, or remove their expiration.
</Note>

An expired key is rejected when used, but its configuration remains visible and the delete action is still available. Expiration is different from disabling a key. To restore access, create a new key. To disable a key before it expires, delete it in the console; the deletion takes effect immediately.

## What the OpenAPI covers

You create and delete API keys in the console only. The Novita OpenAPI has no endpoints for creating or deleting keys, and it does not support configuring a key's expiration.

The key-related OpenAPI endpoints let you list keys and manage their model and network access policies:

* [List API Keys](/api-reference/key-list-with-model-access) — list the keys on your team, with an optional model access summary.
* [Get API Key Model Access Policy](/api-reference/key-get-model-access-policy) — read a single key's model access policy.
* [Set API Key Model Access Policy](/api-reference/key-put-model-access-policy) — set or update a key's model access policy.
* [Reset API Key Model Access Policy](/api-reference/key-delete-model-access-policy) — reset a key's model access policy to the default. This resets the policy only; it does not delete the key itself.
* [Get API Key IP Access Policy](/api-reference/key-get-ip-access-policy) — read a single key's network access policy.
* [Set API Key IP Access Policy](/api-reference/key-put-ip-access-policy) — set or update a key's allowed source IPs.
* [Delete API Key IP Access Policy](/api-reference/key-delete-ip-access-policy) — clear a key's network access policy, restoring no source-IP restriction.

## Store your key as an environment variable

Hardcoding a key in source code can expose it, for example when you commit the file. Reading it from an environment variable such as `NOVITA_API_KEY` keeps it out of your source code.

### Temporary vs. permanent

A key set with `export` (Linux/macOS) or `set` (Windows) lasts only for the current terminal session and is gone when you close it. This is suitable for a quick test. To keep the key across sessions, set it permanently as shown below, then open a new terminal for the change to take effect.

<CodeGroup>
  ```bash Linux theme={"system"}
  # Temporary: current session only
  export NOVITA_API_KEY="<Your API Key>"

  # Permanent: append to your shell profile, then reload
  echo 'export NOVITA_API_KEY="<Your API Key>"' >> ~/.bashrc
  source ~/.bashrc
  ```

  ```bash macOS theme={"system"}
  # Temporary: current session only
  export NOVITA_API_KEY="<Your API Key>"

  # Permanent: append to your shell profile, then reload
  # Newer macOS uses zsh (~/.zshrc); older setups use bash (~/.bash_profile)
  echo 'export NOVITA_API_KEY="<Your API Key>"' >> ~/.zshrc
  source ~/.zshrc
  ```

  ```powershell Windows theme={"system"}
  # Temporary: current PowerShell session only
  $env:NOVITA_API_KEY = "<Your API Key>"

  # Permanent: persist for the current user, then open a new terminal
  setx NOVITA_API_KEY "<Your API Key>"
  ```
</CodeGroup>

Read the key from the environment in your code:

<CodeGroup>
  ```python Python theme={"system"}
  import os
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.novita.ai/openai",
      api_key=os.environ.get("NOVITA_API_KEY"),
  )
  ```

  ```javascript Node.js theme={"system"}
  import OpenAI from "openai";

  const client = new OpenAI({
    baseURL: "https://api.novita.ai/openai",
    apiKey: process.env.NOVITA_API_KEY,
  });
  ```
</CodeGroup>

### The variable is set but the code still can't find it

<AccordionGroup>
  <Accordion title="You set it temporarily and opened a new terminal">
    A key set with `export` or `$env:` exists only in the terminal session where you ran the command. A new terminal or tab does not inherit it. Set the key permanently (`>> ~/.zshrc`, `setx`), or run the `export`/`$env:` command again in the session you are using.
  </Accordion>

  <Accordion title="You set it permanently but didn't restart">
    A permanent change (a shell profile or `setx`) applies to terminals started after the change. Open a new terminal, then restart your IDE or editor so it can read the updated environment. On Windows, `setx` does not affect terminals that are already open.
  </Accordion>

  <Accordion title="A service manager doesn't inherit your shell environment">
    Processes launched by `systemd`, `supervisor`, Docker, or a CI runner do not read your interactive shell profile. Set the variable in the service's own configuration, such as a `systemd` unit's `Environment=`, a `docker run -e` flag, or the CI project's secrets, rather than in `~/.bashrc`.
  </Accordion>

  <Accordion title="You ran the command under sudo">
    `sudo` does not pass your environment through by default, so a variable exported by your user is not visible to the elevated process. Use `sudo -E` to preserve the environment, or set the variable in the elevated context.
  </Accordion>
</AccordionGroup>

## Related

* [Common Error Codes](/guides/error) — resolve `401` and `403` responses related to keys.
* [Rate limits](/guides/llm-rate-limits) — request and token limits that apply to your account.


# Batch Inference
Source: https://docs.novita.ai/guides/llm-batch-api



The Batch API for Large Language Models enables asynchronous processing of numerous inference requests and is fully compatible with the OpenAI API standard.

The Batch API is a cost-effective solution when immediate inference results are unnecessary. It provides a higher rate limits than online calls, ensuring results are delivered within a reasonable timeframe of 24 hours.

This API is ideal for:

* Conducting evaluations and data analysis.
* Classifying extensive datasets.
* Generating document summaries in an offline mode.

Supported Models:

<BatchApiModels />

## Quick Start

### 1. Preparing Batch Files

The Batch API uses .jsonl format files as input, with each line representing the details of an API inference request. Available endpoints include `/v1/chat/completions` and `/v1/completions`.

<Warning>
  Set the `endpoint` parameter to `/v1/chat/completions` or `/v1/completions` for OpenAI API compatibility.
</Warning>

Each request must include a unique `custom_id` to locate inference results in the output file after batch completion. Parameters in the `body` field of each line are sent as actual inference request parameters to the endpoint.

<Warning>
  All requests within a single batch JSONL file must target the same model. Do not mix requests for different models in one batch.
</Warning>

Below is an example input file containing 2 requests:

```JSON theme={"system"}
{"custom_id": "request-1", "body": {"model": "deepseek/deepseek-r1-0528", "messages": [{"role": "user", "content": "Hello, world!"}], "max_tokens": 400}}
{"custom_id": "request-2", "body": {"model": "deepseek/deepseek-r1-0528", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}
```

### 2. Upload Batch Input File

Upload the batch input file to ensure it can be accurately referenced when creating a batch. Use the Files API to upload your .jsonl file and set the purpose to `batch`. Note that the file will be retained for 15 days.

<Tip>
  For how to get API key, refer to the [API Key Management](/api-reference/basic-authentication).
</Tip>

Code Example

**Python**

```python theme={"system"}
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/openai/v1",
    api_key="<Your API Key>",
)

batch_input_file = client.files.create(
    file=open("batch_input.jsonl", "rb"),
    purpose="batch",
)

print(batch_input_file)
```

**Curl**

```bash theme={"system"}
export API_KEY="<Your API Key>"

curl --request POST \
  --url https://api.novita.ai/openai/v1/files \
  --header 'Authorization: Bearer ${API_KEY}' \
  --form 'file=@"/your/batch_input.jsonl"' \
  --form 'purpose="batch"'
```

Sample response upon successful file upload:

```
{
    "id": "file_d2co***as73c0cjd0",
    "object": "file",
    "bytes": 238,
    "filename": "batch_input.jsonl",
    "created_at": 1754894162,
    "purpose": "batch",
    "metadata": {
        "total_requests": 2
    }
}
```

### 3. Creating a Batch

Once the input file is successfully uploaded, you can initiate a batch using the ID of the uploaded File object. The completion window is fixed at `24h` and is currently non-adjustable.

Code Example

**Python**

```python theme={"system"}
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/openai/v1",
    api_key="<Your API Key>",
)

batch = client.batches.create(
  input_file_id="file_d2cor0es1cas73c0cj60",
  endpoint="/v1/chat/completions",
  completion_window="24h"
)
print(batch)
```

**Curl**

```bash theme={"system"}
export API_KEY="<Your API Key>"

curl --request POST \
  --url https://api.novita.ai/openai/v1/batches \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer ${API_KEY}' \
  --data '{
      "input_file_id": "file_d2co***as73c0cjd0",
      "endpoint": "/v1/chat/completions",
      "completion_window": "24h"
  }'
```

This request will yield a Batch object that includes metadata about your batch, as illustrated in the example below:

```JSON theme={"system"}
{
    "id": "batch_d2cq***73a68lu0",
    "object": "batch",
    "endpoint": "/v1/chat/completions",
    "input_file_id": "file_d2co***as73c0cjd0",
    "output_file_id": "",
    "error_file_id": "",
    "completion_window": "24h",
    "in_progress_at": null,
    "expires_at": null,
    "finalizing_at": null,
    "completed_at": null,
    "failed_at": null,
    "expired_at": null,
    "cancelling_at": null,
    "cancelled_at": null,
    "status": "validating",
    "errors": "",
    "version": 0,
    "created_at": "2025-08-11T16:31:52.949816948+08:00",
    "updated_at": null,
    "created_by": "8f242aa1-f725-4a67-8***9-cb68025e0976",
    "created_by_key_id": "key_cc19f96c***e7390644a37da21",
    "remark": "",
    "total": 0,
    "completed": 0,
    "failed": 0,
    "metadata": null,
    "request_counts": {
        "total": 0,
        "completed": 0,
        "failed": 0
    }
}
```

### 4. Check the Status of a Batch

You can check a batch's status at any time to receive the latest batch information.

The status enumeration values of the Batch object are as follows:

<table>
  <thead>
    <tr>
      <th>Status</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>VALIDATING</td>
      <td>The input file is being validated before the batch can begin</td>
    </tr>

    <tr>
      <td>PROGRESS</td>
      <td>Batch is in progress</td>
    </tr>

    <tr>
      <td>COMPLETED</td>
      <td>Batch processing completed successfully</td>
    </tr>

    <tr>
      <td>FAILED</td>
      <td>Batch processing failed</td>
    </tr>

    <tr>
      <td>EXPIRED</td>
      <td>Batch exceeded deadline</td>
    </tr>

    <tr>
      <td>CANCELLING</td>
      <td>Batch is being cancelled</td>
    </tr>

    <tr>
      <td>CANCELLED</td>
      <td>Batch was cancelled</td>
    </tr>
  </tbody>
</table>

Code Example

**Python**

```python theme={"system"}
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/openai/v1",
    api_key="<Your API Key>",
)
batch = client.batches.retrieve("batch_d2cq***73a68lu0")
print(batch)
```

**Curl**

```bash theme={"system"}
export API_KEY="<Your API Key>"

curl --request GET \
  --url https://api.novita.ai/openai/v1/batches/{batch_id} \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer ${API_KEY}'
```

### 5. Retrieve the Results

Once the batch inference is complete, you can download the result output file using the `output_file_id` field from the Batch object.

The result output file will be deleted 30 days after the batch inference concludes, so please retrieve it promptly via the interface.

Code Example

**Python**

```python theme={"system"}
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/openai/v1",
    api_key="<Your API Key>",
)

content = client.files.content("example-250811-1")
print(content.read())
```

**Curl**

```bash theme={"system"}
export API_KEY="<Your API Key>"

curl --request GET \
  --url https://api.novita.ai/openai/v1/files/{file_id}/content \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer ${API_KEY}'
```

The response returns the raw file content. For batch output files, each line contains a response like this:

```json theme={"system"}
{
  "custom_id": "request-2589",
  "error": null,
  "id": "batch_req_task_d2c",
  "response": {
    "body": {
      "id": "29e1432c-edfb-44a4-b531-c23c600abfae",
      "object": "chat.completion",
      "created": 1754902266,
      "model": "deepseek-test",
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": "Hello! 👋 How can I assist you today? 😊"
          },
          "finish_reason": "stop"
        }
      ],
      "usage": {
        "prompt_tokens": 5,
        "completion_tokens": 13,
        "total_tokens": 18
      }
    },
    "request_id": "request-2589",
    "status_code": 200
  }
}
```

## Instructions

### Limitations

1. Each batch can contain up to 50,000 requests.<br />
2. The maximum input file size per batch is 100MB.

### Error Handling

Errors encountered during batch processing are recorded in a separate error file, accessible via the error\_file\_id field. Common error codes include:

<table>
  <thead>
    <tr>
      <th>Error Code</th>
      <th>Description</th>
      <th>Solution</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>400</td>
      <td>Invalid request format</td>
      <td>Check JSONL syntax and required fields</td>
    </tr>

    <tr>
      <td>401</td>
      <td>Authentication failed</td>
      <td>Verify API key</td>
    </tr>

    <tr>
      <td>404</td>
      <td>Batch not found</td>
      <td>Check batch ID</td>
    </tr>

    <tr>
      <td>429</td>
      <td>Rate limit exceeded</td>
      <td>Reduce request frequency</td>
    </tr>

    <tr>
      <td>500</td>
      <td>Server error</td>
      <td>Contact us</td>
    </tr>
  </tbody>
</table>

### Batch Expiration

Batches not completed within 24 hours will transition to an EXPIRED state. Unfinished requests will be canceled, while completed requests will be provided through an output file. You only pay for tokens consumed by completed requests. The batch makes every effort to complete within 24 hours.

## All Batch API

1. [Create batch](/api-reference/model-apis-llm-create-batch)
2. [Retrieve batch](/api-reference/model-apis-llm-retrieve-batch)
3. [Cancel batch](/api-reference/model-apis-llm-cancel-batch)
4. [List batch](/api-reference/model-apis-llm-list-batches)
5. [Upload file](/api-reference/model-apis-llm-upload-batch-input-file)
6. [List files](/api-reference/model-apis-llm-list-files)
7. [Retrieve file](/api-reference/model-apis-llm-query-file)
8. [Delete file](/api-reference/model-apis-llm-delete-file)
9. [Retrieve file content](/api-reference/model-apis-llm-retrieve-file-content)


# LLM API Billing
Source: https://docs.novita.ai/guides/llm-billing

Understand how novita AI LLM API billing works, including token-based pricing, HTTP status code billing rules, and the 499 client disconnection billing policy.

## Pricing

novita AI LLM API is billed per token. Each request incurs charges for:

* **Input Tokens** — tokens consumed by the prompt
* **Output Tokens** — tokens generated by the model

Total Cost = Input Tokens × Input Rate + Output Tokens × Output Rate

<Info>
  Rates vary by model. Refer to the [Pricing Page](https://novita.ai/pricing) for details.
</Info>

## HTTP Status Codes

| HTTP Status | Name | Description | Charged |
| :- | :- | :- | :- |
| 200 | Success | Request completed successfully | **Yes** |
| 400 | Bad Request | Invalid request parameters | No |
| 401 | Unauthorized | Invalid or missing API key | No |
| 403 | Forbidden | Insufficient permissions | No |
| 429 | Rate Limited | TPM or RPM limit exceeded | No |
| 499 | Client Disconnected | Client closed the connection before completion | **Yes** |
| 500 | Internal Server Error | Server-side error | No |
| 503 | Service Unavailable | Service temporarily unavailable | No |
| 504 | Gateway Timeout | Upstream response timed out | No |

**Billing principle:**

* Requests rejected before reaching the model (400/401/403/429): not charged
* Error due to platform issues (500/503/504): not charged
* Requests where the model has begun inference (200/499): charged

## 499 — Client Disconnected

Once a request reaches the model, inference begins immediately on the server side. If the client disconnects before receiving the full response, compute resources have already been consumed. The request is charged based on actual token usage.

| Request Mode | Billing |
| :- | :- |
| Non-Streaming | Charged for actual token usage regardless of disconnect timing |
| Streaming | Charged for actual token usage regardless of disconnect timing |

<Tip>
  **Recommendations**

  * Use `max_tokens` to limit output length at the request level
  * Set client timeout to ≥ 60s to avoid unintended disconnections
  * Prefer controlling generation via `max_tokens` rather than closing the connection
</Tip>

## FAQ

<AccordionGroup>
  <Accordion title="Why am I charged after disconnecting?">
    Inference begins as soon as the request reaches the model. Disconnecting does not halt computation already in progress.
  </Accordion>

  <Accordion title="How can I avoid 499 charges?">
    Set the `max_tokens` parameter to control output length before sending the request.
  </Accordion>

  <Accordion title="Where do 499 charges appear?">
    In your usage dashboard, alongside standard requests, with actual token consumption recorded.
  </Accordion>
</AccordionGroup>


# Novita Deployments User Guide
Source: https://docs.novita.ai/guides/llm-dedicated-endpoint



> **Navigation**: Models Console → Deployments
> **Applies to**: Current production version
> **Last updated**: 2026-04-20

***

## Table of Contents

1. [What are Deployments](#1-what-are-deployments)
2. [Quick Start (Go live in 5 minutes)](#2-quick-start-go-live-in-5-minutes)
3. [Creating a Deployment](#3-creating-a-deployment)
   * 3.1 [Naming](#31-naming)
   * 3.2 [Selecting a Model](#32-selecting-a-model)
   * 3.3 [Selecting a GPU Instance](#33-selecting-a-gpu-instance)
   * 3.4 [Configuring Autoscaling](#34-configuring-autoscaling)
   * 3.5 [Engine Settings (Advanced)](#35-engine-settings-advanced)
4. [Deployment Lifecycle & Status](#4-deployment-lifecycle--status)
5. [Autoscaling In Depth](#5-autoscaling-in-depth)
6. [LoRA Adapter Support](#6-lora-adapter-support)
7. [Billing](#7-billing)
8. [FAQ & Troubleshooting](#8-faq--troubleshooting)

***

## 1. What are Deployments

A Deployment is Novita's **dedicated AI inference endpoint** product. Unlike Serverless endpoints that share compute resources with other users, each Deployment gives you:

* **Exclusive GPU**: All compute resources are yours alone — no noisy neighbors
* **Predictable Performance SLA**: Dedicated compute means consistent, foreseeable inference latency
* **Flexible Model Sources**: Deploy any model from Hugging Face or the Novita model catalog
* **OpenAI-Compatible Chat API**: For pure text inference, simply swap `base_url` and `model` to migrate existing OpenAI integrations
* **Per-second Billing**: You are only charged while the endpoint is active. Billing pauses automatically when Scale-to-Zero kicks in

**When to use Deployments:**

| Use Case | Why it fits |
| - | - |
| Production API services | Stable latency, fully isolated from other users |
| Private or fine-tuned model serving | Deploy any custom HuggingFace model |
| High-concurrency inference | Scale to multiple replicas automatically |
| Cost-sensitive workloads | Scale-to-Zero stops billing during idle periods |

***

## 2. Quick Start (Go live in 5 minutes)

**Step 1 — Navigate to Deployments**

Log in to Novita → left sidebar → **Models Console** → **Models APIs** → **Deployments**

**Step 2 — Create a Deployment**

Click **+ New Deployment** and fill in:

* A Deployment name (e.g. `my-llama3-endpoint`)
* Model source (the Novita model catalog is recommended for the fastest setup)
* GPU instance (the system auto-recommends a suitable spec for your model)
* Autoscaling settings

**Step 3 — Wait for the Deployment to start**

Startup time varies with model size, typically **5–60 minutes**, progressing through three phases:

1. Requesting GPU
2. Downloading Model
3. Engine Initializing

Once the status shows **RUNNING**, the endpoint is ready to receive requests.

**Step 4 — Call the API**

Go to the Deployment detail page → **Quick Start** panel → copy the ready-to-run code snippet.

> Manage your API Keys under **Settings → API Keys**.

***

## 3. Creating a Deployment

Click **+ New Deployment** to open the creation form, which has four configuration sections.

### 3.1 Naming

Recommended naming format: `{model}-{environment}-{purpose}` — e.g. `llama3-prod-chatbot`.

### 3.2 Selecting a Model

Two model sources are supported:

#### Novita Model Catalog (Recommended)

Choose from Novita's hosted model list — no token required, **works out of the box**. Covers all major open-source models (Llama 3, Qwen, DeepSeek, Mistral, and more).

> Novita pre-validates model compatibility and applies engine optimizations, resulting in faster startup and higher stability.

#### Hugging Face Models

Enter a HuggingFace repository ID (e.g. `meta-llama/Meta-Llama-3-8B-Instruct`).

* **Public models**: No token needed, deploy directly
* **Private or Gated models**: A HuggingFace Access Token must be linked first

**How to link your HF Token:**

1. Go to [HuggingFace → Settings → Access Tokens](https://huggingface.co/settings/tokens) and create a token
2. In the Model field on the Create Deployment form, click **Integrate HF Token**
3. Paste and save the token

> If your token expires or is revoked, active Deployments that rely on it will fail to re-pull the model. Keep your token up to date.

#### LoRA Adapter (Optional)

After selecting a Base Model, you can attach one or more LoRA Adapters from HuggingFace. Multiple adapters can run on the same Deployment without requiring additional GPU resources.

See [Section 6 — LoRA Adapter Support](#6-lora-adapter-support) for details.

**Model file format requirements (for custom HuggingFace models):**

### 3.3 Selecting a GPU Instance

The system automatically recommends a GPU configuration based on your model size.

> **TIGHT MEMORY warning**: If the selected GPU has limited VRAM for the chosen model, the system shows a `TIGHT MEMORY` warning. Increase the GPU count or contact Novita support.

> GPU type **cannot be changed** after a Deployment is created. To switch GPU type, delete and recreate the Deployment.

***

### 3.4 Configuring Autoscaling

Autoscaling controls how many replicas run in response to traffic.

#### Enable Autoscaling (Recommended)

Use the dual-handle slider to set the replica range:

| Parameter | Description | Default |
| - | - | - |
| Min Replicas | Minimum active replicas at all times. Set to 0 to enable Scale-to-Zero | 1 |
| Max Replicas | Maximum replicas during peak traffic | 3 |
| Scale-down Delay | Seconds to wait after traffic drops before scaling down (prevents flapping) | 300s (minimum) |

**Scale-to-Zero (Min Replicas = 0):**

* After idling for longer than the Scale-down Delay, the Deployment enters **SLEEPING** status and billing pauses
* The first incoming request wakes it up automatically
* Cold start time: typically 5 minutes depending on model size
* ⚠️ Best suited for dev/test or low-frequency workloads. For production, keep Min Replicas ≥ 1

#### Disable Autoscaling

Runs a fixed number of replicas. Best for workloads with strict latency SLAs that cannot tolerate any scaling delay.

### 3.5 Engine Settings (Advanced)

Novita supports two inference engines — **vLLM** and **SGLang** — matched automatically to your model. These settings are hidden by default during Deployment creation.

#### Max Concurrency per Replica

Controls how many requests a single replica handles simultaneously.

| Setting | Effect |
| - | - |
| Below recommended | Lower latency, but limited throughput |
| Equal to recommended | Optimal balance of throughput and latency (recommended) |
| Above recommended | Higher throughput, but increased per-request latency |

> The system calculates a recommended value based on your GPU instance. Default is 16.

#### Suffix Decoding

N-gram based speculative decoding that pre-generates future tokens to speed up inference.

* Most effective for **highly predictable output formats** (e.g. code generation, structured JSON)
* Provides limited benefit for free-form conversation; excessively high values may actually increase latency

***

## 4. Deployment Lifecycle & Status

### State Transition Diagram

```text theme={"system"}
Create
  │
  ▼
PENDING ──── Waiting for GPU resource allocation
  │
  ▼
DEPLOYING ── Three sub-phases:
  │            ├─ Requesting GPU
  │            ├─ Downloading Model
  │            └─ Engine Initializing
  │
  ├──────────────── FAILED (deployment failed)
  │
  ▼
RUNNING ──── Live and accepting requests
  │
  ├─ Zero traffic + Scale-to-Zero enabled ──► SLEEPING
  │                                               │
  │                                 First request ──► DEPLOYING ──► RUNNING
  │
  ├─ Config update ──► ROLLING (zero-downtime rolling update)
  │
  ├─ Traffic change ──► SCALING (autoscaling in progress)
  │
  └─ Manual terminate ──► TERMINATING ──► TERMINATED (can be redeployed or deleted)
```

> **When billing starts**: Only running replicas are billed. Instances still deploying and replicas still scaling up do not count toward charges.

***

## 5. Autoscaling In Depth

### How It Works

Novita autoscaling monitors live traffic and dynamically adjusts replica count within the Min–Max range:

* **Scale-Up**: Request queue backlog detected → add replicas → more GPUs handle requests in parallel
* **Scale-Down**: Traffic drops → wait for Scale-down Delay to expire → reduce replicas
* **Scale-to-Zero**: When Min Replicas = 0 and the Deployment has been idle past the delay, it enters SLEEPING and billing stops

### Cost vs. Availability Trade-off

| Configuration | Cost | Availability | Best for |
| - | - | - | - |
| Min=0, Max=N | Lowest (no charge when idle) | Cold start delay (5 min) | Dev/test, low-frequency workloads |
| Min=1, Max=N | Medium | Always available, scales on demand | Most production workloads ✅ |
| Min=N, Max=N (fixed) | Highest | No scaling delay at all | Ultra-low latency SLA requirements |

### Per-Replica Cost

Each additional replica adds cost at the same GPU rate as the base replica.
Example: a 2× H100 Deployment that scales to 2 replicas doubles the GPU cost.

### Best Practices

* Set **Min Replicas = 1** in production to avoid cold starts impacting end users
* The default Scale-down Delay of 300s (5 minutes) works well for most cases; increase it if your traffic is highly variable
* Set Max Replicas to no more than 1.5× your expected (peak QPS / per-replica QPS) to avoid unexpected cost spikes

***

## 6. LoRA Adapter Support

### What is LoRA

LoRA (Low-Rank Adaptation) is a parameter-efficient fine-tuning technique that adds lightweight adapter layers on top of a Base Model to customize it for specific tasks — without retraining the full model.

### Using LoRA in Novita Deployments

**Adding adapters at creation time:**

In Create Deployment → Model field → after selecting a Base Model, click **+ Add Adapter** and enter the LoRA adapter's HuggingFace repository ID.

**Viewing adapters at runtime:**

In the Engine Configuration panel, a `+N LoRA` badge appears next to the Model ID. Hover over it to see the full list of attached adapters.

### Multi-LoRA: Multiple Adapters on One Deployment

A single Deployment can run multiple LoRA adapters simultaneously. Specify which adapter to use per request via the `model` field:

> Multi-LoRA requires no extra GPU resources. All adapters share a single copy of the Base Model weights in memory.

***

## 7. Billing

### Billing Unit

Charged by **GPU-second**: number of GPUs × seconds running × unit price.

### When Billing Starts and Stops

| Event | Details |
| - | - |
| **Billing starts** | After GPU allocation completes during DEPLOYING (i.e. when Downloading Model begins) |
| **Billing stops** | When the Deployment enters SLEEPING or TERMINATED status |
| **Continuous billing** | A RUNNING Deployment is billed even when it receives zero API requests |

### GPU Pricing

> For the latest pricing, refer to the [Novita pricing page](https://novita.ai/pricing).

### Billing Example

**Scenario**: A customer deploys model instance X on a single RTX 4090 (priced at \$0.61/GPU/hour), with autoscaling set to Min=0, Max=5.

Usage and charges for 9:00–10:00:

1. **9:00:00 – 9:15:40** — Instance is SLEEPING. Charge: **\$0.00**
2. **9:15:41 – 9:16:45** — 1 running replica serving traffic (65 seconds).
   Charge: ($0.61 ÷ 3600) × 1 replica × 65s = **$0.011\*\*
3. **9:16:46 – 10:00:00** — 2 running replicas serving traffic (1,994 seconds).
   Charge: ($0.61 ÷ 3600) × 2 replicas × 1,994s = **$0.676\*\*

**Total for 9:00–10:00: $0 + $0.011 + $0.676 = $0.687**

### Cost Control Tips

1. **Enable Scale-to-Zero** (Min Replicas = 0) for low-frequency workloads — zero cost when idle
2. **Audit your Deployment list regularly** and delete unused Deployments
3. **Cap Max Replicas conservatively** to prevent unexpected cost spikes from runaway autoscaling
4. **TERMINATED status costs nothing** — terminate and redeploy on demand

***

## 8. FAQ & Troubleshooting

### Deployment Issues

**Q: My Deployment has been stuck in DEPLOYING for a long time — what should I do?**

* `Requesting GPU`: GPU resources may be constrained. Wait 5–10 minutes, or try a different GPU type
* `Downloading Model`: Large models (70B+) can take 10+ minutes to download
* `Engine Initializing`: Should complete within 5 minutes under normal conditions

**Q: My Deployment shows FAILED — what are the common causes?**

* Model is not in `.safetensors` format (`.bin` is not supported)
* HuggingFace Token is invalid or lacks access to a gated model
* Insufficient GPU VRAM for the model (TIGHT MEMORY configuration)
* Model architecture is not yet supported

Debugging steps: check the change log in the Settings Tab → verify model file format → validate the HF Token → increase GPU count and recreate the Deployment.

**Q: My Deployment is SLEEPING — how do I wake it up?**

Send any API request to it. The Deployment wakes up automatically. The first request waits for the cold start to complete before receiving a response.

***

### API Issues

**Q: What do the common HTTP error codes mean?**

| Code | Cause | Resolution |
| - | - | - |
| `400` | Malformed request | Validate your request JSON; ensure all required fields are present |
| `401` | Missing or invalid API Key | Include a valid key in `Authorization: Bearer <Key>` |
| `403` | API Key lacks access to this endpoint | Confirm the key belongs to the same account that owns the Deployment |
| `404` | Wrong Endpoint URL or Model ID | Re-copy the URL and Model ID from the Quick Start panel |
| `422` | Invalid parameter value (e.g. max\_tokens too large) | Adjust the parameter — try reducing max\_tokens |
| `429` | Rate limit exceeded | Reduce request frequency, or contact Novita to raise your limit |
| `500` | Internal server error | Retry after a short wait; if it persists, contact Novita support |

**Q: Where do I find my API Key?**

Go to **Settings → API Keys** to create or manage keys. A key is only shown once at creation — save it immediately.

***

### Billing Issues

**Q: Why am I being charged when there are no requests?**

A RUNNING Deployment continuously occupies GPU resources regardless of request volume.
**Fix**: Enable Autoscaling and set Min Replicas = 0. The Deployment will automatically sleep and stop billing when idle.

**Q: How do I stop all charges completely?**

Two options:

* **Scale-to-Zero**: Let autoscaling trigger naturally (requires Autoscaling on with Min = 0)
* **Terminate**: Click **Terminate** on the Deployment detail page to release the GPU immediately

***

## Appendix: Glossary

| Term | Definition |
| - | - |
| Deployment | Novita's dedicated inference endpoint product |
| Replica | A single running instance of the inference service; multiple replicas run in parallel |
| Scale-to-Zero | Setting Min Replicas to 0 so the endpoint sleeps when idle and billing stops |
| Scale-down Delay | Wait period before scaling down, preventing flapping on variable traffic |
| LoRA Adapter | Lightweight fine-tuning plugin layered on top of a Base Model |
| Endpoint URL | The API access address for this Deployment |
| Endpoint ID | Unique identifier for this Deployment |
| Base Model | The underlying foundation model being served |
| Max Concurrency | Maximum simultaneous requests a single replica handles |
| Suffix Decoding | N-gram speculative decoding to accelerate inference on predictable outputs |
| GPU-second | Billing unit: 1 GPU running for 1 second |

***

*For support, contact the Novita team at: [support@novita.ai](mailto:support@novita.ai)*


# DeepSeek-OCR Usage
Source: https://docs.novita.ai/guides/llm-deepseek-ocr



## Overview

DeepSeek-OCR is designed for document recognition and image-to-text scenarios, pushing the limits of visual and textual compression. The model can render long texts into highly compressed images, achieving an OCR accuracy of 97% at a lossless compression ratio of 10x and around 60% accuracy at 20x compression.

<Note>
  This model currently supports only single-turn, independent recognition tasks. It does not support multi-turn conversations. Only one image can be uploaded per request, and it is strongly recommended to use preset prompts for optimal performance.
</Note>

## Recommended Preset Prompts

```bash theme={"system"}
# Convert the document contents to markdown format
<|grounding|>Convert the document to markdown.

# Perform text recognition on this image
<|grounding|>OCR this image.

# Extract all text without layout consideration
Free OCR.

# Parse any figures or tables in the document
Parse the figure.

# Provide a detailed description of the image content
Describe this image in detail.

# Locate the position of <|ref|>xxxx<|/ref|> in the image
Locate <|ref|>xxxx<|/ref|> in the image.
```

## Usage Example

This example uses the `<|grounding|>OCR this image.` preset prompt to perform image text recognition.

```python theme={"system"}
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/openai",
    api_key="<Your API Key>",
)

response = client.chat.completions.create(
    model="deepseek/deepseek-ocr-2",
    messages=[
      {
        "role": "user",
        "content": [
          {
            "type": "image_url",
            "image_url": {
              "url": "https://example.com/image.png"
            }
          },
          {
            "type": "text",
            "text": "<|grounding|>OCR this image."
          }
        ]
      }
    ],
    stream=False,
    max_tokens=4096
)

content = response.choices[0].message.content

print(content)
```

Example input image:

<Frame>
  <img alt="OCR Example Image" />
</Frame>

Example output:

```
<|/ref|><|det|>[[37, 48, 279, 140]]<|/det|>
<|ref|>Deploy open-source and specialized models<|/ref|><|det|>[[42, 48, 857, 133]]<|/det|>
<|ref|>smarterandfasterwithsimpleApls.Accessthe<|/ref|><|det|>[[44, 185, 902, 246]]<|/det|>
<|ref|>latest chat, code, image, audio, video models and<|/ref|><|det|>[[41, 291, 945, 370]]<|/det|>
<|ref|>more,ready for production with built-in<|/ref|><|det|>[[40, 407, 756, 488]]<|/det|>
<|ref|>scalability.<|/ref|><|det|>[[39, 515, 232, 606]]<|/det|>
<|ref|>Explore<|/ref|><|det|>[[87, 813, 266, 879]]<|/det|>
<|ref|>Models<|/ref|><|det|>[[289, 816, 432, 878]]<|/det|>
```


# Function Calling
Source: https://docs.novita.ai/guides/llm-function-calling



`Function Calling` empowers AI models to interact with external tools and APIs, enabling them to perform specific actions and access real-time information. This capability extends the functionality of AI models beyond simple text generation, allowing for more dynamic and practical applications.

## Supported Models

The following models support `Function Calling`:

<FunctionCallingModels />

## Quick Start Guide

This guide demonstrates how to use Function Calling to retrieve current weather information for a user's specified location.  We will walk through a complete Python code example.

For the specific API format of Function Calling, please refer to the API reference [Create Chat Completion](/api-reference/model-apis-llm-create-chat-completion).

### 1. Initialize the Client

First, you need to initialize the client with your Novita API key.

```python theme={"system"}
from openai import OpenAI
import json

client = OpenAI(
    base_url="https://api.novita.ai/openai",
    # Get the Novita AI API Key from: https://novita.ai/settings/key-management.
    api_key="<YOUR Novita AI API Key>",
)

model = "deepseek/deepseek_v3"
```

### 2. Define the Function to Be Called

Next, define the Python function that the model can call. In this example, it's a function to get weather information.

```python theme={"system"}
# Example function to simulate fetching weather data.
def get_weather(location):
    """Retrieves the current weather for a given location."""
    print("Calling get_weather function with location: ", location)
    # In a real application, you would call an external weather API here.
    # This is a simplified example returning hardcoded data.
    return json.dumps({"location": location, "temperature": "60 degrees Fahrenheit"})
```

### 3. Construct the API Request with Tools and User Message

Now, create the API request to the Novita endpoint. This request includes the `tools` parameter, defining the functions the model can use, and the user's message.

```python theme={"system"}
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather of an location, the user shoud supply a location first",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"]
            },
        }
    },
]

messages = [
    {
        "role": "user",
        "content": "What is the weather in San Francisco?"
    }
]

# Let's send the request and print the response.
response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=tools,
)

# Please check if the response contains tool calls if in production.
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.model_dump())
```

**Output:**

```js theme={"system"}
{'id': '0', 'function': {'arguments': '{"location": "San Francisco, CA"}', 'name': 'get_weather'}, 'type': 'function'}
```

### 4. Respond with the Function Call Result and Get the Final Answer

The next step is to process the function call, execute the `get_weather` function, and send the result back to the model to generate the final response to the user.

```python theme={"system"}
# Ensure tool_call is defined from the previous step
if tool_call:
    # Extend conversation history with the assistant's tool call message
    messages.append(response.choices[0].message)

    function_name = tool_call.function.name
    if function_name == "get_weather":
        function_args = json.loads(tool_call.function.arguments)
        # Execute the function and get the response
        function_response = get_weather(
            location=function_args.get("location"))
        # Append the function response to the messages
        messages.append(
            {
                "tool_call_id": tool_call.id,
                "role": "tool",
                "content": function_response,
            }
        )

    # Get the final response from the model, now with the function result
    answer_response = client.chat.completions.create(
        model=model,
        messages=messages,
        # Note: Do not include tools parameter here.
    )
    print(answer_response.choices[0].message)
```

**Output:**

```
ChatCompletionMessage(content="The weather in San Francisco, CA is currently **60 degrees Fahrenheit**. For more detailed information, such as specific conditions (e.g., sunny, cloudy, rainy), you might want to check a local weather app or website. Let me know if you'd like help with anything else!", refusal=None, role='assistant', function_call=None, tool_calls=None)
```

### The Complete Code

```python theme={"system"}
from openai import OpenAI
import json

client = OpenAI(
    base_url="https://api.novita.ai/openai",
    # Get the Novita AI API Key from: https://novita.ai/settings/key-management.
    api_key="<YOUR Novita AI API Key>",
)

model = "deepseek/deepseek_v3"

# Example function to simulate fetching weather data.
def get_weather(location):
    """Retrieves the current weather for a given location."""
    print("Calling get_weather function with location: ", location)
    # In a real application, you would call an external weather API here.
    # This is a simplified example returning hardcoded data.
    return json.dumps({"location": location, "temperature": "60 degrees Fahrenheit"})

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather of an location, the user shoud supply a location first",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"]
            },
        }
    },
]

messages = [
    {
        "role": "user",
        "content": "What is the weather in San Francisco?"
    }
]

# Let's send the request and print the response.
response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=tools,
)

# Please check if the response contains tool calls if in production.
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.model_dump())

# Ensure tool_call is defined from the previous step
if tool_call:
    # Extend conversation history with the assistant's tool call message
    messages.append(response.choices[0].message)

    function_name = tool_call.function.name
    if function_name == "get_weather":
        function_args = json.loads(tool_call.function.arguments)
        # Execute the function and get the response
        function_response = get_weather(
            location=function_args.get("location"))
        # Append the function response to the messages
        messages.append(
            {
                "tool_call_id": tool_call.id,
                "role": "tool",
                "content": function_response,
            }
        )

    # Get the final response from the model, now with the function result
    answer_response = client.chat.completions.create(
        model=model,
        messages=messages,
        # Note: Do not include tools parameter here
    )
    print(answer_response.choices[0].message)
```


# Interleaved Thinking Support
Source: https://docs.novita.ai/guides/llm-interleaved-thinking



> **Last Updated**: 2025-12-03 <br />
> **Status**: Supported (OpenAI-Compatible)

## 1. Overview

**Interleaved Thinking** is an advanced reasoning framework that enables models to perform explicit reasoning steps between tool calls.

Models with Interleaved Thinking can:

* Reflect on the current environment and tool outputs
* Decide the next action based on updated reasoning
* Maintain a continuous reasoning chain across multiple tool invocations
* Provide transparent, inspectable multi-step thinking through `reasoning_details` or `reasoning_content`

This capability transforms traditional function-calling into **agent-level tool use**, making complex workflows more accurate, reliable, and context-aware.

Novita fully supports Interleaved Thinking for all models that natively expose reasoning streams (e.g., MiniMax-M3 and other OpenAI-compatible reasoning models).

## 2. Key Concepts

### 2.1 Interleaving

Instead of executing a single reasoning phase followed by a tool call, the model performs:

```BASH theme={"system"}
Reason → Tool Call → Observe → Reason → Tool Call → ...
```

This allows the model to adjust its strategy dynamically based on previous tool outputs.

### 2.2 Reasoning Details (`reasoning_details`)

For some models, the content of the model's thinking will be returned in the form of a separate structure:

```JSON theme={"system"}
"reasoning_details": [
  {
    "type": "reasoning.text",
    "format": "openai-responses-v1",
    "text": "Model’s step-by-step reasoning..."
  }
]
```

For such models, Novita supports returning this field in both streaming and non-streaming modes.

### 2.3 Conversation Memory Requirement

To maintain reasoning continuity: You **must append the model’s full response** including `reasoning_details`, `tool_calls`, and `content` to subsequent `messages`.

Failing to preserve the chain may result in:

* Incorrect tool use
* Lost reasoning context
* Repeated or circular tool calls
* Reduced reliability

This requirement mirrors OpenAI’s reasoning APIs.

## 3. API Behavior

### 3.1 Request Format

No changes are required on the user side.
Interleaved Thinking works with the standard OpenAI-compatible Chat Completions API.

### 3.2 Response Format

The model may return the following fields:

* `reasoning_content`: original thinking content
* `reasoning_details`: structured reasoning segments, this field is optioanl
* `tool_calls`: tool invocation plan
* `content`: natural language output

These extend the standard OpenAI format.

## 4. Example Request (MiniMax-M3)

```JSON theme={"system"}
{
  "model": "minimax/minimax-m3",
  "messages": [
    {
      "role": "user",
      "content": "How's the weather in San Francisco?"
    },
    {
      "role": "assistant",
      "name": "MiniMax AI",
      "content": "",
      "tool_calls": [
        {
          "id": "call_function_asqvfevfc8af_1",
          "type": "function",
          "function": {
            "name": "get_weather",
            "arguments": "{\"location\": \"San Francisco, US\"}"
          }
        }
      ],
      "reasoning_details": [
        {
          "type": "reasoning.text",
          "id": "reasoning-text-1",
          "format": "openai-responses-v1",
          "index": 0,
          "text": "The user is asking about the weather in San Francisco..."
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call_function_asqvfevfc8af_1",
      "content": "24℃, sunny"
    }
  ],
  "stream": true,
  "reasoning_split": true,
  "max_tokens": 1024,
  "temperature": 0.7,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get weather for a specific location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": { "type": "string" }
          },
          "required": ["location"]
        }
      }
    }
  ]
}
```

## 5. Example Response (Non-Streaming)

```JSON theme={"system"}
{
  "id": "07a4dedfdb1498b045498dfd42497639",
  "object": "chat.completion",
  "created": 1764303147,
  "model": "MiniMax",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "",
        "name": "MiniMax",
        "tool_calls": [
          {
            "index": 0,
            "id": "call_function_9w7wq1j9zmpl_1",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\"location\": \"ShangHai\"}"
            }
          }
        ],
        "reasoning_content": "The user asked for Shanghai weather...",
        "reasoning_details": [
          {
            "type": "reasoning.text",
            "text": "The user is asking about the weather in Shanghai...",
            "id": "reasoning-text-1",
            "format": "openai-responses-v1",
            "index": 0
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}
```

## 6. Streaming Response Example

```JSON theme={"system"}
{
  "id": "664c5ad870c1888fbcbd267d9829e354",
  "object": "chat.completion.chunk",
  "created": 1764303504,
  "model": "minimax/minimax-m3",
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "reasoning_content": "...\n\nThe user has specifically asked...",
        "reasoning_details": [
          {
            "type": "reasoning.text",
            "text": ".\n\nThe user has specifically asked...",
            "id": "reasoning-text-1",
            "format": "openai-responses-v1",
            "index": 0
          }
        ]
      },
      "finish_reason": null
    }
  ]
}
```

## 7. Developer Notes

### 7.1 Models That Support Interleaved Thinking

All models exposing `reasoning_details` through OpenAI-compatible APIs, including:

* MiniMax-M3
* (Upcoming) Novita Reasoning Series
* Other reasoning-enabled partner models

### 7.2 Pricing

Billing is based on reasoning tokens, following the model’s pricing rules.
`reasoning_details` will increase token usage.

### 7.3 Error Handling

You may encounter:

* Missing tool parameters
* Recursive or repeated tool calls
* Incorrect assumptions in the reasoning phase

Ensure your application validates tool arguments and handles model errors gracefully.

## 8. Best Practices

**✓ Always include full model messages in the next request**

Include:

* `content`
* `tool_calls`
* `reasoning_details`

**✓ Enable streaming for long-chain reasoning**

Streaming allows the client to:

* Monitor the reasoning process
* Detect incorrect tool plans early
* Provide faster user feedback

**✓ Combine with server-side state machines for stability**

For production systems, we recommend pairing Interleaved Thinking with deterministic guardrails, e.g.:

* Parameter validators
* Execution sandbox
* Maximum recursion safeguards

## 9. Summary

Interleaved Thinking significantly enhances multi-step reasoning and tool-use reliability:

* Transparent and inspectable step-wise reasoning
* Adaptive planning between tool invocations
* Stronger context retention across long workflows
* Fully compatible with OpenAI-style Chat Completions API

Novita will continue expanding support for advanced reasoning models, bringing agent-level intelligence to the API layer.


# Model Access for API Keys
Source: https://docs.novita.ai/guides/llm-model-access



Model access controls which models an individual API key can call. It does not change your team's enabled model range. Instead, it limits the models that a key can call within that range. Calls to models outside the key's model access policy are rejected.

<Info>
  This capability covers models in the LLM product line. Standalone image and video multimodal capabilities are not currently included in model access configuration.
</Info>

Examples:

* **Separate keys by purpose**: let production keys call only stable models and experimental keys call only low-cost models.
* **Limit unexpected costs**: if a key makes unexpected calls to high-cost models, restrict it to a smaller set of models.
* **Limit shared keys**: for a key shared with a contractor or partner, allow only the agreed models.

## Access modes

Each API key has one of two access modes:

| Access mode | Meaning |
| :- | :- |
| All enabled models | Default mode. The key can access all models your team has enabled, including models enabled in the future. You can exclude specific models. Future enabled models remain accessible unless you exclude them. |
| Selected models only | The key can access only the models you select. Models your team enables later are not added automatically; you must add them yourself. |

In both modes, the accessible models never exceed your team's enabled model range and never include models that the platform has taken offline.

* All enabled models: accessible models = team's enabled models (including future ones) − excluded models.
* Selected models only: accessible models = selected models (intersected with the team's enabled models).

## Configure model access

<Note>
  Only team admins can configure model access for an API key. Developers and Basic members see a read-only summary for keys they can access but cannot edit it. Billing members do not see model access.
</Note>

<Steps>
  <Step title="Open Key Management">
    Go to [Key Management](https://novita.ai/settings/key-management) and find the API key you want to configure.
  </Step>

  <Step title="Open model access settings">
    While creating or editing the key, open the **Model Access** section.
  </Step>

  <Step title="Choose an access mode">
    Select **All enabled models** or **Selected models only**:

    * For **All enabled models**, add any models you want to block under **Excluded models**.
    * For **Selected models only**, add at least one model under **Accessible models**.
  </Step>

  <Step title="Save">
    After you save, the new model access settings may take some time to take effect.
  </Step>
</Steps>

<Tip>
  When creating or editing a key, you can reuse another key's settings within the same team with **Copy config** and **Paste config**. After pasting, save the settings or adjust them first.
</Tip>

## When a model is taken offline

<Warning>
  A model being taken offline and an API key access restriction are different situations. Once a model is offline, no API key can call it. This is not a model access configuration problem with a particular key.
</Warning>

If the platform takes a model offline, it is removed from the set of available models:

* Calls to that model immediately return a "model unavailable" response, even if the key previously had access to it.
* If a key's saved configuration still contains the offline model, the model remains visible but is greyed out. Remove it before saving; otherwise, the configuration cannot be saved.

## When a call is rejected

<Warning>
  An offline model returns a "model unavailable" response. This is not a key permission denial and does not return `model_access_denied`.
</Warning>

When an API key calls a model outside its model access policy, the call returns HTTP 403 with the error code `model_access_denied`. Contact your team admin to adjust the policy.

See also [Common Error Codes](/guides/error).

## Related

* [API Keys](/guides/llm-api-keys) — create, store, and manage API keys, including expiration and OpenAPI boundaries.
* [Network Access for API Keys](/guides/llm-network-access) — restrict which source IPs can use a key.
* [Get API Key Model Access Policy](/api-reference/key-get-model-access-policy) — read a key's model access policy programmatically.


# LLM Monitoring
Source: https://docs.novita.ai/guides/llm-monitor

Description of your new file.

Novita AI provides comprehensive monitoring metrics for the usage of large language model (LLM) APIs. These metrics help you gain deep insights into the availability and performance of your LLM API requests.

You can view the monitoring metrics on the [LLM Monitoring Page](https://novita.ai/models-console/llm-metrics).

## Metric Descriptions

<Note>
  All metrics are broken down by **model** and sampled at the **minute level**. However, depending on your selected time interval, samples may not be shown for every minute. In such cases, values will be averaged across the selected time range.
</Note>

* **Requests Per Minute (RPM)**

Displays the number of API requests sent per minute. This helps you understand usage patterns and the level of API concurrency.

* **Request Success Rate**

Shows the percentage of successful API responses per minute (non-5xx status codes), reflecting the API's availability.

* **Average Token Count per Request**

Displays the average number of input and output tokens per request per minute, which helps you monitor token consumption patterns.

* **End-to-End (E2E) Latency**

Shows the total time required for the model to generate a full response in each request per minute. Metrics include the 99th percentile, 95th percentile, and average latency.

* **Time to First Token (TTFT)**

<Note>
  Only tracked for streaming requests where stream=true.
</Note>

Displays the time it takes to process the prompt and generate the first output token in each request per minute. Includes the 99th percentile, 95th percentile, and average latency.

* **Time Per Output Token (TPOT)**

<Note>
  Only tracked for streaming requests where stream=true.
</Note>

Displays the average time between consecutive output tokens in each request per minute. Includes the 99th percentile, 95th percentile, and average latency.


# Network Access for API Keys
Source: https://docs.novita.ai/guides/llm-network-access



A network access policy restricts which source IPs can use an individual API key. Once configured, only IPs in the allowed list can use the key to invoke models; requests from other sources are rejected.

A network access policy is configured per key, and policies are independent. An empty allowed list does not restrict the source IP.

<Info>
  Network access applies only to model invocation. Management endpoints, including billing queries and key policy read/write operations, are not restricted by source IP. The policy limits model calls from sources outside the allowed list; it does not prevent an insider from routing traffic through an allowed source.
</Info>

## Allowed IP rules

* Supports IPv4 addresses (for example, `203.0.113.5`) and IPv4 CIDR ranges (for example, `198.51.100.0/24`).
* **IPv6 is not supported yet.** If your egress is IPv6, or a dual-stack setup routes through IPv6, the call is rejected.
* Up to 100 entries per key.
* An empty allowed list means the source IP is not restricted; this is the default.

<Warning>
  Confirm your own egress IP before configuring the policy. When the list is non-empty, every source outside it, including your testing egress, is rejected. With dynamic public IPs, multi-datacenter egress, NAT gateways, or serverless functions, the egress IP can change or include multiple addresses. Use a CIDR range to cover the whole block, or confirm and add all egress IPs.
</Warning>

## Configure network access

<Note>
  Team admins can configure network access for any key in the team; developers can configure keys they own. Basic members can view the configuration of their own keys but cannot edit it. Billing members have read-only access.
</Note>

<Steps>
  <Step title="Open Key Management">
    Go to [Key Management](https://novita.ai/settings/key-management) and find the API key you want to configure.
  </Step>

  <Step title="Open network access settings">
    While creating or editing the key, open the **Network Access** section.
  </Step>

  <Step title="Add allowed IPs">
    Add allowed IPv4 addresses or CIDR ranges one by one. Leave the list empty to keep the source IP unrestricted.
  </Step>

  <Step title="Save">
    After you save, the new network access settings apply to subsequent calls.
  </Step>
</Steps>

## When a call is rejected

When an API key invokes a model from a source IP outside the allowed range, the call returns HTTP 403 with the error code `ip_access_denied`. Confirm the request's public egress IP and add it to the key's allowed list, or contact your team admin to adjust the network access policy. See also [Common Error Codes](/guides/error).

## Related

* [API Keys](/guides/llm-api-keys) — create, store, and manage API keys, including expiration and OpenAPI boundaries.
* [Model Access for API Keys](/guides/llm-model-access) — restrict which models a key can call.
* [Get API Key IP Access Policy](/api-reference/key-get-ip-access-policy) — read a key's network access policy programmatically.


# Interactive Playground
Source: https://docs.novita.ai/guides/llm-playgrounds



To help users better understand and test the behavior of large language models, **Novita** provides a flexible interactive playground. Users can fine-tune a series of parameters to precisely control the model's generation logic, content style, and response structure, enabling more targeted output.

## Response Format

Users can choose between two modes:

* **Custom JSON Output Structure**: Define a JSON Schema to guide the model in generating output that follows a predefined format, ideal for structured tasks or API integration scenarios.
* **Free-form Generation**: Allow the model to freely organize answers based on context, suitable for open-ended Q\&A and creative writing applications.

## System Prompt

By setting a system prompt, users can define the model's "persona" and behavioral context. For example, if you set the prompt as “You are a professional travel planner,” the model will respond from that perspective, enhancing consistency and contextual coherence.

## Parameter Configuration

The playground supports the adjustment of the following core parameters:

* **max\_tokens**: Sets the maximum number of tokens the model can generate. Lower values improve response speed, while higher values are suitable for complex, lengthy outputs.
* **temperature**: Controls the creativity and randomness of the output. Higher values lead to more diverse and imaginative responses, while lower values yield more conservative and deterministic outputs, ideal for precise tasks.
* **top\_p (nucleus sampling)**: Dynamically adjusts the token sampling range per step to improve diversity and fluency. Recommended to use alongside temperature.
* **top\_k**: Limits the model to select from the top K highest probability tokens at each step, which enhances efficiency and reduces noise.
* **min\_p**: An alternative to top\_p and top\_k, used to filter out tokens with probabilities below a certain threshold, suitable for accuracy-critical tasks.
* **presence\_penalty**: Encourages the model to introduce new topics in its output. Positive values increase the likelihood of expanding to new content.
* **frequency\_penalty**: Suppresses the generation of repeated tokens. Positive values reduce repetition and help maintain originality.
* **repetition\_penalty**: Further reduces the risk of loops or repetitive prompts, enhancing output robustness.

## Usage Recommendations

* **For Creative Writing**: Increase temperature, top\_p, and presence\_penalty to encourage diverse and imaginative outputs.
* **For Structured Tasks and Tool Invocation**: Use a fixed JSON schema with lower temperature and top\_k to ensure consistent and reliable outputs.
* **For Testing Interactions**: Compare different parameter combinations to observe changes in generation style, information density, and contextual understanding, aiding in faster prompt iteration.

***

To replicate the behavior from the playground in API calls, refer to the corresponding parameter documentation in the platform guide.


# Prompt Cache
Source: https://docs.novita.ai/guides/llm-prompt-cache



Prompt Cache is a cost-optimization feature provided by the Novita inference engine.

When a request matches a previous Prompt, the system returns a cached result and charges only a minimal cache token fee — significantly reducing cost and improving response latency.

## 1. Benefits

With Prompt Cache enabled, you gain:

* **Lower Cost**

  Repeated Prompts no longer require full inference. Only minimal cache token fees are charged.

* **Lower Latency**

  Cached results are returned instantly without running the model.

* **Higher Throughput**

  In high-QPS scenarios, Prompt Cache reduces compute load and improves overall system capacity.

* **Transparent to Your Application**

  no additional logic or system changes are required.

## 2. Supported Models

Several serverless open-source models currently support prompt cache billing, including:

<PromptCacheModels />

For pricing details regarding the prompt cache feature of supported models, please refer to: [https://novita.ai/pricing](https://novita.ai/pricing) (see "Cache" section).

## 3. Use Cases

Prompt Cache is highly effective in workloads with **frequent repeated Prompts**, including but not limited to:

* **Template-based Generation**
  * Fixed-format summaries
  * Template-driven rewriting
  * Prompts reused across tasks
* **Text Classification & Field Extraction**
  * Content type classification
  * Tag or key information extraction
* **Content Moderation**
  * Review of comments, ads, or titles
  * Many moderation prompts repeat across users and time
* **Repeated System Prompts in Chat Applications**
  * Chatbot persona definitions
  * Global conversation rules
  * Background information reused across multiple turns
* **Workflow / Assistant-style Prompts**
  * SQL generation assistants
  * Code repair assistants
  * Summary assistants with fixed output formats

These scenarios naturally achieve high cache hit rates, reducing inference cost significantly.

## 4. Response Examples

<Note>
  When the cache is hit, no inference is performed, resulting in significantly lower cost and faster responses.
</Note>

If the model supports prompt caching, your API calls require no modifications. Below is a sample response when hitting the cached result:

```JSON theme={"system"}
{
    "prompt_tokens": 3295,
    "completion_tokens": 137,
    "total_tokens": 3432,
    "prompt_tokens_details":
    {
        "audio_tokens": 0,
        "cached_tokens": 448,
        "cache_creation_Prompt_tokens": 0,
        "cache_read_Prompt_tokens": 0
    }
}
```


# Rate limits
Source: https://docs.novita.ai/guides/llm-rate-limits



Rate limits control how frequently users can make requests to our LLM API within specific time periods. Understanding and working within these limits is essential for optimal API usage.

<Tip>
  New to the API? See [API Keys](/guides/llm-api-keys) for how to create and authenticate with a key first.
</Tip>

## 1. Understanding Rate Limits

### What are Rate Limits?

Rate limits restrict the number of API requests that can be made within defined time periods. They help:

* Prevent API abuse and misuse;
* Ensure fair resource distribution among users;
* Maintain consistent API performance and reliability;
* Protect the stability of our services.

### Default Rate Limits

Each account has a default rate limit for model calls, measured in RPM (requests per model per minute) and TPM (tokens per model per minute). Rate limits vary by account tier, as outlined in the tables below.

<table>
  <thead>
    <tr>
      <th>Tier</th>
      <th>How to reach</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>T1</td>
      <td>Monthly top-ups did not exceed \$50 in any of the last 3 calendar months.</td>
    </tr>

    <tr>
      <td>T2</td>
      <td>Monthly top-ups were at least \$50 but did not exceed \$500 in any of the last 3 calendar months.</td>
    </tr>

    <tr>
      <td>T3</td>
      <td>Monthly top-ups were at least \$500 but did not exceed \$3,000 in any of the last 3 calendar months.</td>
    </tr>

    <tr>
      <td>T4</td>
      <td>Monthly top-ups were at least \$3,000 but did not exceed \$10,000 in any of the last 3 calendar months.</td>
    </tr>

    <tr>
      <td>T5</td>
      <td>Monthly top-ups were at least \$10,000 in at least one of the last 3 calendar months.</td>
    </tr>
  </tbody>
</table>

<Tip>The last 3 calendar months refers to the current month and the two months before it.</Tip>

Default Rate Limit by Tier (RPM / TPM):

<DynamicRPMList />

## 2. Handling Rate Limits

### How to Monitor Rate Limits?

When you exceed the rate limit, the API will return:

* HTTP Status Code: 429 (Too Many Requests);
* A rate limit exceeded message in the response body.

### Best Practices

To avoid hitting rate limits:

1. Implement request throttling in your application;
2. Add exponential backoff for retries;
3. Monitor your API usage patterns.

### When You Hit Rate Limits

If you receive a 429 error, you can:

1. **Retry Later**: Wait a short period before retrying your request;
2. **Optimize Requests**: Reduce request frequency;
3. **Rate Limits Increase**: For higher rate limits, you can:
   * <a href="https://discord.gg/YyPRAzwp7P">Contact us through Discord</a>
   * or <a href="https://meet.brevo.com/novita-ai/contact-sales">Book a call with our sales team</a>

***

**If you have any questions, [please reach out to us on Discord](https://discord.gg/YyPRAzwp7P).**


# Reasoning Models
Source: https://docs.novita.ai/guides/llm-reasoning



## Overview

Reasoning models are advanced language models optimized for complex problem-solving tasks. By generating detailed reasoning steps (chain-of-thought), they improve the accuracy of answers in analytical scenarios.

### Typical Use Cases

* **Complex Problem Solving**: Suitable for tasks requiring step-by-step logic, such as math or scientific reasoning.
* **Decision Support Systems**: Helps explain the logic behind conclusions by providing detailed reasoning processes.
* **Education and Training**: Assists learners in understanding complex concepts by presenting derivation processes clearly.

***

## Installation & Setup

Before using reasoning models, make sure the latest OpenAI SDK is installed:

```bash theme={"system"}
pip install -U openai
```

***

## API Usage

Use the `/chat/completions` endpoint to invoke reasoning models.

### Request Parameters

* `max_tokens`: Sets the maximum number of tokens the model can return.
* `temperature`: Recommended between 0.5 and 0.7 (suggested: 0.6) to balance creativity and logic.
* `top_p`: Recommended value is 0.95.

***

### Example Code

#### Streaming Response

```python theme={"system"}
from openai import OpenAI

client = OpenAI(api_key="YOUR_API_KEY", base_url="https://api.novita.ai/openai")
messages = [
    {"role": "user", "content": "Explain Newton's Second Law."}
]

response = client.chat.completions.create(
    model="deepseek/deepseek-v4.1-flash",
    messages=messages,
    stream=True,
    max_tokens=4096
)

content = ""
reasoning_content = ""
for chunk in response:
    if chunk.choices[0].delta.content:
        content += chunk.choices[0].delta.content
    if chunk.choices[0].delta.reasoning_content:
        reasoning_content += chunk.choices[0].delta.reasoning_content

print("Final Answer:", content)
print("Reasoning Steps:", reasoning_content)
```

#### Non-Streaming Response

```python theme={"system"}
response = client.chat.completions.create(
    model="deepseek/deepseek-v4.1-flash",
    messages=[
        {"role": "user", "content": "What is the greenhouse effect? How can it be mitigated?"}
    ],
    stream=False,
    max_tokens=4096
)

content = response.choices[0].message.content
reasoning_content = response.choices[0].message.reasoning_content

print("Final Answer:", content)
print("Reasoning Steps:", reasoning_content)
```

***

## Context Management

Reasoning outputs are not automatically carried over to the next round of dialogue. You must manually maintain the message history:

```python theme={"system"}
messages.append({"role": "assistant", "content": content})
messages.append({"role": "user", "content": "Please continue explaining the solution."})
```

***

## Supported Models

The following reasoning models are currently supported on the Novita platform:

<ReasoningModels />

***

## Billing

* Billing is based on the number of tokens for both input and output.
* Please refer to each model's pricing page for specific billing rules and token conversion details.

***

## Notes & Best Practices

* Avoid placing reasoning instructions in the `system` message. Instead, make the intent explicit in the `user` message.
* For mathematical tasks, clearly instruct the model, e.g., “Please reason step by step and provide a final answer.”
* To prevent the model from skipping reasoning steps, consider asking for a newline before the final answer.


# Recommended Models
Source: https://docs.novita.ai/guides/llm-recommended



A list of recommended open models for common use cases.

## Which open models should I use?

Actually, there’s no single right answer! Here’s a curated list based on Novita internal testing, community feedback, and external benchmarks. We recommend using it as a starting point, and we will update it regularly as new models emerge.

<Note>
  * Model sizes are marked as Small, Medium, or Large. For MoE models, the activated parameter size is what matters for latency.
  * For best latency, use small or medium models. For best quality, use large models or fine-tune medium or small models.
  * Model names below are exact model IDs — copy them directly into your API calls. Each links to its model page for live pricing and a playground.
  * You can explore all models in the [Novita Model Library](https://novita.ai/models/llm).
</Note>

### Code generation & reasoning

| Model ID | Size / Notes | Context Window |
| - | - | - |
| [`moonshotai/kimi-k2.7-code`](https://novita.ai/models/llm/moonshotai-kimi-k2.7-code) | Large | 262K |
| [`zai-org/glm-5.2`](https://novita.ai/models/llm/zai-org-glm-5.2) | Large | 1M |
| [`deepseek/deepseek-v4-pro`](https://novita.ai/models/llm/deepseek-deepseek-v4-pro) | Large | 1M |

### General reasoning & planning

| Model ID | Size / Notes | Context Window |
| - | - | - |
| [`deepseek/deepseek-v4-pro`](https://novita.ai/models/llm/deepseek-deepseek-v4-pro) | Large | 1M |
| [`deepseek/deepseek-v4.1-flash`](https://novita.ai/models/llm/deepseek-deepseek-v4.1-flash) | Large | 1M |
| [`qwen/qwen3.5-397b-a17b`](https://novita.ai/models/llm/qwen-qwen3.5-397b-a17b) | Large, 17B active | 262K |

### Agents, function calling & tool use

| Model ID | Size / Notes | Context Window |
| - | - | - |
| [`zai-org/glm-5.2`](https://novita.ai/models/llm/zai-org-glm-5.2) | Large | 1M |
| [`moonshotai/kimi-k2.7-code`](https://novita.ai/models/llm/moonshotai-kimi-k2.7-code) | Large | 262K |
| [`minimax/minimax-m3`](https://novita.ai/models/llm/minimax-minimax-m3) | Large | 1M |

### Long context & summarization

| Model ID | Size / Notes | Context Window |
| - | - | - |
| [`zai-org/glm-5.2`](https://novita.ai/models/llm/zai-org-glm-5.2) | Large | 1M |
| [`deepseek/deepseek-v4-pro`](https://novita.ai/models/llm/deepseek-deepseek-v4-pro) | Large | 1M |
| [`deepseek/deepseek-v4.1-flash`](https://novita.ai/models/llm/deepseek-deepseek-v4.1-flash) | Large, cost-effective | 1M |

### Vision & document understanding

| Model ID | Size / Notes | Context Window |
| - | - | - |
| [`moonshotai/kimi-k2.5`](https://novita.ai/models/llm/moonshotai-kimi-k2.5) | Large | 262K |
| [`qwen/qwen3.5-397b-a17b`](https://novita.ai/models/llm/qwen-qwen3.5-397b-a17b) | Large, 17B active | 262K |
| [`google/gemma-4-31b-it`](https://novita.ai/models/llm/google-gemma-4-31b-it) | Medium | 262K |

### Low-latency NLU & extraction

| Model ID | Size / Notes | Context Window |
| - | - | - |
| [`inclusionai/ling-3.0-flash`](https://novita.ai/models/llm/inclusionai-ling-3.0-flash) | MoE, small activation | 262K |
| [`meta-llama/llama-3.1-8b-instruct`](https://novita.ai/models/llm/meta-llama-llama-3.1-8b-instruct) | Small | 128K |
| [`google/gemma-4-26b-a4b-it`](https://novita.ai/models/llm/google-gemma-4-26b-a4b-it) | Medium, 4B active | 262K |

## Migrating from older models

If you are still calling one of the models below, switch to its recommended replacement:

| If you use | Switch to |
| - | - |
| `deepseek/deepseek-r1-0528` | `deepseek/deepseek-v4-pro` |
| `zai-org/glm-4.5` | `zai-org/glm-5.2` |
| `moonshotai/kimi-k2-instruct` | `moonshotai/kimi-k2.7-code` |
| `qwen/qwen3-coder-480b-a35b-instruct` | `qwen/qwen3.8-max` |
| `meta-llama/llama-4-maverick` / `llama-4-scout`, `qwen/qwen2.5-vl-72b-instruct` | `moonshotai/kimi-k2.5`, `qwen/qwen3.5-397b-a17b`, or `google/gemma-4-31b-it` |


# Structured Outputs
Source: https://docs.novita.ai/guides/llm-structured-outputs



`Structured Outputs` enables the model to generate responses that adhere to your supplied [JSON Schema](https://json-schema.org/specification).

## Supported Models

The following models support `Structured Outputs`:

<StructuredOutputsModels />

## Quick Start Guide

This guide demonstrates how to use `Structured Outputs` ability to generate the JSON response via `json_schema` response format. We will walk through a complete Python code example.

### 1. Initialize the Client

First, you need to initialize the client with your Novita API key.

```python theme={"system"}
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/openai",
    # Get the Novita AI API Key from: https://novita.ai/settings/key-management.
    api_key="<YOUR Novita AI API Key>",
)

# Go to the [Models](https://novita.ai/models) page to see the models that support `Structured Outputs`.
model = "qwen/qwen3.8-flash"
```

### 2. Define the JSON schema

This example creates a schema for extracting expense information from the user's input.

```python theme={"system"}

# Define system prompt for expense tracking.
system_prompt = """You are an expense tracking assistant. 
Extract expense information from the user's input and format it according to the provided schema."""

# Define JSON schema for structured response.
response_format = {
    "type": "json_schema",
    "json_schema": {
        "name": "expense_tracking_schema",
        "schema": {
            "type": "object",
            "properties": {
                "expenses": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "description": {
                                "type": "string",
                                "description": "Description of the expense"
                            },
                            "amount": {
                                "type": "number",
                                "description": "Amount spent in dollars"
                            },
                            "date": {
                                "type": "string",
                                "description": "When the expense occurred"
                            },
                            "category": {
                                "type": "string",
                                "description": "Category of expense (e.g., food, office, travel)"
                            }
                        },
                        "required": [
                            "description",
                            "amount"
                        ]
                    }
                },
                "total": {
                    "type": "number",
                    "description": "Total amount of all expenses"
                }
            },
            "required": [
                "expenses",
                "total"
            ],
        },
    },
}
```

### 3. Request chat completion API

Now, make the chat completion request to the Novita endpoint.

This request includes the `response_format` parameter, defining the JSON schema we defined in the previous step.

```python theme={"system"}
chat_completion = client.chat.completions.create(
    model=model,
    messages=[
        {
            "role": "system",
            "content": system_prompt,
        },
        {
            "role": "user",
            "content": """I spent $120 on dinner at an Italian restaurant last Friday with my colleagues.
Also bought office supplies for $45 on Monday.""",
        },
    ],
    max_tokens=1024,
    temperature=0.8,
    stream=False,
    response_format=response_format,
)

response_content = chat_completion.choices[0].message.content

# Parse and prettify the JSON
try:
    json_response = json.loads(response_content)
    prettified_json = json.dumps(json_response, indent=2)
    print(prettified_json)
except json.JSONDecodeError:
    print("Could not parse response as JSON. Raw response:")
    print(response_content)
```

**Output:**

```json theme={"system"}
{
  "expenses": [
    {
      "date": "2023-03-17",
      "description": "Dinner at Italian restaurant",
      "amount": 120,
      "category": "Food & Dining"
    },
    {
      "date": "2023-03-13",
      "description": "Office supplies",
      "amount": 45,
      "category": "Office Supplies"
    }
  ],
  "total": 165
}
```

### The Complete Code

```python theme={"system"}
from openai import OpenAI
import json

client = OpenAI(
    base_url="https://api.novita.ai/openai",
    # Get the Novita AI API Key from: https://novita.ai/settings/key-management.
    api_key="<YOUR Novita AI API Key>",
)

# Go to the [Models](https://novita.ai/models) page to see the models that support `Structured Outputs`.
model = "qwen/qwen3.8-flash"

# Example of using JSON Schema for structured output
# This example creates a schema for extracting expense information

# Define system prompt for expense tracking
system_prompt = """You are an expense tracking assistant. 
Extract expense information from the user's input and format it according to the provided schema."""

# Define JSON schema for structured response
response_format = {
    "type": "json_schema",
    "json_schema": {
        "name": "expense_tracking_schema",
        "schema": {
            "type": "object",
            "properties": {
                "expenses": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "description": {
                                "type": "string",
                                "description": "Description of the expense"
                            },
                            "amount": {
                                "type": "number",
                                "description": "Amount spent in dollars"
                            },
                            "date": {
                                "type": "string",
                                "description": "When the expense occurred"
                            },
                            "category": {
                                "type": "string",
                                "description": "Category of expense (e.g., food, office, travel)"
                            }
                        },
                        "required": [
                            "description",
                            "amount"
                        ]
                    }
                },
                "total": {
                    "type": "number",
                    "description": "Total amount of all expenses"
                }
            },
            "required": [
                "expenses",
                "total"
            ],
        },
    },
}

chat_completion = client.chat.completions.create(
    model=model,
    messages=[
        {
            "role": "system",
            "content": system_prompt,
        },
        {
            "role": "user",
            "content": """I spent $120 on dinner at an Italian restaurant last Friday with my colleagues.
Also bought office supplies for $45 on Monday.""",
        },
    ],
    max_tokens=1024,
    temperature=0.8,
    stream=False,
    response_format=response_format,
)

response_content = chat_completion.choices[0].message.content

# Parse and prettify the JSON
try:
    json_response = json.loads(response_content)
    prettified_json = json.dumps(json_response, indent=2)
    print(prettified_json)
except json.JSONDecodeError:
    print("Could not parse response as JSON. Raw response:")
    print(response_content)
```


# Vision Language Models
Source: https://docs.novita.ai/guides/llm-vision



## Overview

Vision-Language Models (VLMs) are a type of multimodal foundation model capable of processing both image and text inputs. These models understand visual content in conjunction with language instructions, and generate high-quality responses based on the combined context. They are widely used in scenarios involving image recognition, content interpretation, and intelligent visual Q\&A.

### Typical Use Cases

* **Image Recognition and Description**: Automatically identifies objects, colors, scenes, and spatial relationships in images, and generates natural language descriptions.
* **Multimodal Understanding**: Combines image input and contextual text for multi-turn dialogue and task completion.
* **Visual Question Answering**: Acts as an advanced OCR tool by recognizing and interpreting text embedded in images.
* **Emerging Applications**: Ideal for use in intelligent vision assistants, robot perception, AR interfaces, and more.

***

## API Usage Guide

To invoke a Vision-Language Model, use the `/chat/completions` endpoint with both image and text inputs.

### Image Detail Parameter

Use the `detail` field to control image resolution. The following modes are available:

* `high`: High resolution, preserves more detail—ideal for precision tasks.
* `low`: Low resolution, faster response—suitable for real-time usage.
* `auto`: Automatically selects the appropriate mode.

***

### Example Message Format

#### Image via URL

```json theme={"system"}
{
  "role": "user",
  "content": [
    {
      "type": "image_url",
      "image_url": {
        "url": "https://example.com/image.png",
        "detail": "high"
      }
    },
    {
      "type": "text",
      "text": "Please describe the scene in the image."
    }
  ]
}
```

#### Image via Base64

```json theme={"system"}
{
  "role": "user",
  "content": [
    {
      "type": "image_url",
      "image_url": {
        "url": "data:image/jpeg;base64,{base64_image}",
        "detail": "low"
      }
    },
    {
      "type": "text",
      "text": "What text is present in the image?"
    }
  ]
}
```

***

### Python Code: Encode Image to Base64

```python theme={"system"}
import base64
from PIL import Image
import io

def image_to_base64(image_path):
    with Image.open(image_path) as img:
        buffered = io.BytesIO()
        img.save(buffered, format="JPEG")
        return base64.b64encode(buffered.getvalue()).decode('utf-8')

base64_image = image_to_base64("path/to/your/image.jpg")
```

***

## Multi-Image Input

The API supports sending multiple images alongside text input. For best results, we recommend sending no more than two images per request.

```json theme={"system"}
{
  "role": "user",
  "content": [
    {
      "type": "image_url",
      "image_url": {
        "url": "https://example.com/image1.png"
      }
    },
    {
      "type": "image_url",
      "image_url": {
        "url": "data:image/jpeg;base64,{base64_image}"
      }
    },
    {
      "type": "text",
      "text": "Compare the common features of these two images."
    }
  ]
}
```

***

## Supported Models

The following Vision-Language Models are currently supported on the Novita platform:

<VisionModels />

Visit the [Model Hub](https://novita.ai/models-console/library) for a complete and up-to-date list of available models.

***

## Billing

Image input is tokenized and counted toward billing together with text.

* Each model uses a different image-to-token conversion method.
* Refer to each model’s pricing page for detailed billing and token policy.

***

## API Call Examples

### Single Image Description

```python theme={"system"}
from openai import OpenAI

client = OpenAI(api_key="YOUR_KEY", base_url="https://api.novita.ai/openai")

response = client.chat.completions.create(
    model="zai-org/glm-4.5v",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/cityscape.jpg"
                    }
                },
                {
                    "type": "text",
                    "text": "Describe the main buildings in the image."
                }
            ]
        }
    ],
    stream=True
)

for chunk in response:
    print(chunk.choices[0].delta.content or "", end="", flush=True)
```

### Multi-Image Comparison

```python theme={"system"}
response = client.chat.completions.create(
    model="zai-org/glm-4.5v",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/product1.jpg"
                    }
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/product2.jpg"
                    }
                },
                {
                    "type": "text",
                    "text": "Please compare the key differences between these two products."
                }
            ]
        }
    ],
    stream=True
)

for chunk in response:
    print(chunk.choices[0].delta.content or "", end="", flush=True)
```

***

## Notes & Troubleshooting

* Image resolution and clarity significantly affect model performance. Use high-quality sources where possible.
* Base64-encoded images should ideally be under 1MB to avoid timeouts or errors.
* For detailed usage, [book a call with our sales team](https://meet.brevo.com/novita-ai/contact-sales) or contact support if needed.


# LobeChat
Source: https://docs.novita.ai/guides/lobechat

Integrate Novita AI with LobeChat to build advanced AI apps with multimodal capabilities, enhanced privacy, and customizable features.

With the **Novita AI & LobeChat integration**, you gain seamless access to Novita AI’s comprehensive suite of LLM models, including Llama, DeepSeek, and more. This integration enables you to create and deploy advanced AI applications tailored to your needs. Whether it’s customer service, content creation, or personal assistance, LobeChat, and Novita AI provide the tools to build intelligent, dynamic, and privacy-focused AI solutions.

This guide will walk you through integrating Novita AI’s LLMs with the LobeChat platform, enabling you to deploy AI applications with advanced capabilities like multimodal interaction, context-aware reasoning, and extensible plugins.

## **What Is LobeChat?**

LobeChat is an open-source, modern UI/Framework designed for ChatGPT and other LLMs. It simplifies the development of AI chat applications by supporting speech synthesis, multi-modal inputs, and an extensible plugin ecosystem. With LobeChat, you can deploy your private chat application using models like OpenAI’s ChatGPT, Claude, Gemini, or Novita AI’s LLMs in just one click.

## **Prerequisites**

Before you begin, ensure you have the following:

* **Novita AI LLM API Key**:
  * Visit [Novita AI’s website](https://novita.ai/) and create an account.
  * After logging in, go to the [**Key Management**](https://novita.ai/settings/key-management) page to generate your **API Key**. This key is required to connect Novita AI’s models to LobeChat.

    <Frame>
      ![Novita AI key management](https://mintlify.s3.us-west-1.amazonaws.com/novitaai/images/third-party/dify-1.png)
    </Frame>
* **LobeChat Account**
  * Sign up for a LobeChat account at [LobeChat](https://lobechat.com).

## **Integration Steps**

### **Step 1: Connect Novita AI to LobeChat**

<Frame>
  <img alt="Connect Novita AI to LobeChat" />
</Frame>

To connect Novita AI’s models with LobeChat:

* Log in to your **LobeChat account** and access the [**Settings**](https://lobechat.com/settings/common).
* Navigate to the **AI Service Provider** tab.
* Scroll down to find the **Novita AI** section and expand it.
* Paste your **Novita AI API Key** into the provided field and click **Save**.

### **Step 2:** Select a Novita AI Model

Once the API Key is configured, you can select a Novita AI LLM model to power your AI application:

<Frame>
  <img alt="Choose a Novita AI Model" />
</Frame>

### Step 3: Launch Your First Chat

Begin your conversation with Novita AI LLM on LobeChat.


# LoLLMS WebUI
Source: https://docs.novita.ai/guides/lollmswebui

Easily integrate Novita AI with LoLLMS WebUI to enhance your productivity and simplify complex tasks.

LoLLMS WebUI, a centralized platform designed for effortless interaction with Large Language Models (LLMs) and multimodal AI systems, offers an intuitive interface to unlock the full potential of AI. The integration between Novita AI and LoLLMS WebUI will unleash transformative power,enabling you to simplify complex tasks, find answers, and explore new possibilities effortlessly.

By combining Novita AI with LoLLMS, you'll gain access to cutting-edge AI capabilities, making it your ultimate assistant for enhanced productivity. This guide provides a step-by-step walkthrough to help you maximize the benefits of this powerful integration.

## How to Leverage Novita AI with LoLLMS WebUI

You can find the GitHub repository of LoLLMS WebUI here: [ParisNeo/lollms-webui](https://github.com/ParisNeo/lollms-webui).

### Obtain Your Novita AI API Key

1. [Log in](https://novita.ai/user/login) to your Novita AI account.
2. Navigate to [the Key Management page](https://novita.ai/settings/key-management).
3. Generate a new API Key and copy it.

### Install LoLLMS WebUI

1. Automatic Installation:
   * Download the installation script from [the scripts folder](https://github.com/ParisNeo/lollms-webui/tree/main/scripts) and run it:
     * `lollms_installer.bat` for Windows.
     * `lollms_installer.sh` for Linux.
     * `lollms_installer_macos.sh` for Mac.
2. Manual Installation:
   * Ensure Python 3.11 is installed. Check your version with `python --version`.
   * If needed, download Python 3.11 from [Python.org](https://www.python.org/downloads/release/python-3118/).

Step 1: Clone the Repository

```bash theme={"system"}
git clone --recursive https://github.com/ParisNeo/lollms-webui.git
cd lollms-webui
git submodule update --init --recursive
```

Step 2: Create and Activate a Virtual Environment

* Create a Virtual Environment:

```bash theme={"system"}
python -m venv venv
```

* Activate the Virtual Environment:
  * On Windows:

    ```bash theme={"system"}
    .\venv\Scripts\activate
    ```
  * On Linux/Mac:

    ```bash theme={"system"}
    source venv/bin/activate
    ```

Step 3: Install Requirements

```bash theme={"system"}
pip install -r requirements.txt
pip install -e ./lollms_core
```

Step 4: Create global\_paths\_cfg.yaml

```bash theme={"system"}
mkdir -p $HOME/.lollms_personal_data

cat > global_paths_cfg.yaml << EOL
lollms_path: $(pwd)/lollms_core/lollms
lollms_personal_path: $HOME/.lollms_personal_data
EOL
```

### Configure Novita AI in LoLLMS WebUI

1. Install Novita AI Binding

* Set environment variables for Novita AI binding. For example:

  * On Windows:

  ```bash theme={"system"}
  set NOVITA_AI_API_KEY="your_api_key_here"
  set NOVITA_AI_MODEL_NAME="your_model_name_here"
  ```

  * On Linux/Mac:

  ```bash theme={"system"}
  export NOVITA_AI_API_KEY="your_api_key_here"
  export NOVITA_AI_MODEL_NAME="your_model_name_here"
  ```
* Run the script to finalize the setup:

  ```bash theme={"system"}
  python zoos/bindings_zoo/novita_ai/__init__.py
  ```

2. Run the Server

```bash theme={"system"}
python app.py
```

3. Use Novita AI in LoLLMS WebUI

* Open your browser and navigate to [http://localhost:9600](http://localhost:9600) (or the port shown in the terminal).
* Select Novita AI from the available bindings.
* Enter your Novita AI API key.
* Select your desired Novita AI model.

[******Here is an application: Vibe Coding Using Novita AI Bindings and Services on LoLLMS WebUI.******](https://www.youtube.com/watch?v=jyFaP4zTM9g)


# MLflow
Source: https://docs.novita.ai/guides/mlflow

Trace Novita AI calls in MLflow with OpenAI-compatible SDK instrumentation for Python and JavaScript.

This guide shows how to integrate **Novita AI** with MLflow Tracing. By using Novita AI's OpenAI-compatible endpoint (`https://api.novita.ai/openai`), you can capture prompts, responses, latency, token usage, and model metadata in MLflow.

<Frame>
  <img alt="MLflow trace details and timeline" />
</Frame>

# Prerequisites

Before you start, make sure you have:

* Novita AI API key: create one in [Key Management](https://novita.ai/settings/key-management).
* A running MLflow tracking server. You can use local default `http://localhost:5000`.
* Python or JavaScript runtime.

# Integration Steps

## Step 1: Install Dependencies

<CodeGroup>
  ```bash Python icon="python" theme={"system"}
  pip install 'mlflow[genai]' openai
  ```

  ```bash JavaScript / TypeScript icon="js" theme={"system"}
  npm install @mlflow/openai openai
  ```
</CodeGroup>

## Step 2: Start MLflow Server

If you have a local Python environment >= 3.10, you can start MLflow with:

```bash theme={"system"}
mlflow server
```

MLflow also provides a Docker Compose setup:

```bash theme={"system"}
git clone --depth 1 --filter=blob:none --sparse https://github.com/mlflow/mlflow.git
cd mlflow
git sparse-checkout set docker-compose
cd docker-compose
cp .env.dev.example .env
docker compose up -d
```

Then open `http://localhost:5000` to confirm the MLflow UI is accessible.

## Step 3: Enable Tracing and Call Novita AI

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import openai
  import mlflow

  # Enable auto-tracing for OpenAI-compatible calls
  mlflow.openai.autolog()

  # Optional: set tracking target and experiment
  mlflow.set_tracking_uri("http://localhost:5000")
  mlflow.set_experiment("Novita AI")

  client = openai.OpenAI(
      base_url="https://api.novita.ai/openai",
      api_key="<your_novita_api_key>",
  )

  response = client.chat.completions.create(
      model="deepseek/deepseek-v4.1-flash",
      messages=[
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": "What is the capital of France?"},
      ],
  )

  print(response.choices[0].message.content)
  ```

  ```ts JavaScript / TypeScript icon="js" theme={"system"}
  import { OpenAI } from "openai";
  import { tracedOpenAI } from "@mlflow/openai";

  const client = tracedOpenAI(
    new OpenAI({
      baseURL: "https://api.novita.ai/openai",
      apiKey: "<your_novita_api_key>",
    })
  );

  const response = await client.chat.completions.create({
    model: "deepseek/deepseek-v4.1-flash",
    messages: [
      { role: "system", content: "You are a helpful assistant." },
      { role: "user", content: "What is the capital of France?" },
    ],
    temperature: 0.1,
    max_tokens: 100,
  });

  console.log(response.choices[0].message?.content);
  ```
</CodeGroup>

## Step 4: View Traces in MLflow UI

Open your MLflow UI (for example `http://localhost:5000`) and go to your configured experiment to inspect traces.

You should see:

* Prompt and completion content
* Latency and token usage
* Model and request metadata
* Errors/exceptions (if any)

## Step 5: Advanced Tracing References

### Streaming and Async

MLflow supports tracing for streaming and async Novita AI APIs. See:

* [OpenAI Tracing](https://mlflow.org/docs/latest/genai/tracing/integrations/listing/openai/)

### Combine with frameworks or manual tracing

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import json
  from openai import OpenAI
  import mlflow
  from mlflow.entities import SpanType

  # Initialize the OpenAI client with Novita AI API endpoint
  client = OpenAI(
      base_url="https://api.novita.ai/openai",
      api_key="<your_novita_api_key>",
  )


  # Create a parent span for the Novita AI call
  @mlflow.trace(span_type=SpanType.CHAIN)
  def answer_question(question: str):
      messages = [{"role": "user", "content": question}]
      response = client.chat.completions.create(
          model="deepseek/deepseek-v4.1-flash",
          messages=messages,
      )

      # Attach session/user metadata to the trace
      mlflow.update_current_trace(
          metadata={
              "mlflow.trace.session": "session-12345",
              "mlflow.trace.user": "user-a",
          }
      )
      return response.choices[0].message.content


  answer = answer_question("What is the capital of France?")
  ```

  ```ts JavaScript / TypeScript icon="js" theme={"system"}
  import * as mlflow from "@mlflow/core";
  import { OpenAI } from "openai";
  import { tracedOpenAI } from "@mlflow/openai";

  mlflow.init({
    trackingUri: "http://localhost:5000",
    experimentId: "<your_experiment_id>",
  });

  // Wrap the OpenAI client and point to Novita AI endpoint
  const client = tracedOpenAI(
    new OpenAI({
      baseURL: "https://api.novita.ai/openai",
      apiKey: "<your_novita_api_key>",
    })
  );

  // Create a traced function that wraps the Novita AI call
  const answerQuestion = mlflow.trace(
    async (question: string) => {
      const resp = await client.chat.completions.create({
        model: "deepseek/deepseek-v4.1-flash",
        messages: [{ role: "user", content: question }],
      });
      return resp.choices[0].message?.content;
    },
    { name: "answer-question" }
  );

  await answerQuestion("What is the capital of France?");
  ```
</CodeGroup>

For full upstream reference:

* MLflow Novita AI integration page: [Tracing Novita AI](https://mlflow.org/docs/latest/genai/tracing/integrations/listing/novitaai/)
* MLflow OpenAI tracing docs: [OpenAI Tracing](https://mlflow.org/docs/latest/genai/tracing/integrations/listing/openai/)

For Novita model details and endpoint usage, see:

* Novita LLM API guide: [LLM API](/guides/llm-api)


# A Brief Introduction to Clip Skip
Source: https://docs.novita.ai/guides/model-apis-clip-skip



Clip Skip is a feature that literally skips part of the image generation process, resulting in slightly different outputs. This also leads to faster image rendering.

### But why would anyone want to skip a part of the diffusion process?

A typical Stable Diffusion 1.5 base model image goes through 12 "clip" layers, which represent levels of refinement. The early layers are very broad, while the later layers produce images that are clearer and more specific. In the case of some base models, especially those based on Danbooru tags, trial and error has shown that skipping certain layers can lead to better images, as the broad clip layers may introduce unwanted noise. You can literally skip layers, saving GPU time and achieving better art.

If you want a picture of "a cow", you might not care about the subcategories of "cow" that the text model might have. Especially since these can vary in quality. So if you want "a cow", you might not want "an Aberdeen Angus bull". (The full post is at the bottom of this page.)

You can think of CLIP Skip as a setting for "how accurate you want the text model to be". You can test it out and see that each clip stage adds more definition in terms of description. For example, if you have a detailed prompt about a young man standing in a field, with lower clip stages you’d get an image of "a man standing", while deeper stages would yield "a young man standing" or "a young man standing in a forest", and so on. CLIP Skip becomes particularly effective when using models that are structured in a specific way, where the "1girl" tag can break down into many sub-tags connected to that main tag.

### Do I need it?

It’s a minor optimization recommended only for hardcore, quality-obsessed enthusiasts. If you’re working on anime or semi-realism, it’s worth a try.

### Limitations

**How many layers to skip**

Generally speaking, skipping 1 or 2 layers can yield good results. However, skipping more than 2 layers may produce images that appear to have low guidance.

**Inconsistent compatibility**

Clip Skip has become one of those "wear your seatbelt" kinds of safe defaults, where many people prefer to set it and forget it. This approach isn’t wise. The feature can also yield unpredictable results when used with other technologies, such as LoRAs and Textual Inversions. Missing layers where layers are expected can degrade the image quality or have no effect at all.

It’s often faster to simply try it out than to compare results. Just make sure you also lock the seed, guidance, concept, and sampler to accurately assess the differences.


# Configure Custom AWS S3 Bucket
Source: https://docs.novita.ai/guides/model-apis-configure-custom-s3-bucket



By default, Novita AI temporarily stores output results in our private S3 bucket and returns the results to the user through a temporary authorized S3 link.

However, you can set up a **Custom S3 Bucket** to allow us to save the results in your own bucket. Please follow the steps below to enable this.

## 1. Configure S3 Bucket Policy

First, change your S3 Bucket Policy configuration to the following format (replace `${BucketName}` with your bucket name):

```json theme={"system"}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "CanonicalUser": "e98cde8d11ec7c03ac08688f1a933b08b0f0f7746b21c4f2e7b2c8202cc0532f"
      },
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": "arn:aws:s3:::${BucketName}/*"
    }
  ]
}
```

## 2. Enable Custom Storage in V3 APIs

For V3 API endpoints, Novita AI provides the `custom_storage` parameter in the request body, allowing you to configure your custom S3 bucket for storing generated images.

Here's an example using the `txt2img` API endpoint:

```bash theme={"system"}
curl --location 'https://api.novita.ai/v3/async/txt2img' \
--header 'Authorization: Bearer {{API Key}}' \
--header 'Content-Type: application/json' \
--data '{
  "extra": {
    "response_image_type": "jpeg",
    "custom_storage": {
      "aws_s3": {
        "region": "us-east-2",
        "bucket": "test_bucket",
        "path": "/"
      }
    }
  },
  "request": {
    "prompt": "a cute dog",
    "model_name": "realisticVisionV51_v51VAE_94301.safetensors",
    "negative_prompt": "",
    "width": 512,
    "height": 384,
    "image_num": 2,
    "steps": 20,
    "seed": 123,
    "clip_skip": 1,
    "sampler_name": "Euler a",
    "guidance_scale": 7.5
  }
}'
```


# Upload Custom LoRA Models
Source: https://docs.novita.ai/guides/model-apis-custom-model



<Tip>
  * Currently, the Model Upload feature only supports LoRA models, with a limit of 5 uploads per user. We plan to gradually lift these restrictions in the future;
  * The Model Upload feature is temporarily available free of charge. However, we may introduce a fee for this service in the future. Please stay tuned for future announcements;
  * If you have any questions, please contact our customer service or technical team through the [Discord Channel](https://discord.gg/YyPRAzwp7P).
</Tip>

1. Log in to your account and go to the Model Management page.

2. Click the `Upload Model` button to upload your models.

3. Upload the LoRA model and fill in the form. Please note that the model name cannot be modified after submission.

4. After the model is uploaded successfully, you can use the model from the model list.

**For example:**

<Tip>
  In the following example, the LoRA model name `test_sd_name_in_api.safetensors` can be found in the `MODEL NAME IN API` column in your private model list.
</Tip>

```bash theme={"system"}
curl --location --request POST 'https://api.novita.ai/v3/async/txt2img' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{API Key}}' \
--data-raw '{
    "model_name": "AnythingV5_v5PrtRE.safetensors",
    "width": 512,
    "height": 768,
    "prompt": "a body stood high, behind him, looking down.",
    "negative_prompt": "",
    "sampler_name": "DPM++ 2S a Karras",
    "guidance_scale": 7.5,
    "steps": 30,
    "image_num": 1,
    "clip_skip": 0,
    "seed": -1,
    "loras": [
        {"model_name":"test_sd_name_in_api.safetensors", "strength":0.7}
    ]
}'
```


# Overview
Source: https://docs.novita.ai/guides/model-apis-overview



Novita AI deploys state-of-the-art open-source models. Use our APIs to access them out of the box!

<CardGroup>
  <Card title="Try Novita's LLM Playground" icon="box" href="https://novita.ai/model-api/product/llm-api/playground?utm_source=docs-guides">
    Test prompts, explore our models, and compare results instantly, no coding required.
  </Card>

  <Card title="Integrate with open-source LLM APIs" icon="box" href="/guides/llm-api">
    Get started with Novita's OpenAI-compatible APIs.
  </Card>

  <Card title="Explore our Image, Audio and Video Generation Models" icon="box" href="https://novita.ai/model-api/playground?utm_source=docs-guides">
    Try the Image, Audio and Video Generation Models in the Playground.
  </Card>

  <Card title="Integrate with Image, Audio and Video Generation APIs" icon="box" href="/api-reference/model-apis-introduction">
    Explore our API references.
  </Card>
</CardGroup>


# Rate limits
Source: https://docs.novita.ai/guides/model-apis-rate-limits



## 1. Understanding Rate Limits

Rate limiting refers to the rules that govern how frequently a user’s API may access our platform services within a given time window. Its goals are:

* **Prevent API abuse and misuse:** By setting an upper bound on request frequency, we limit abnormal or unintended traffic.
* **Ensure fair resource allocation:** We prevent a small number of users from monopolizing system resources, so everyone can access the service on equal terms.
* **Maintain API performance and reliability:** By smoothing out traffic spikes, we stabilize response times and reduce failure rates due to overload, improving overall service quality.
* **Protect system stability:** Rate limits help absorb sudden bursts of traffic, preventing high-concurrency spikes from overwhelming our infrastructure.

## 2. Rate Limiting Metrics

For our image and video models, we use two primary rate-limiting metrics:

* **IPM (Images Per Minute):** The number of images an image model is allowed to generate per minute.
* **RPM (Requests Per Minute):** The number of API requests a video model is allowed to handle per minute.

## 3. Default Rate Limits

For different models, our platform applies differentiated rate-limiting strategies based on each model’s computational resource consumption.

### **IPM**

The IPM (Images Per Minute) limit controls the number of images that can be generated per minute. The default IPM values for each model are listed in the table below.

| Resource/Service | Model API | User Default Settings |
| :- | :- | :- |
| Text to Image | `txt2img_v3` | 20 |
| Image to Image | `img2img_v3` | 10 |
| Remove Background | `remove_background` | 10 |
| Replace Background | `replace_background` | 10 |
| Remove Text | `remove_text` | 10 |
| Inpainting | `inpainting` | 10 |
| Cleanup | `cleanup` | 10 |
| Merge Face | `merge_face` | 10 |
| FLUX.1 \[schnell] Text to Image | `flux-1-schnell` | 10 |
| Upscale | `upscale_v3` | 20 |

### **RPM**

The RPM (Requests Per Minute) limit controls the number of API requests allowed per minute. The default RPM values for each model are provided in the table below.

| Resource/Service | Model API | User Default Settings |
| :- | :- | :- |
| Video Merge Face | `video_merge_face` | 10 |
| Text to Video | `txt2video` | 2 |
| Image to Video | `img2video` | 2 |
| Wan 2.1 Text to Video | `wan_txt_to_video` | 20 |
| Wan 2.1 Image to Video | `wan i2v` | 20 |
| Hunyuan Video Fast | `hunyuan_video_fast` | 20 |
| KLING V1.6 Image to Video | `Kling i2v` | 20 |
| KLING V1.6 Text to Video | `Kling t2v` | 20 |

## 4. Handling Rate Limits

### How to Monitor Rate Limits?

If your API requests exceed the allowed rate, the API will return:

* **HTTP Status Code:** 429 Too Many Requests
* **Response Body:** A message indicating that the rate limit has been exceeded.

### Best Practices

**To avoid triggering rate limits, you can:**

* **Implement client-side request throttling:** Respect the platform’s rate limits by controlling your application’s request rate, ensuring you don’t send too many calls in a short period.
* **Use exponential backoff on retries:** When you receive a rate-limit response (e.g., HTTP 429), wait progressively longer between retry attempts instead of retrying immediately, reducing load on the service.
* **Monitor your API usage:** Continuously track and log your request counts, frequencies, and any error responses so you can adjust your usage patterns proactively.

### When You Hit Rate Limits

**If you receive an HTTP 429 ("Too Many Requests") response, you can:**

* **Retry later:** Wait a short period before sending your request again.
* **Optimize your requests:** Reduce the frequency of calls to stay within the platform’s rate limits.
* **Request a higher rate limit:** If you require a higher rate limit, please fill out the contact form at the [Quotas\&Limits](https://novita.ai/quota-limits/image)  to get in touch with us.


# A Brief Introduction to Sampler
Source: https://docs.novita.ai/guides/model-apis-sampler



Stable Diffusion Art. (n.d.). Samplers. Retrieved from [https://stable-diffusion-art.com/samplers/.](https://stable-diffusion-art.com/samplers/)

Many sampling methods are available in Novita AI, including Euler a, Heun, and DDIM. What are samplers? How do they work? What are the differences between them? Which one should you use? You will find the answers in this article.

<Frame>
  <img />
</Frame>

To produce an image, Stable Diffusion first generates a completely random image in the latent space. The noise predictor then estimates the noise of the image, which is subsequently subtracted from the image. This process is repeated multiple times, resulting in a clean image.

This denoising process is called sampling because Stable Diffusion generates a new sample image at each step. The method used in sampling is referred to as the `sampler` or `sampling method`.

Sampling is just one part of the Stable Diffusion model. Read the article "How Does Stable Diffusion Work?" if you want to understand the entire model.

Below is a sampling process in action. The sampler gradually produces cleaner and cleaner images.

<Frame>
  <img />
</Frame>

While the framework is the same, there are many different ways to carry out this denoising process. It is often a trade-off between speed and accuracy.

### Samplers Overview

As of this writing, there are 19 samplers available in Novita AI, and this number seems to be growing over time. What are the differences?

<Frame>
  <img />
</Frame>

You will learn about them in the later part of this article. The technical details can be overwhelming, so I will include a bird's-eye view in this section to help you get a general idea of what they are.

#### Old-School ODE Solvers

Let’s start with the easier ones. Some of the samplers on the list were invented more than a hundred years ago. They are old-school solvers for ordinary differential equations (ODE).

* Euler – The simplest possible solver.
* Heun – A more accurate but slower version of Euler.
* LMS (Linear Multi-Step Method) – Same speed as Euler but supposedly more accurate.

#### Ancestral Samplers

Do you notice that some sampler names have a single letter "a"?

* Euler a
* DPM2 a
* DPM++ 2S a
* DPM++ 2S a Karras

These are ancestral samplers. An ancestral sampler adds noise to the image at each sampling step. They are stochastic samplers because the sampling outcome contains some randomness.

Be aware that many other samplers are also stochastic, even if their names do not include an "a".

The drawback of using an ancestral sampler is that the image may not converge. Compare the images generated using Euler a and Euler below.

Euler a does not converge. (sampling steps 2–40)

<Frame>
  <img />
</Frame>

Euler converges. (sampling steps 2-40)

<Frame>
  <img />
</Frame>

Images generated with Euler a do not converge at high sampling steps, while images from Euler converge well.

For reproducibility, it is desirable to have the image converge. If you want to generate slight variations, you should use a variational seed.

#### Karras Noise Schedule

The samplers labeled "Karras" use the noise schedule recommended in the [Karras article](https://arxiv.org/abs/2206.00364). If you look closely, you will see that the noise step sizes are smaller near the end. This adjustment improves the quality of the images.

#### DDIM and PLMS

DDIM (Denoising Diffusion Implicit Model) and PLMS (Pseudo Linear Multi-Step Method) were the samplers included with the original Stable Diffusion v1. DDIM is one of the first samplers designed for diffusion models, while PLMS is a newer and faster alternative to DDIM. They are generally considered outdated and are not widely used anymore.

#### DPM and DPM++

DPM (Diffusion Probabilistic Model Solver) and DPM++ are new samplers designed for diffusion models released in 2022. They represent a family of solvers with similar architecture.

DPM and DPM2 are similar, with DPM2 being second-order (more accurate but slower).

DPM++ is an improvement over DPM.

DPM adaptive adjusts the step size adaptively. It can be slow since it doesn’t guarantee completion within the specified number of sampling steps.

#### UniPC

UniPC (Unified Predictor-Corrector) is a new sampler released in 2023. Inspired by the predictor-corrector method in ODE solvers, it can achieve high-quality image generation in 5-10 steps.

### How to Pick a Sampler

In this section, I will provide some objective comparisons to help you decide.

#### Image Convergence

In this section, I will generate the same image using different samplers with up to 40 sampling steps. The last image at the 40th step will be used as a reference for evaluating how quickly the sampling converges, with the Euler method serving as the benchmark.

> Euler, DDIM, PLMS, LMS Karras and Heun

First, let’s look at Euler, DDIM, PLMS, LMS Karras, and Heun as a group since they represent old-school ODE solvers or original diffusion solvers. DDIM converges at about the same number of steps as Euler but with more variations due to the injection of random noise during its sampling steps.

<Frame>
  <img />
</Frame>

PLMS did not perform well in this test.

LMS Karras seems to have difficulty converging and stabilizes at a higher baseline.

Heun converges faster but is twice as slow since it is a second-order method. Therefore, we should compare Heun at 30 steps with Euler at 15 steps, for example.

> Ancestral Samplers

If a stable, reproducible image is your goal, you should avoid using ancestral samplers, as they all fail to converge.

<Frame>
  <img />
</Frame>

> DPM and DPM2

DPM fast did not converge well. DPM2 and DPM2 Karras performs better than Euler but again in the expense of being two times slower.

DPM adaptive performs deceptively well because it uses its own adaptive sampling steps. It can be very slow.

<Frame>
  <img />
</Frame>

> DPM++ solvers

DPM++ SDE and DPM++ SDE Karras suffer the same shortcoming as ancestral samplers. They not only don’t converge, but the images also fluctuate significantly as the number of steps changes.

DPM++ 2M and DPM++ 2M Karras perform well. The Karras variant converges faster when the number of steps is high enough.

<Frame>
  <img />
</Frame>

> UniPC

UniPC converges a bit slower than Euler, but not too bad.

<Frame>
  <img />
</Frame>

#### Speed

<Frame>
  <img />
</Frame>

Although DPM adaptive performs well in convergence, it is also the slowest.

You may have noticed the rest of the rendering times fall into two groups, with the first group taking about the same time (about 1x), and the other group taking about twice as long (about 2x). This reflects the order of the solvers. 2nd order solvers, although more accurate, need to evaluate the denoising U-Net twice. So they are 2x slower.

#### Quality

Of course, speed and convergence mean nothing if the images look crappy.

**Final images**

Let’s first look at samples of the image.

<Frame>
  <img />
</Frame>

DPM++ fast failed pretty badly. Ancestral samples did not converge to the image that other samplers converged to.

Ancestral samplers tend to converge to an image of a kitten, while the deterministic ones tend to converge to a cat. There are no correct answers as long as they look good to you.

Perceptual quality
An image can still look good even if it hasn’t converged. Let’s look at how quickly each sampler can produce a high-quality image.

You will see perceptual quality measured with BRISQUE (Blind/Referenceless Image Spatial Quality Evaluator). It measures the quality of natural images.

DDIM is doing surprisingly well here, capable of producing the highest quality image within the group in as few as 8 steps.

### So… which one is the best?

Here are my recommendations:

1. If you want to use something fast, converging, new, and with decent quality, excellent choices are

* DPM++ 2M Karras with 20 – 30 steps
* UniPC with 20-30 steps.

2. If you want good quality images and don’t care about convergence, good choices are

* DPM++ SDE Karras with 10-15 steps (Note: This is a slower sampler)
* DDIM with 10-15 steps.

3. Avoid using any ancestral samplers if you prefer stable, reproducible images.

4. Euler and Heun are fine choices if you prefer something simple. Reduce the number of steps for Heun to save time.


# SDKs for Model APIs
Source: https://docs.novita.ai/guides/model-apis-sdks



Novita AI provides official SDKs to help you easily integrate our services into your applications.

## JavaScript SDK

### Installation

Install our JavaScript SDK using `npm`:

```bash theme={"system"}
npm i novita-sdk
```

### Quick Start

Visit [https://github.com/novitalabs/javascript-sdk](https://github.com/novitalabs/javascript-sdk) for a quick start.

## Python SDK

### Installation

Install our Python SDK using `pip`:

```bash theme={"system"}
pip install novita-client
```

### Quick Start

Visit [https://github.com/novitalabs/python-sdk](https://github.com/novitalabs/python-sdk) for a quick start.


# Training Image Caption Guidance
Source: https://docs.novita.ai/guides/model-apis-training-guidance



### Preparation

Depending on how and what you are training, you may need to crop the photos to a specific width and height. Other types of training may categorize images into various sizes and do not require cropping. Look into what is required for the training method you are using, the model you are training, and the program you are using to train your model.

### Captioning – General Notes

#### Avoid Automated Captioning

* BLIP and deepbooru are exciting, but it is still a bit early for them.

* BLIP and deepbooru struggle with context and relative importance.

* It is faster to manually caption than to fix mistakes made by BLIP or deepbooru and still have to manually caption.

#### Caption in the Same Manner You Prompt

* Captioning and prompting are related.

* Recognize how you typically prompt. Do you use verbose sentences? Short descriptions? Vague or detailed prompts?

* Caption in a similar style and verbosity to how you usually prompt.

#### Follow a Set Structure per Concept

* Following a structure will benefit the learning process.

* You might have one structure for photographs and another for illustrations. However, try to avoid mixing and matching structures when captioning a single dataset.

#### Captions are Like Variables You Can Use in Your Prompts

Everything you describe in a caption can be thought of as a variable that you can manipulate in your prompt. This has two implications:

1. You want to describe as many details as possible about anything that isn’t the concept you are trying to implicitly teach. In other words, describe everything that you want to become a variable. For example, if you are teaching a specific face but want to be able to change the hair color, you should describe the hair color in each image so that "hair color" becomes one of your variables.

2. You don’t want to describe anything (beyond a class-level description) that you want to be implicitly taught. In other words, the thing you are trying to teach shouldn’t become a variable. For example, if you are teaching a specific face, you should not describe it as having a big nose. You don’t want the nose size to be variable because then it isn’t that specific face anymore. However, you can still caption "face" if you want, which provides context to the model you are training. This does have some implications described in the following point.

#### Leveraging Classes as Tags

* There are two concepts here.

1. Using generic class tags will bias that entire class toward your training data. This may or may not be desired depending on your goals.

2. Using generic class tags provides context to the learning process. Conceptually, it is easier to learn what a "face" is when the model already has a reasonable approximation of "face".

* If you want to bias the entire class of your model toward your training images, use broad class tags rather than specific ones. For example, if you want to teach your model that every man should look like Brad Pitt, your captions should contain the tag "man" but should not be more specific than that. This influences your model to produce a Brad Pitt-looking man whenever you use the word "man" in your prompt. This also allows your model to draw on and leverage what it already knows about the concept of "man" while it is training.

* If you want to reduce the impact of your training on the entire class, include specific tags and de-emphasize class tags. For example, if you want to teach your model that only "ohwxman" should look like Brad Pitt, and you don't want every "man" to look like Brad Pitt, you would not use "man" as a tag, only tagging it with "ohwxman". This reduces the impact of your training images on the tag "man" and strongly associates your training images with "ohwxman". Your model will draw on what it knows about "ohwxman", which is practically nothing (see note), thus building up knowledge almost solely from your training images, creating a very strong association.

* **Note:** This is simplified for the sake of understanding. This would actually be tokenized into two tokens, "ohwx" and "man", but these tokens would be strongly correlated for training purposes, which should still reduce the impact on the overall class of "man" compared to training with "man" as a token in the caption. The mathematics involved is quite complex and well beyond the scope of this document.

#### Consistent Captioning

* Use consistent captions across all of your training. This will help you consistently invoke your concept when prompting.

* Using inconsistent tags across your dataset will make the concept you are trying to teach harder for the model to grasp, as you are essentially forcing it to learn both the concept and the different phrasings for that concept. It’s much better to have it just learn the concept under a single term.

* For example, you probably don’t want to have both "legs raised in air" and "raised legs" if you are trying to teach a single concept of a person with their legs up in the air. You want to be able to consistently invoke this pose in your prompt, so choose one way to caption it.

#### Avoid Repetition

* Try to avoid repetition wherever possible. Similar to prompting, repeating words increases the weighting of those words.

* For example, if we repeat the word "background" too much, we may have three tags that say "background" (e.g., simple background, white background, lamp in background). Even though we want the background to have low weight, we have unintentionally increased the weighting significantly. It would be better to combine these or reword them (e.g., simple white background with a lamp).

#### Take Note of Ordering

* Again, just like with prompting, order matters for the relative weighting of tags.

* Having a specific structure or order that you generally use for captions can help you maintain the relative weightings of tags between images in your dataset, which should benefit the training process.

* Having a standardized ordering makes the whole captioning process faster as you become familiar with captioning in that structure.

#### Use Your Model's Existing Knowledge to Your Advantage

* Your model already produces decent results and reasonably understands what you are prompting. Take advantage of that by captioning with words that already work well in your prompts.

* You want to use descriptive words, but if you use words that are too obscure or niche, you likely can't leverage much of the existing knowledge. For example, you could say "sarcastic" or "mordacious". The model has some idea of what "sarcastic" conveys, but it likely has no clue what "mordacious" means.

* You can also look at this from the opposite perspective. If you were trying to teach the concept of "mordacious", you might have a dataset full of images that convey "sarcastic" and caption them with both the tags "sarcastic" and "mordacious" side by side (so that they are close in relative weighting).

### Captioning – Structure

This is mainly for people or characters, so it might not be quite as applicable to something like fantasy landscapes, but perhaps it can provide some inspiration.

#### General Format

#### Loose Associations

* This is where we put any final loose associations we have with the image.

* This could be anything that pops up in our head, usually “feelings” that we feel when looking at the image or concepts we feel are portrayed, really anything goes here as long as it exists in the image.

* Keep in mind this is for loose associations. If the image is very obviously portraying some feeling, we may want it tagged closer to the start of the caption for higher weighting.

* For example: happy, sad, joyous, hopeful, lonely, sombre

#### FULL EXAMPLE OF A SINGLE IMAGE

This is an example of how we would caption a single image we picked off of safebooru. We will assume that I want to train the style of this image and associate it with the tag "ohwxStyle", and we will assume that we have many images in this style within our dataset.

Sample Image: [https://safebooru.org/index.php?page=post\&s=view\&id=3887414](https://safebooru.org/index.php?page=post\&s=view\&id=3887414)

* Globals: ohwxStyle

* Type or Perspective Of a: anime, drawing, of a young woman, full body shot, from side

* Action words: sitting, looking at viewer, smiling, head tilt, holding a phone, eyes closed

* Subject description: short brown hair, pale pink dress with dark edges, stuffed animal in lap, brown slippers

* Notable details: sunlight through windows as lighting source

* Background or location: brown couch, red patterned fabric on couch, wooden floor, white water-stained paint on walls, refrigerator in background, coffee machine sitting on a countertop, table in front of couch, bananas and coffee pot on table, white board on wall, clock on wall, stuffed animal chicken on floor

* Loose associations: dreary environment

All together: ohwxStyle, anime, drawing, of a young woman, full body shot, from side, sitting, looking at viewer, smiling, head tilt, holding a phone, eyes closed, short brown hair, pale pink dress with dark edges, stuffed animal in lap, brown slippers, sunlight through windows as lighting source, brown couch, red patterned fabric on couch, wooden floor, white water-stained paint on walls, refrigerator in background, coffee machine sitting on a countertop, table in front of couch, bananas and coffee pot on table, white board on wall, clock on wall, stuffed animal chicken on floor, dreary environment


# API V2 to V3 Migration Guide
Source: https://docs.novita.ai/guides/model-apis-v2-to-v3-migration



## Text to Image

### Request Body Parameter Mapping

<table>
  <thead>
    <tr>
      <th>V2</th>
      <th>V3</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>**extra**<span>object</span></td>
      <td>**extra**<span>object</span></td>

      <td />
    </tr>

    <tr>
      <td>enable\_nsfw\_detection<br /><span>boolean</span></td>
      <td>enable\_nsfw\_detection<br /><span>boolean</span></td>

      <td />
    </tr>

    <tr>
      <td>nsfw\_detection\_level<br /><span>Enum: `0, 1, 2`</span></td>
      <td>nsfw\_detection\_level<br /><span>Enum: `0, 1, 2`</span></td>

      <td />
    </tr>

    <tr>
      <td>enable\_progress\_info</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>response\_image\_type<br /><span>Enum: `png`, `jpeg`</span></td>
      <td>response\_image\_type<br /><span>Enum: `png, webp, jpeg`</span></td>
      <td>V3 adds support for `webp`image format</td>
    </tr>

    <tr>
      <td />

      <td>**request**<span>object</span></td>
      <td><span>New Field</span><br />All image generation parameters must be passed via the `request`in V3</td>
    </tr>

    <tr>
      <td>**prompt**<span>string</span><br />\<lora:\$sd\_name:\$weight></td>
      <td>prompt<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td />

      <td>loras<span>object\[]</span></td>
      <td><span>Moved Inside</span><br />**Migrate LoRA usage: From **`prompt`** to **`request.loras`** parameter**</td>
    </tr>

    <tr>
      <td />

      <td>model\_name<span>string</span></td>
      <td><span>New Field</span><br />Name of lora, retrieve the corresponding sd\_name\_in\_api value by invoking the [Get Model API](https://novita.ai/docs/api-reference/model-apis-get-model) endpoint with filter.types=lora as the query parameter.</td>
    </tr>

    <tr>
      <td />

      <td>strength<span>number(float32)</span></td>
      <td><span>New Field</span><br />The strength value of lora. The larger the value, the more biased the effect is towards lora, Range \[0, 1]</td>
    </tr>

    <tr>
      <td>**negative\_prompt**<span>string</span></td>
      <td>negative\_prompt<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**sampler\_name**<span>string</span></td>
      <td>sampler\_name<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**batch\_size**<span>integer</span></td>
      <td>image\_num<span>integer</span></td>
      <td><span>Changed</span><br /> `num_images` **→** `request.image_num`</td>
    </tr>

    <tr>
      <td>**n\_iter**</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**steps**<span>string</span></td>
      <td>steps<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**cfg\_scale**<span>integer</span></td>
      <td>guidance\_scale<br /><span>number(float32)</span></td>
      <td><span>Changed</span><br /> `cfg_scale`**→** `request.guidance_scale`</td>
    </tr>

    <tr>
      <td>**seed**<span>integer</span></td>
      <td>seed<span>integer</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**height**<span>integer</span></td>
      <td>height<span>integer</span></td>
      <td><span>Moved Inside</span><br />Range Change: \[128, 2048].</td>
    </tr>

    <tr>
      <td>**width**<span>integer</span></td>
      <td>width<span>integer</span></td>
      <td><span>Moved Inside</span><br />Range Change: \[128, 2048].</td>
    </tr>

    <tr>
      <td>**model\_name**<span>string</span></td>
      <td>model\_name<span>string</span></td>
      <td><span>Moved Inside</span><br />This parameter specifies the name of the model checkpoint. Retrieve the corresponding sd\_name value by invoking the [Query Model](https://novita.ai/docs/api-reference/model-apis-get-model) API with filter.types=checkpoint as the query parameter.</td>
    </tr>

    <tr>
      <td>**restore\_faces**<span>bool</span></td>
      <td>restore\_faces<span>bool</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**restore\_faces\_model**</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**sd\_vae**<span>string</span></td>
      <td>sd\_vae<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**clip\_skip**<span>integer</span></td>
      <td>clip\_skip<span>integer</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**enable\_hr**<span>boolean</span></td>
      <td>hires\_fix<span>object</span></td>
      <td><span>Changed</span><br /> `enable_hr`**→** `request.hires_fix`</td>
    </tr>

    <tr>
      <td>**hr\_upscaler**<br /><span>Enum:<br /> `Latent`, `ESRGAN_4x`, `R-ESRGAN 4x+`, `R-ESRGAN 4x+ Anime6B`</span></td>
      <td>upscaler<br /><span>Enum: `RealESRGAN_x4plus_anime_6B`, `RealESRNet_x4plus,Latent`</span></td>
      <td><span>Changed</span><br /> `hr_upscaler`**→**`request.hires_fix.upscaler`</td>
    </tr>

    <tr>
      <td>**hr\_scale**<span>number</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**hr\_resize\_x**<span>integer</span></td>
      <td>target\_width<span>integer</span></td>
      <td><span>Changed</span><br /> `hr_resize_x`**→** `request.hires_fix.target_width`</td>
    </tr>

    <tr>
      <td>**hr\_resize\_y**<span>integer</span></td>
      <td>target\_height<span>integer</span></td>
      <td><span>Changed</span><br /> `hr_resize_y`**→** `request.hires_fix.target_height`</td>
    </tr>

    <tr>
      <td>**img\_expire\_ttl**<span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Default 3600s</td>
    </tr>

    <tr>
      <td>**sd\_refiner**<span>object</span></td>
      <td>refiner<span>object</span></td>
      <td><span>Changed</span><br /> `sd_refiner`**→** `request.refiner`</td>
    </tr>

    <tr>
      <td>checkpoint<span>string</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>switch\_at<br /><span>number(float32)</span></td>
      <td>switch\_at<br /><span>number(float32)</span></td>
      <td><span>Changed</span><br /> `sd_refiner.switch_at`**→** `request.refiner.switch_at`</td>
    </tr>

    <tr>
      <td>**controlnet\_units**<span>object\[]</span></td>

      <td />

      <td><span>Deprecated</span><br /><span>`img2img` Only</span></td>
    </tr>
  </tbody>
</table>

### Response Parameter Mapping

<table>
  <thead>
    <tr>
      <th>V2</th>
      <th>V3</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>**code**</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**msg**</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**data**</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>task\_id</td>
      <td>**task\_id**</td>
      <td><span>Changed</span><br /> `data.task_id`**→** `task_id`</td>
    </tr>

    <tr>
      <td>warn</td>

      <td />

      <td>Deprecated</td>
    </tr>
  </tbody>
</table>

### Example

<CardGroup>
  <Card title="Text to Image V2">
    * Request

    ```bash theme={"system"}
    curl --location "https://api.novita.ai/v2/txt2img" \
    --header "Authorization: Bearer {{API Key}}" \
    --header "Content-Type: application/json" \
    --data '{
      "extra": {
        "enable_nsfw_detection": false,
        "nsfw_detection_level": 0,
        "response_image_type": "jpeg"
      },
      "prompt": "(masterpiece, best quality, ultra-detailed:1.3), <lora:AnimeStyle_XL_v1_AutoRunMech_103883:0.8>, 1girl, mecha armor, futuristic city background, glowing neon lights, dynamic pose, cyberpunk style, intricate mechanical details, (floating holographic interface:1.2), sparkling blue eyes, wind-blown hair, (steam and energy particles:1.1), cinematic lighting",
      "negative_prompt":"(worst quality, low quality:1.4), (deformed, distorted:1.3), disfigured, blurry, bad anatomy, extra limbs, (mutated hands:1.2), (text, watermark:1.5), 
    overexposed, underexposed, (cartoonish:1.2), (simplistic background:1.3), 
    grainy, (traditional drawing:1.2), (realistic:1.4), (noisy:1.3)",
      "sampler_name":"DPM++ 2M Karras",
      "batch_size": 1,
      "n_iter": 1,
      "steps": 25,
      "cfg_scale": 7,
      "seed": 65531,
      "height": 512,
      "width": 512,
      "model_name": "sd_xl_base_1.0.safetensors",
      "restore_faces": false,
      "restore_faces_model": "",
      "sd_vae": "",
      "clip_skip": 1,
      "enable_hr": true,
      "hr_upscaler": "Latent",
      "hr_scale": 2,
      "hr_resize_x": 1024,
      "hr_resize_y": 1024,
      "img_expire_ttl": 3600
    }'
    ```

    * Response

    ```
    {
      "code": 0,
      "msg": "",
      "data": {
        "task_id": "12905b6a-d436-4010-b199-d800130c0aab"
      }
    }
    ```
  </Card>

  <Card title="Text to Image V3">
    * Reqeust

    ```bash theme={"system"}
    curl --location 'https://api.novita.ai/v3/async/txt2img' \
    --header 'Authorization: Bearer {{API Key}}' \
    --header 'Content-Type: application/json' \
    --data '{
        "extra": {
            "enable_nsfw_detection": false,
            "nsfw_detection_level": 0,
            "response_image_type": "jpeg"
        },
        "request": {
          "prompt": "(masterpiece, best quality, ultra-detailed:1.3), 1girl, mecha armor, futuristic city background, glowing neon lights, dynamic pose, cyberpunk style, intricate mechanical details,(floating holographic interface:1.2), sparkling blue eyes, wind-blown hair, (steam and energy particles:1.1), cinematic lighting",
          "negative_prompt": "(worst quality, low quality:1.4), (deformed, distorted:1.3), disfigured,blurry, bad anatomy, extra limbs, (mutated hands:1.2), (text, watermark:1.5),overexposed, underexposed,(cartoonish:1.2), (simplistic background:1.3), grainy, (traditional drawing:1.2),(realistic:1.4), (noisy:1.3)",
            "model_name": "sd_xl_base_1.0.safetensors",
            "width": 512,
            "height": 512,
            "image_num": 1,
            "steps": 25,
            "seed": 65531,
            "restore_faces": false,
            "clip_skip": 1,
            "sampler_name":"DPM++ 2M Karras",
            "guidance_scale": 7,
            "hires_fix": {
                "target_width": 1024,
                "target_height": 1024,
                "strength": 0.8,
                "upscaler": "Latent"
            },
            "loras": [
              {
                "model_name": "AnimeStyle_XL_v1_AutoRunMech_103883",
                "strength": 0.8
              }
            ]
        }
    }'
    ```

    * Repspone

    ```bash theme={"system"}
    {
      "task_id":"0696ce57-22df-4a89-9efc-80c386a82dbd"
    }
    ```
  </Card>
</CardGroup>

## Image to Image

### Request Body Parameter Mapping

<table>
  <thead>
    <tr>
      <th>V2</th>
      <th>V3</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>**extra**<span>object</span></td>
      <td>**extra**<span>object</span></td>

      <td />
    </tr>

    <tr>
      <td>enable\_nsfw\_detection<br /><span>boolean</span></td>
      <td>enable\_nsfw\_detection<br /><span>boolean</span></td>

      <td />
    </tr>

    <tr>
      <td>nsfw\_detection\_level<br /><span>Enum: `0, 1, 2`</span></td>
      <td>nsfw\_detection\_level<br /><span>Enum: `0, 1, 2`</span></td>

      <td />
    </tr>

    <tr>
      <td>enable\_progress\_info</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>response\_image\_type<br /><span>Enum: `png`, `jpeg`</span></td>
      <td>response\_image\_type<br /><span>Enum: `png, webp, jpeg`</span></td>
      <td>V3 adds support for `webp`image format</td>
    </tr>

    <tr>
      <td />

      <td>**request**<span>object</span></td>
      <td><span>New field</span><br />All image generation parameters must be passed via the `request`in V3</td>
    </tr>

    <tr>
      <td>**prompt**<span>string</span><br />\<lora:\$sd\_name:\$weight></td>
      <td>prompt<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td />

      <td>loras<span>object\[]</span></td>
      <td><span>Moved Inside</span><br />**Migrate LoRA usage: From **`prompt`** to **`request.loras`** parameter**</td>
    </tr>

    <tr>
      <td />

      <td>model\_name<span>string</span></td>
      <td><span>New Field</span><br />Name of lora, retrieve the corresponding sd\_name\_in\_api value by invoking the [Get Model API](https://novita.ai/docs/api-reference/model-apis-get-model) endpoint with filter.types=lora as the query parameter.</td>
    </tr>

    <tr>
      <td />

      <td>strength<br /><span>number(float32)</span></td>
      <td><span>New Field</span><br />The strength value of lora. The larger the value, the more biased the effect is towards lora, Range \[0, 1]</td>
    </tr>

    <tr>
      <td>**negative\_prompt**<span>string</span></td>
      <td>negative\_prompt<br /><span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**sampler\_name**<span>string</span></td>
      <td>sampler\_name<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**batch\_size**<span>integer</span></td>
      <td>image\_num<span>integer</span></td>
      <td><span>Changed</span><br /> `batch_size`**→** `request.image_num`</td>
    </tr>

    <tr>
      <td>**n\_iter**<span>integer</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**steps**<span>string</span></td>
      <td>steps<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**cfg\_scale**<span>integer</span></td>
      <td>guidance\_scale<br /><span>number(float32)</span></td>
      <td><span>Changed</span><br /> `cfg_scale`**→** `request.guidance_scale`</td>
    </tr>

    <tr>
      <td>**seed**<span>integer</span></td>
      <td>seed<span>integer</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**height**<span>integer</span></td>
      <td>height<span>integer</span></td>
      <td><span>Moved Inside</span><br />Range Change: \[128, 2048].</td>
    </tr>

    <tr>
      <td>**width**<span>integer</span></td>
      <td>width<span>integer</span></td>
      <td><span>Moved Inside</span><br />Range Change: \[128, 2048].</td>
    </tr>

    <tr>
      <td>**model\_name**<span>string</span></td>
      <td>model\_name<span>string</span></td>
      <td><span>Moved Inside</span><br />This parameter specifies the name of the model checkpoint. Retrieve the corresponding sd\_name value by invoking the [Query Model](https://novita.ai/docs/api-reference/model-apis-get-model) API with filter.types=checkpoint as the query parameter.</td>
    </tr>

    <tr>
      <td>**init\_images**<span>string\[]</span></td>
      <td>image\_base64<span>string</span></td>
      <td><span>Changed</span><br /> `init_images`**→** `request.image_base64`</td>
    </tr>

    <tr>
      <td>**denoising\_strength**<br /><span>number(float)</span></td>
      <td>strength<br /><span>number(float)</span></td>
      <td><span>Changed</span><br /> `denoising_strength`**→** `request.strength`</td>
    </tr>

    <tr>
      <td>**restore\_faces**<span>bool</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**sd\_vae**<span>string</span></td>
      <td>sd\_vae<span>string</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**clip\_skip**<span>integer</span></td>
      <td>clip\_skip<span>integer</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>**mask**<span>string</span></td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>**mask\_blur**<span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>**resize\_mode**<span>integer</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**image\_cfg\_scale**<span>integer</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**inpainting\_fill**<span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>**inpaint\_full\_res**<span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>**inpaint\_full\_res\_padding**<br /><span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>**inpainting\_mask\_invert**<br /><span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>**initial\_noise\_multiplier**<br /><span>number(float32)</span></td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>**img\_expire\_ttl**<span>integer</span></td>

      <td />

      <td><span>Deprecated</span><br />Default 3600s</td>
    </tr>

    <tr>
      <td>**sd\_refiner**<span>object</span></td>
      <td>refiner<span>object</span></td>
      <td><span>Changed</span><br /> `sd_refiner`**→** `request.refiner`</td>
    </tr>

    <tr>
      <td>checkpoint</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>switch\_at<br /><span>number(float32)</span></td>
      <td>switch\_at<br /><span>number(float32)</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td />

      <td>controlnet<span>object</span></td>
      <td><span>New Field</span></td>
    </tr>

    <tr>
      <td>**controlnet\_units**<span>object\[]</span></td>
      <td>units<span>object\[]</span></td>
      <td><span>Changed</span><br /> `controlnet_units`**→** `request.controlnet.units`</td>
    </tr>

    <tr>
      <td>model<span>string</span></td>
      <td>model\_name<br /><span>string</span></td>
      <td><span>Changed</span><br /> `controlnet_units.model`**→** <br />`request.controlnet.units.model_name`</td>
    </tr>

    <tr>
      <td>weight<span>number</span></td>
      <td>strength<br /><span>number(float32)</span></td>
      <td><span>Changed</span><br /> `controlnet_units.weight`**→** <br />`request.controlnet.units.strength`</td>
    </tr>

    <tr>
      <td>input\_image<span>string</span></td>
      <td>image\_base64<br /><span>string</span></td>
      <td><span>Changed</span><br /> `controlnet_units.input_image`**→** <br />`request.controlnet.units.image_base64`</td>
    </tr>

    <tr>
      <td>module<span>string,Enum</span></td>
      <td>preprocessor<br /><span>string,Enum</span></td>
      <td><span>Changed</span><br />`controlnet_units.module`**→** <br />`request.controlnet.units.preprocessor`</td>
    </tr>

    <tr>
      <td>control\_mode</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>mask</td>

      <td />

      <td><span>Deprecated</span><br />Recommendation: Use V3 Inpainting API</td>
    </tr>

    <tr>
      <td>resize\_mode</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>processor\_res</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>threshold\_a</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>threshold\_b</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>guidance\_start<br /><span>number(float32)</span></td>
      <td>guidance\_start<br /><span>number(float32)</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>guidance\_end<br /><span>number(float32)</span></td>
      <td>guidance\_end<br /><span>number(float32)</span></td>
      <td>Moved Inside</td>
    </tr>

    <tr>
      <td>pixel\_perfect</td>

      <td />

      <td>Deprecated</td>
    </tr>
  </tbody>
</table>

### Response Parameter Mapping

<table>
  <thead>
    <tr>
      <th>V2</th>
      <th>V3</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>code</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>msg</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>data</td>

      <td />

      <td>Deprecated</td>
    </tr>

    <tr>
      <td>task\_id</td>
      <td>**task\_id**</td>
      <td><span>Changed</span><br /> `data.task_id`**→** `task_id`</td>
    </tr>

    <tr>
      <td>warn</td>

      <td />

      <td>Deprecated</td>
    </tr>
  </tbody>
</table>

### Example

<CardGroup>
  <Card title="Image to Image V2">
    * Request

    ```
    curl --request POST \
      --url https://api.novita.ai/v2/img2img \
      --header 'Authorization: <authorization>' \
      --header 'Content-Type: application/json' \
      --data '{
      "extra": {
        "enable_nsfw_detection": false,
        "nsfw_detection_level": 0,
        "response_image_type": "png"
      },
      "prompt": "realistic, photograph, (masterpiece), 8k quality, (detailed eyes:1.2), (highest quality:1.1), highly detailed, majestic, top quality, best quality, newest, ai-generated, (intricate details:1.1), extremely beautiful, elegant, majestic, immersive background+, (detailed face, perfect face), In the heart of a vibrant garden, a girl with red hair and brown eyes sits in contemplation of the nature around her.",
      "negative_prompt": "(worst quality:1.5), (low quality:1.5), (normal quality:1.5), anime, cartoon, painting, drawing, illustration, manga, sketch, nudity, young, child, hairband, headband, horns, lowres, bad anatomy, bad hands, multiple eyebrow, (cropped), extra limb, missing limbs, deformed hands, long neck, long body, long torso, (bad hands), signature, username, artist name, conjoined fingers, deformed fingers, ugly eyes, imperfect eyes, skewed eyes, unnatural face, unnatural body, error, grain, jpeg artifacts",
      "sampler_name":"DPM++ 2M Karras",
      "batch_size": 1,
      "n_iter": 1,
      "steps": 25,
      "cfg_scale": 7.5,
      "seed": 765824,
      "height": 768,
      "width": 512,
      "model_name": "cyberrealistic_v31_62396.safetensors",
      "init_images": [
        "<IMAGE_BASE64>"
      ],
      "denoising_strength": 0.8,
      "restore_faces": false,
      "clip_skip": 1,
      "img_expire_ttl": 3600,
      "controlnet_units": {
        "model": "control_v11p_sd15_lineart",
        "weight": 1,
        "input_image": "<IMAGE_BASE64>",
        "module": "lineart",
        "guidance_start": 0,
        "guidance_end": 1
      }
    }'
    ```

    * Response

    ```
    {
      "code": 0,
      "msg": "",
      "data": {
        "task_id": "a1b0d99b-2ef0-4b20-8eeb-c641ae84ed45"
      }
    }
    ```
  </Card>

  <Card title="Image to Image V3">
    * Request

    ```
    curl --request POST \
      --url https://api.novita.ai/v3/async/img2img \
      --header 'Authorization: <authorization>' \
      --header 'Content-Type: <content-type>' \
      --data '{
      "extra": {
        "enable_nsfw_detection": false,
        "nsfw_detection_level": 0,
        "response_image_type": "png"
      },
      "request": {
        "prompt": "realistic, photograph, (masterpiece), 8k quality, (detailed eyes:1.2), (highest quality:1.1), highly detailed, majestic, top quality, best quality, newest, ai-generated, (intricate details:1.1), extremely beautiful, elegant, majestic, immersive background+, (detailed face, perfect face), In the heart of a vibrant garden, a girl with red hair and brown eyes sits in contemplation of the nature around her.",
        "negative_prompt": "(worst quality:1.5), (low quality:1.5), (normal quality:1.5), anime, cartoon, painting, drawing, illustration, manga, sketch, nudity, young, child, hairband, headband, horns, lowres, bad anatomy, bad hands, multiple eyebrow, (cropped), extra limb, missing limbs, deformed hands, long neck, long body, long torso, (bad hands), signature, username, artist name, conjoined fingers, deformed fingers, ugly eyes, imperfect eyes, skewed eyes, unnatural face, unnatural body, error, grain, jpeg artifacts",
        "sampler_name": "DPM++ 2M Karras",
        "image_num": 1,
        "steps": 25,
        "guidance_scale": 7.5,
        "seed": 765824,
        "height": 768,
        "width": 512,
        "model_name": "cyberrealistic_v31_62396.safetensors",
            "image_base64": "<IMAGE_BASE64>",
        "strength": 0.8,
        "clip_skip": 1,
        "controlnet": {
          "units": [
            {
              "model_name": "control_v11p_sd15_lineart",
              "strength": 1,
              "image_base64": "<IMAGE_BASE64>",
              "preprocessor": "lineart",
              "guidance_start": 0,
              "guidance_end": 1
            }
          ]
        }
      }
    }'
    ```

    * Response

    ```bash theme={"system"}
    {
      "task_id": "9490dde7-cd28-493e-9db0-8a38a65bf5e6"
    }
    ```
  </Card>
</CardGroup>


# What's the Variational Autoencoder VAE?
Source: https://docs.novita.ai/guides/model-apis-vae



### Introduction to Autoencoders

Autoencoders are a class of neural networks primarily used for unsupervised learning and dimensionality reduction. The fundamental idea behind autoencoders is to encode input data into a lower-dimensional representation and then decode it back to the original data, aiming to minimize the reconstruction error. The basic architecture of an autoencoder consists of two main components: the encoder and the decoder.

* **Encoder**: The encoder is responsible for transforming the input data into a compressed or latent representation. It typically consists of one or more layers of neurons that progressively reduce the dimensions of the input.

* **Decoder**: The decoder, on the other hand, takes the compressed representation produced by the encoder and attempts to reconstruct the original input data. Like the encoder, it often consists of one or more layers, but in reverse order, gradually increasing the dimensions.

<Frame>
  <img />
</Frame>

### Variational Autoencoders (VAEs) Overview

Variational Autoencoders (VAEs) address some of the limitations of traditional autoencoders by introducing a probabilistic approach to encoding and decoding. The motivation behind VAEs lies in their ability to generate new data samples by sampling from a learned distribution in the latent space, rather than from a fixed latent vector as was the case with vanilla autoencoders. This makes them suitable for generative tasks.

* **Probabilistic Nature**: Unlike deterministic autoencoders, VAEs model the latent space as a probability distribution. This produces a probability distribution function over the input encodings instead of just a single fixed vector, allowing for a more nuanced representation of uncertainty in the data. The decoder then samples from this probability distribution.

* **Role of Latent Space**: The latent space in VAEs serves as a continuous, structured representation of the input data. Since it is continuous by design, this allows for easy interpolation. Each point in the latent space corresponds to a potential output, enabling smooth transitions between different data points and ensuring that points closer in the latent space lead to similar generations.

The concept can be elucidated through a straightforward example, as presented below. Encoders within a neural network are tasked with acquiring a representation of input images in the form of a vector. This vector encapsulates various features such as a subject’s smile, hair color, gender, age, etc., denoted as a vector similar to \[0.4, 0.03, 0.032, …]. In this illustration, the focus is narrowed to a singular latent representation, specifically the attribute of a "smile".

Autoencoders vs VAEs - Sciforce MediumIn the context of Vanilla Autoencoders (AE), the smile feature is encapsulated as a fixed, deterministic value. In contrast, Variational Autoencoders (VAEs) are deliberately crafted to encapsulate this feature as a probabilistic distribution. This design choice facilitates the introduction of variability in generated images by enabling the sampling of values from the specified probability distribution.

<Frame>
  <img />
</Frame>

In summary, VAEs go beyond mere data reconstruction; they generate new samples and provide a probabilistic framework for understanding latent representations. The inclusion of probabilistic elements in the model’s architecture sets VAEs apart from traditional autoencoders. Compared to traditional autoencoders, VAEs offer a richer understanding of the data distribution, making them particularly powerful for generative tasks.


# Novita Connect
Source: https://docs.novita.ai/guides/novita-connect

Run Claude Code, Codex, OpenCode, Pi and DeepSeek Harness on Novita models with one command.

Novita Connect plugs Novita models into the coding harness you already use. Keep your tools, workflow and shortcuts; switch the model underneath with one command, and switch back by simply running your harness as usual.

<Frame>
  <video>
    <source type="video/webm" />

    <source type="video/mp4" />

    <img alt="Novita Connect connects coding harnesses to Novita models" />
  </video>
</Frame>

The CLI is called `novi`. It starts your harness with Novita settings injected for that session only, so your own configuration is left untouched.

<Note>
  Novita Connect is currently in **Beta**. Commands and the default model lineup may change. It runs on macOS and Linux (amd64 and arm64).
</Note>

## Requirements

* A Novita API key from [Key Management](https://novita.ai/settings/key-management).
* macOS or Linux with `curl`.
* The harness you want to use. If it isn't installed, the `novi` menu shows installation instructions and an official download link.

## Quickstart

<Steps>
  <Step title="Install novi">
    ```bash theme={"system"}
    curl -fsSL https://novita.ai/connect/install.sh | sh
    ```

    The installer puts `novi` in `~/.local/bin`, verifies the download against `SHA256SUMS`, and installs the latest release.
  </Step>

  <Step title="Log in with your Novita API key">
    ```bash theme={"system"}
    novi login
    ```

    Paste a key from [Key Management](https://novita.ai/settings/key-management). You can also set `NOVITA_API_KEY` instead of saving a key.
  </Step>

  <Step title="Start a harness">
    ```bash theme={"system"}
    novi claude
    ```

    Claude Code starts on Novita models. Run `novi` on its own to open the interactive menu and pick a harness, browse models, or check your balance.
  </Step>
</Steps>

### Installer options

| Variable | Effect |
| - | - |
| `NOVI_INSTALL_DIR` | Install somewhere other than `~/.local/bin` |
| `NOVI_VERSION=x.y.z` | Pin a specific release instead of the latest |

```bash theme={"system"}
curl -fsSL https://novita.ai/connect/install.sh | NOVI_VERSION=0.3.0 sh
```

## Supported harnesses

| Harness | How to start | Your config |
| - | - | - |
| Claude Code | `novi claude` | Untouched |
| Codex | `novi codex` | Untouched |
| OpenCode | `novi opencode` | Per-process provider |
| Pi | `novi pi` | Temporary provider extension |
| DeepSeek Harness | `novi dsh` | Temporary patch overlay |
| Claude Desktop (Beta) | Menu → Launch desktop app | Long-term setup; Disconnect restores |
| Codex Desktop (Beta) | Menu → Launch / One-time use | Long-term setup or isolated instance |

`--model` and `--print-env` go right after the harness name; everything else is passed through to the harness:

```bash theme={"system"}
novi codex --model zai-org/glm-5.3
novi claude --print-env   # show what would be injected, secrets masked
```

OpenCode, Pi and DeepSeek Harness use Novita Chat Completions through the same local relay. OpenCode and Pi keep their original providers and model selections alongside Novita, and all generated provider files are removed on exit. `novi dsh` starts the web profile; use `novi dsh headless "task"` or `novi dsh --profile <name>` to choose another profile.

<AccordionGroup>
  <Accordion title="Claude Code and an existing claude.ai login">
    Claude Code preserves an existing claude.ai login and custom model picker; otherwise it uses the relay token. Its default model and tier aliases point to Novita during the session. Preserving the login does not provide automatic Anthropic subscription passthrough.
  </Accordion>

  <Accordion title="Codex and an existing ChatGPT login">
    When Codex's `auth.json` contains an access token, its original models and Novita models share the picker. Original model requests go to OpenAI with the client's credential; Novita requests use only the Novita key. Without a ChatGPT login, `novi` uses a standalone Novita provider. A login stored only in a system keychain is not yet detected.
  </Accordion>

  <Accordion title="Harness version requirements">
    DeepSeek Harness requires a version with `--patch` support (verified with 0.2.0-rc.2). Pi requires its provider extension API. OpenCode 2 runs with a private server so it inherits the session configuration.
  </Accordion>
</AccordionGroup>

## Models

Every Novita model the harness can use appears in its own model picker (`/model`), with prices and context sizes. The default lineup:

| Role | Model |
| - | - |
| Main | `moonshotai/kimi-k3` |
| Mid | `zai-org/glm-5.3` |
| Background tasks | `deepseek/deepseek-v4.1-flash` |

Pick another model for a session with `--model <id>`, and list everything available with prices:

```bash theme={"system"}
novi models
```

Tools, MCP servers, subagents and web search keep working on Novita models, and so does Codex's tool search. Web search runs through [Novita AI Search](/guides/ai-search-quickstart) and is billed to your account; set `NOVI_NO_WEB_SEARCH=1` to turn it off.

## How your key stays safe

* **Your key stays with `novi`.** Each session runs through a local relay on `127.0.0.1`. The harness only gets a session token that works there, so neither the harness nor anything it runs can read your Novita API key.
* **Nothing is left behind.** CLI sessions inject everything into the harness process. Settings a harness saves on its own during the session (model, effort) are put back when it exits.

## Desktop apps (Beta)

On macOS, `novi` can connect Claude Desktop and Codex Desktop to Novita. Open the `novi` menu and choose the app, or run:

```bash theme={"system"}
novi codex-desktop     # launch; first use saves long-term setup (macOS)
novi claude-desktop    # same launch behavior for Claude Desktop (macOS)
```

* **Launch desktop app** opens the app with a long-term Novita setup, and reuses an existing setup instead of reconfiguring every time. The setup keeps one background relay on `127.0.0.1:17861` for both apps.
* **One-time use** (Codex Desktop only, experimental) starts an isolated instance with its own `CODEX_HOME` and app data. Your original configuration stays untouched; you may need to complete first-run setup.
* **Disconnect / restore settings** (press **m** in the menu for connection management) puts back what `novi` changed and keeps your later edits. Every change is journaled.

<Warning>
  Claude Desktop runs in third-party gateway mode with a Novita profile. Cloud sessions and Remote Control are unavailable in this mode, and earlier Anthropic conversations stay in the original data directory. Use **Switch to official Claude** to go back while keeping the Novita profile for later.
</Warning>

On Linux, use **Connect / start** in the menu, then quit and reopen the app yourself. Run `novi desktop status` for desktop app and connection diagnostics.

## Usage receipts

CLI sessions print a receipt on stderr when they exit, without changing the agent's stdout. To summarize locally observed input, output and cache tokens by model:

```bash theme={"system"}
novi usage --last 7d
```

Add `--json` for request-level records. Usage metadata is stored privately under `~/.config/novi/usage`; no prompts, response text, credentials or working directories are recorded.

<Note>
  Totals are estimates based on the catalog price at request time, not your actual Novita bill, and exclude search and other charges. Check your [billing page](/guides/llm-billing) for actual charges.
</Note>

## Commands

| Command | Description |
| - | - |
| `novi` | Interactive menu |
| `novi login` | Save your Novita API key (or set `NOVITA_API_KEY`) |
| `novi claude [--model M] [args…]` | Start Claude Code on Novita |
| `novi codex [--model M] [args…]` | Start Codex on Novita |
| `novi opencode [--model M] [args…]` | Start OpenCode on Novita |
| `novi pi [--model M] [args…]` | Start Pi on Novita |
| `novi dsh [--model M] [args…]` | Start DeepSeek Harness on Novita |
| `novi models` | Model list with prices |
| `novi doctor` | Key, connectivity, harness versions and updates |
| `novi usage [--last 7d] [--json]` | Local usage history (estimated model cost) |
| `novi update --check` | Check for a new `novi` release |
| `novi update` | Verified in-place update to the latest release |
| `novi desktop status` | Desktop app and connection diagnostics |
| `novi desktop disconnect --all` | Restore both desktop connections |
| `novi uninstall [--yes]` | Remove `novi` and everything it added |
| `novi <harness> --print-env` | Show what would be injected (secrets masked) |
| `novi version` | Print the installed version |

## Environment variables

| Variable | Effect |
| - | - |
| `NOVITA_API_KEY` | Key to use instead of the saved one |
| `NOVI_NO_WEB_SEARCH=1` | Turn off web search |
| `NOVI_NO_UPDATE_CHECK=1` | Don't look up the latest harness versions |
| `NOVI_NPM_REGISTRY` | npm registry mirror for version lookups |
| `NOVI_CODEX_APPLY_PATCH=freeform` | Experimental: Codex's freeform `apply_patch` tool |
| `NOVI_NO_ANIMATION=1` / `NO_COLOR=1` | Static menu |
| `NOVI_NOVITA_DIRECT=1` | Bypass your proxy for `api.novita.ai` only |
| `NOVI_RELAY_DUMP=<file>` | Debug: log relay requests (never credentials) |

## Update and uninstall

```bash theme={"system"}
novi update
```

`novi update` downloads the latest release, verifies its checksum and version, then replaces `novi` in place. A running desktop relay is restarted on the new binary. To go back, reinstall a specific version with `NOVI_VERSION=x.y.z` and the installer.

```bash theme={"system"}
novi uninstall
```

Uninstall restores desktop connections, then removes `novi` and `~/.config/novi/`. Your harness configs and session history stay. Usage history and recovery backups are preserved; remove them separately if you no longer need them.

## Known limitations

* Tested with Claude Code 2.1.283–2.1.295 and Codex 0.153.4–0.162.0. `novi doctor` flags older versions.
* Codex conversations started through `novi` can't be resumed with plain `codex resume`.
* Codex's own sandbox needs unprivileged user namespaces, which Ubuntu 24.04 blocks by default.
* Claude Desktop gateway mode does not use Anthropic sign-in; Remote Control and other features that require the official endpoint are unavailable.
* macOS and Linux only.

## FAQ

<AccordionGroup>
  <Accordion title="Does Novita Connect change my harness configuration?">
    No. CLI sessions inject Novita settings into the harness process only, and settings the harness saves on its own during the session are put back when it exits. To use your harness without Novita, run it as usual (for example `claude` instead of `novi claude`).
  </Accordion>

  <Accordion title="Can the harness see my Novita API key?">
    No. The harness talks to a local relay on `127.0.0.1` using a session token that only works there. Your API key stays with `novi`.
  </Accordion>

  <Accordion title="How do I use a different model?">
    Pass `--model <id>` right after the harness name, for example `novi claude --model zai-org/glm-5.3`, or switch inside the harness with `/model`. Run `novi models` to see every available model with prices.
  </Accordion>

  <Accordion title="My proxy can't reach Novita. What can I do?">
    If direct access works, run `NOVI_NOVITA_DIRECT=1 novi` to bypass the proxy for `api.novita.ai` only. Set `"novita_direct": true` in `novi`'s `config.json` to persist this, including for the background desktop relay.
  </Accordion>

  <Accordion title="Something isn't working. Where do I start?">
    Run `novi doctor`. It checks your key, connectivity, harness versions and updates, and includes desktop app paths, configuration paths and connection status.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup>
  <Card title="Claude Code" icon="terminal" href="/guides/claude-code">
    Configure Claude Code manually with Novita's Anthropic-compatible API.
  </Card>

  <Card title="Codex" icon="terminal" href="/guides/codex">
    Configure Codex manually with Novita's OpenAI-compatible API.
  </Card>
</CardGroup>


# NovitaClaw Overview
Source: https://docs.novita.ai/guides/novitaclaw



Deploy a persistent OpenClaw agent on Novita Sandbox with one command, then manage it from CLI, Web UI, or automation scripts. No session cap — your instance runs 24/7 for as long as you need it.

***

## What's New in v1.6.0

* **Gateway Config Export / Import** — Back up, restore, and migrate your OpenClaw configuration across sandboxes. Export to stdout or file, import with automatic JSON validation and Gateway restart. Supports cross-sandbox migration workflows.

```bash CLI icon="terminal" theme={"system"}
# Export config
novitaclaw gateway config export -o config.json
# Import into another sandbox
novitaclaw gateway config import config.json
```

* **ClawHub Skill Install** — Install OpenClaw extension skills from ClawHub directly into a sandbox. Skills are available immediately with no Gateway restart required.

```bash CLI icon="terminal" theme={"system"}
novitaclaw skill install sonoscli
```

### Previous Releases

* **Telegram Channel (CLI)** — Polling and Webhook modes for connecting your agent to Telegram.
* **Slack Channel** — Socket and HTTP modes for connecting your agent to Slack.
* **Install Script Improvements** — Install directory changed to `~/.novitaclaw-cli`, improved `update` command with PEP 668 support and symlink recovery.
* **On-Demand Mode** — Sandbox auto-pauses after idle, resumes on access. Zero billing while paused.
* **Feishu Channel** — Connect your agent to Feishu (Lark) via Webhook or Event mode.
* **Web Terminal** — Browser-based terminal access to your sandbox, no SSH required.
* **File Manager** — Browse and manage workspace files directly from the browser.
* **systemd Service Management** — Gateway, Web Terminal, and File Manager are fully managed by systemd with automatic restart on failure.
* **Crash Auto-Recovery** — Automatic diagnostics, repair, and service restoration on repeated crashes.
* **Config Auto-Backup** — Every config write creates an automatic backup; crash recovery restores from the latest known-good backup.

***

## Getting Started

### Prerequisites

* Python installed
* A Novita API key (create/manage keys in [Key Management](https://novita.ai/settings/key-management))

**How to install Python**

**Windows:** Download the Python installer and run it. **Check "Add Python to PATH"** before clicking Install Now — skipping this is the most common reason beginners hit errors later.

**macOS:** Open Terminal and run:

```bash CLI icon="terminal" theme={"system"}
python3 --version
```

If you see Python 3.10 or higher, you're good. If Terminal prompts you to install Command Line Developer Tools, click Install and wait a few minutes.

**Linux (Ubuntu / Debian):**

```bash CLI icon="terminal" theme={"system"}
sudo apt update && sudo apt install python3 python3-pip -y
```

### Step 1: Install NovitaClaw

```bash CLI icon="terminal" theme={"system"}
sudo pip3 install novitaclaw
```

```powershell PowerShell icon="terminal" theme={"system"}
pip install novitaclaw
```

After installation, verify by running `novitaclaw --help`. If you see a list of commands, you're all set.

### Step 2: Set your API key

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=sk_your_api_key
```

```powershell PowerShell icon="terminal" theme={"system"}
$env:NOVITA_API_KEY = "sk_your_api_key"
```

### Step 3: Launch your instance

```bash CLI icon="terminal" theme={"system"}
novitaclaw launch
```

On success, the CLI returns:

* **Web UI URL** — Chat with your agent
* **Gateway WebSocket URL & Token** — For programmatic access
* **Web Terminal URL** — Terminal access to the sandbox in your browser
* **File Manager URL** — Browse and manage workspace files
* **Login credentials** — Username and password for Web Terminal & File Manager

Open the returned **Web UI URL** and go to the `Chat` tab to start using your agent.

### Web Terminal

The Web Terminal provides browser-based terminal access to your sandbox environment. Open the **Web Terminal URL** returned by the launch command and log in with the provided credentials. This gives you a full shell session — install packages, inspect logs, or debug your agent directly.

### File Manager

The File Manager lets you browse, upload, download, and manage files in the sandbox workspace through a web interface. Open the **File Manager URL** and log in with the same credentials. Use it to view agent workspace files, edit configurations, or transfer files without needing CLI access.

***

## Pricing

| What you use | What you pay |
| - | - |
| Sandbox runtime | Sandbox rates |
| Novita-hosted LLMs | Model rates |
| Third-party LLMs | Sandbox runtime only |


# Command Reference
Source: https://docs.novita.ai/guides/novitaclaw-commands



Complete reference for all NovitaClaw CLI commands.

## `launch`

Create a new sandbox instance.

```bash CLI icon="terminal" theme={"system"}
novitaclaw launch [OPTIONS]
```

| Option | Description |
| - | - |
| `--api-key` | Novita API key |
| `--gateway-token` | Custom gateway token (optional, auto-generated by default) |
| `--timeout` | Creation timeout in seconds (default: `60`) |
| `--type` | Sandbox type: `persistent` (default) or `on-demand` |
| `--idle-timeout` | Idle timeout in seconds for on-demand mode (default: `300`, range: 60–86400) |

## `list`

List active sandbox instances.

```bash CLI icon="terminal" theme={"system"}
novitaclaw list --api-key YOUR_API_KEY
```

| Option | Description |
| - | - |
| `--api-key` | Novita API key |
| `--quiet`, `-q` | Only print sandbox IDs, one per line (useful for scripting) |

## `status`

Check a sandbox instance status. Displays webhook URLs for all configured channels.

```bash CLI icon="terminal" theme={"system"}
novitaclaw status --api-key YOUR_API_KEY
```

| Option | Description |
| - | - |
| `--api-key` | Novita API key |
| `--json` | Output in JSON format (useful for extracting webhook URLs) |

## `stop`

Terminate a sandbox instance.

```bash CLI icon="terminal" theme={"system"}
novitaclaw stop --api-key YOUR_API_KEY
```

| Option | Description |
| - | - |
| `--api-key` | Novita API key |
| `--yes`, `-y` | Skip confirmation prompt |

## `pause`

Pause a sandbox instance. Zero billing while paused.

```bash CLI icon="terminal" theme={"system"}
novitaclaw pause --api-key YOUR_API_KEY
```

## `resume`

Resume a paused sandbox instance (\~1s to restore).

```bash CLI icon="terminal" theme={"system"}
novitaclaw resume --api-key YOUR_API_KEY
```

## `config set`

Update sandbox runtime configuration.

```bash CLI icon="terminal" theme={"system"}
# idle-timeout range: 60–86400 seconds
novitaclaw config set idle-timeout 600
# timeout in seconds
novitaclaw config set timeout 3600
```

## `tui`

Connect to a sandbox via terminal UI.

```bash CLI icon="terminal" theme={"system"}
novitaclaw tui --token YOUR_GATEWAY_TOKEN
```

## `doctor`

Run diagnostics and repair checks on a sandbox instance.

```bash CLI icon="terminal" theme={"system"}
novitaclaw doctor --api-key YOUR_API_KEY
```

| Option | Description |
| - | - |
| `--api-key` | Novita API key |
| `--deep` | Scan system services for extra gateway installs |
| `--fix` | Apply recommended repairs (alias for `--repair`) |
| `--force` | Apply aggressive repairs (overwrites custom service config) |
| `--generate-gateway-token` | Generate and configure a gateway token |
| `--no-workspace-suggestions` | Disable workspace memory system suggestions |
| `--repair` | Apply recommended repairs without prompting |
| `--yes` | Accept defaults without prompting |

## `gateway update`

Update OpenClaw to the latest version inside a sandbox.

```bash CLI icon="terminal" theme={"system"}
novitaclaw gateway update --api-key YOUR_API_KEY
```

| Option | Description |
| - | - |
| `--api-key` | Novita API key |
| `--restart` | Restart gateway after update |

## `gateway restart`

Restart the OpenClaw Gateway in a sandbox.

```bash CLI icon="terminal" theme={"system"}
novitaclaw gateway restart --api-key YOUR_API_KEY
```

## `services setup`

Install and start Web Terminal and File Manager on an existing sandbox. Use this to add services to older sandboxes that were launched before these services were included.

```bash CLI icon="terminal" theme={"system"}
novitaclaw services setup --api-key YOUR_API_KEY
```

## `pair list`

List pending pairing requests for a channel.

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair list --channel CHANNEL --api-key YOUR_API_KEY
```

## `pair approve`

Approve a pairing request for a channel.

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair approve --channel CHANNEL --code CODE --api-key YOUR_API_KEY
```

## `pair feishu`

Configure Feishu (Lark) as a messaging channel.

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair feishu \
  --app-id cli_xxx \
  --app-secret secret_xxx \
  --mode webhook \
  --verification-token tok_xxx \
  --encrypt-key key_xxx
```

| Option | Description |
| - | - |
| `--app-id` | Feishu app ID |
| `--app-secret` | Feishu app secret |
| `--mode` | `event` (default) or `webhook` |
| `--verification-token` | Webhook verification token (webhook mode only) |
| `--encrypt-key` | Webhook encrypt key (webhook mode only) |
| `--webhook-host` | Webhook bind address (default: `0.0.0.0`) |
| `--webhook-port` | Webhook listen port (default: `3000`) |
| `--webhook-path` | Webhook URL path (default: `/feishu/events`) |

## `pair telegram`

Configure Telegram as a messaging channel.

```bash CLI icon="terminal" theme={"system"}
# Polling mode (default)
novitaclaw pair telegram --bot-token YOUR_BOT_TOKEN

# Webhook mode
novitaclaw pair telegram \
  --bot-token YOUR_BOT_TOKEN \
  --mode webhook \
  --webhook-url https://example.com/webhook/telegram \
  --webhook-secret YOUR_SECRET
```

| Option | Description |
| - | - |
| `--bot-token` | Telegram bot token from BotFather |
| `--mode` | `polling` (default) or `webhook` |
| `--webhook-url` | Public webhook URL (webhook mode only) |
| `--webhook-secret` | Webhook secret (webhook mode only) |
| `--webhook-host` | Webhook bind address (default: `0.0.0.0`) |
| `--webhook-port` | Webhook listen port (default: `8787`) |
| `--webhook-path` | Webhook URL path (default: `/webhook/telegram`) |
| `--dm-policy` | DM policy (default: `pairing`) |

## `pair slack`

Configure Slack as a messaging channel.

```bash CLI icon="terminal" theme={"system"}
# Socket mode (default)
novitaclaw pair slack --bot-token xoxb-... --app-token xapp-...

# HTTP mode
novitaclaw pair slack \
  --bot-token xoxb-... \
  --mode http \
  --signing-secret YOUR_SIGNING_SECRET
```

| Option | Description |
| - | - |
| `--bot-token` | Slack bot token (`xoxb-...`) |
| `--app-token` | Slack app token (`xapp-...`, socket mode only) |
| `--mode` | `socket` (default) or `http` |
| `--signing-secret` | Slack signing secret (HTTP mode only) |
| `--webhook-host` | Webhook bind address (default: `0.0.0.0`) |
| `--webhook-port` | Webhook listen port (default: `8788`) |
| `--webhook-path` | Webhook URL path (default: `/webhook/slack`) |

## `update`

Check for CLI updates and upgrade to the latest version.

```bash CLI icon="terminal" theme={"system"}
novitaclaw update
```

***

## Programmatic Usage

Use JSON output for automation:

```bash CLI icon="terminal" theme={"system"}
novitaclaw -j launch --api-key YOUR_API_KEY
```

Example response:

```json JSON icon="code" theme={"system"}
{
  "success": true,
  "data": {
    "sandbox_id": "...",
    "webui": "https://...",
    "gateway_ws": "wss://...",
    "gateway_token": "...",
    "terminal_url": "https://...",
    "filemanager_url": "https://...",
    "services_username": "admin",
    "services_password": "..."
  }
}
```

***

## Environment Variables

| Variable | Description |
| - | - |
| `NOVITA_API_KEY` | API key, eliminates the need for `--api-key` flag |
| `OPENCLAW_GATEWAY_TOKEN` | Preset gateway token |

***

## Troubleshooting

### Command not found

Try one of these:

```bash CLI icon="terminal" theme={"system"}
# Linux
~/.local/bin/novitaclaw launch

# macOS (typical path)
~/Library/Python/3.x/bin/novitaclaw launch
```

### Connection timeout

Increase launch timeout:

```bash CLI icon="terminal" theme={"system"}
novitaclaw launch --timeout 180 --api-key YOUR_API_KEY
```

***

## Frequently Asked Questions

| Problem | Why it happens | What to do |
| - | - | - |
| "Command not found" / "Not recognized" | Python PATH not configured during install | Reinstall Python and check "Add to PATH", or add the bin directory manually |
| "Permission Denied" | Insufficient permissions | Windows: Run Terminal as Administrator. Mac/Linux: prefix with `sudo` |
| "Invalid API Key" | Key copied incorrectly | Check for extra spaces around the `sk_` string, or generate a new key |
| Sandbox won't stop | Requires confirmation to prevent accidental deletion | Type `y` when prompted, or use `--yes` to skip confirmation |
| Mac: `pip` command not found | macOS uses `pip3` by default | Use `pip3 install novitaclaw` instead of `pip` |


# Configuration
Source: https://docs.novita.ai/guides/novitaclaw-configuration



Configure models, connect external messaging channels, set up on-demand mode, and understand the service reliability features of your NovitaClaw instance.

## On-Demand Mode

On-demand mode automatically pauses your sandbox after a configurable idle period and resumes it on access. Ideal for low-traffic AI assistants, webhook-based IM integrations, and scheduled tasks. Zero billing while paused.

### Launching an On-Demand Sandbox

```bash CLI icon="terminal" theme={"system"}
# Default idle timeout: 300 seconds
novitaclaw launch --type on-demand

# Custom idle timeout (60–86400 seconds)
novitaclaw launch --type on-demand --idle-timeout 600
```

On-demand mode cannot be combined with `--mode node`.

### Manual Pause and Resume

```bash CLI icon="terminal" theme={"system"}
# Pause a sandbox (zero billing while paused)
novitaclaw pause

# Resume a sandbox (~1s to restore to pre-pause state)
novitaclaw resume
```

### Runtime Configuration

```bash CLI icon="terminal" theme={"system"}
# Update idle timeout (60–86400 seconds)
novitaclaw config set idle-timeout 600

# Update sandbox lifetime timeout (300–2592000 seconds)
novitaclaw config set timeout 3600
```

Changes take effect immediately. The in-sandbox agent picks up the new configuration on its next check cycle.

### How It Works

* Idle detection — An agent daemon inside the sandbox periodically checks OpenClaw session activity and writes status to `/tmp/.novitaclaw-status.json`.
* Auto-pause — The server-side idle monitor reads the agent status. After 2 consecutive idle checks, the sandbox is automatically paused.
* Auto-resume — Incoming webhook requests or Web UI access automatically resume a paused sandbox.
* Cron pre-wakeup — The scheduler scans paused sandboxes for upcoming cron schedules and resumes them \~120 seconds before the next job fires, ensuring cron jobs run on time.

### Checking Status

```bash CLI icon="terminal" theme={"system"}
# View sandbox status (does not trigger resume when paused)
novitaclaw status

# List shows sandbox_type and state columns
novitaclaw list
```

While paused, the `status` command still returns full URL information (read from the database without connecting to the sandbox), so scripts can store addresses without triggering a resume.

## Configuring Models

Your instance is pre-configured with a Novita-hosted model out of the box. To change the models your agent uses, navigate to `Settings → Config`, click Raw to switch to Raw JSON5 view, then click the reveal button next to "secrets redacted" to show the full config.

Update the following two sections:

### Step 1: Register the model under your provider

Add a new object to the `models` array inside `models.providers.novita`:

```json JSON icon="code" theme={"system"}
{
  "models": {
    "providers": {
      "novita": {
        "models": [
          {
            "id": "model-id",
            "name": "display name",
            "reasoning": true,
            "input": ["text"],
            "contextWindow": 200000,
            "maxTokens": 50000
          }
        ]
      }
    }
  }
}
```

### Step 2: Set it as primary or fallback

Update the `model` field under `agents.defaults` to reference your model using the `provider/model-id` format:

```json JSON icon="code" theme={"system"}
{
  "agents": {
    "defaults": {
      "model": {
        "primary": "novita/model-id",
        "fallbacks": ["novita/fallback-model-id"]
      }
    }
  }
}
```

Click Update to save. Every LLM available on the [Novita platform](https://novita.ai/model-api/product/llm-api) is supported. Third-party providers can also be configured — when you bring your own LLM, you pay only for sandbox runtime, not Novita model usage.

<img alt="NovitaClaw model configuration" />

## Connecting Channels

OpenClaw supports external messaging channels so your agent is reachable outside the Web UI. Channels are disabled by default and must be configured.

### Telegram

Connect your agent to Telegram as a messaging channel. Two connection modes are supported: Polling (default, long-poll — no public URL needed) and Webhook (HTTP push — best for on-demand sandboxes).

**Step 1: Create a Telegram Bot**

* Open Telegram and find [@BotFather](https://t.me/BotFather).
* Send `/newbot` and follow the prompts to name your bot.
* Copy the bot token BotFather provides.

#### Mode 1: Polling

Polling mode uses long-poll connections. No public URL required — simplest to configure.

<Note>
  Polling mode is not recommended for on-demand sandboxes. When the sandbox auto-pauses, the connection drops and incoming messages are lost. Use Webhook mode for on-demand sandboxes.
</Note>

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair telegram \
  --bot-token
```

#### Mode 2: Webhook

Webhook mode requires the sandbox to expose an HTTP port for receiving Telegram push events. Best suited for on-demand sandboxes — incoming webhook requests automatically trigger resume.

The `--webhook-url` is the public URL assigned to your sandbox. Run the following command to retrieve it:

```bash CLI icon="terminal" theme={"system"}
novitaclaw status --json | jq -r '.telegram_webhook_url'
```

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair telegram \
  --bot-token \
  --mode webhook \
  --webhook-url \
  --webhook-secret
```

#### Optional Webhook Parameters

| Parameter | Default | Description |
| - | - | - |
| `--webhook-host` | `0.0.0.0` | Webhook server bind address |
| `--webhook-port` | `8787` | Webhook listen port |
| `--webhook-path` | `/webhook/telegram` | Webhook URL path |
| `--dm-policy` | `pairing` | DM policy |

#### Pairing Flow

On first conversation, the bot replies with a pairing code:

```bash CLI icon="terminal" theme={"system"}
# List pending pairing requests
novitaclaw pair list --channel telegram

# Approve a pairing request
novitaclaw pair approve --channel telegram --code
```

#### Mode Comparison

| | Polling | Webhook |
| - | - | - |
| Connection | Outbound (long-poll) | Inbound (HTTP push) |
| Public port | Not required | Required |
| Setup complexity | Low | Medium (additional secrets) |
| On-demand auto-resume | Not supported (connection drops on pause) | Supported (webhook triggers resume) |
| Recommended for | Always-on sandboxes, development | On-demand sandboxes, production IM |

### Slack

Connect your agent to Slack as a messaging channel. Two connection modes are supported: Socket (default, WebSocket — no public URL needed) and HTTP (Events API webhook — best for on-demand sandboxes).

#### Mode 1: Socket

Socket mode uses a WebSocket connection. No public URL required — simplest to configure.

<Note>
  Socket mode is not recommended for on-demand sandboxes. When the sandbox auto-pauses, the WebSocket connection drops and incoming messages are lost. Use HTTP mode for on-demand sandboxes.
</Note>

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair slack \
  --bot-token xoxb-... \
  --app-token xapp-...
```

#### Mode 2: HTTP

HTTP mode uses Slack Events API webhooks. Best suited for on-demand sandboxes — incoming webhook requests automatically trigger resume.

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair slack \
  --bot-token xoxb-... \
  --mode http \
  --signing-secret
```

#### Optional HTTP Parameters

| Parameter | Default | Description |
| - | - | - |
| `--webhook-host` | `0.0.0.0` | Webhook server bind address |
| `--webhook-port` | `8788` | Webhook listen port |
| `--webhook-path` | `/webhook/slack` | Webhook URL path |

#### Pairing Flow

On first conversation, the bot replies with a pairing code:

```bash CLI icon="terminal" theme={"system"}
# List pending pairing requests
novitaclaw pair list --channel slack

# Approve a pairing request
novitaclaw pair approve --channel slack --code
```

#### Mode Comparison

| | Socket | HTTP |
| - | - | - |
| Connection | WebSocket (outbound) | HTTP push (inbound) |
| Public port | Not required | Required |
| Setup complexity | Low | Medium (additional secrets) |
| On-demand auto-resume | Not supported (connection drops on pause) | Supported (webhook triggers resume) |
| Recommended for | Always-on sandboxes, development | On-demand sandboxes, production IM |

### Channel Status

`novitaclaw status` displays webhook URLs for all configured channels:

```bash CLI icon="terminal" theme={"system"}
novitaclaw status
# Shows: Feishu Webhook, Telegram Webhook, Slack Webhook (when configured)

# JSON mode
novitaclaw status --json | jq -r '.feishu_webhook_url'
novitaclaw status --json | jq -r '.telegram_webhook_url'
novitaclaw status --json | jq -r '.slack_webhook_url'
```

### Feishu

Connect your agent to Feishu (Lark) as a messaging channel. Two connection modes are supported: Webhook (HTTP push) and Event (WebSocket long-poll).

#### Prerequisites: Create a Feishu App

* Open the [Feishu Open Platform](https://open.feishu.cn/app), log in and click Create Custom App.
* On the Credentials & Basic Info page, copy: App ID (format: `cli_xxx`) and App Secret.
* Go to Permission Management, click Batch Import, and paste the following permissions:

```json JSON icon="code" theme={"system"}
{
  "scopes": {
    "tenant": [
      "im:message", "im:message:send_as_bot", "im:message:readonly",
      "im:message.p2p_msg:readonly", "im:message.group_at_msg:readonly",
      "im:resource", "im:chat.access_event.bot_p2p_chat:read",
      "im:chat.members:bot_access"
    ],
    "user": ["im:chat.access_event.bot_p2p_chat:read"]
  }
}
```

* Go to App Capabilities > Bot and enable bot capability.
* Create a version and publish the app.

#### Mode 1: Webhook

Webhook mode requires the sandbox to expose an HTTP port for receiving Feishu push events. Best suited for on-demand sandboxes — incoming webhook requests automatically trigger resume.

Additional credentials: On the Feishu Open Platform, go to Development Configuration > Events & Callbacks > Encryption Strategy and copy: Verification Token and Encrypt Key.

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair feishu \
  --app-id cli_xxx \
  --app-secret secret_xxx \
  --mode webhook \
  --verification-token tok_xxx \
  --encrypt-key key_xxx
```

On the Feishu Open Platform Event Subscription page:

* Select Request URL Configuration
* Enter the Webhook URL — get it via:

```bash CLI icon="terminal" theme={"system"}
novitaclaw status --json | jq -r '.feishu_webhook_url'
```

* Add event: `im.message.receive_v1`

#### Mode 2: Event

Event mode uses a Feishu WebSocket long-poll connection. No public URL required — simplest to configure.

<Note>
  Event mode is not recommended for on-demand sandboxes. When the sandbox auto-pauses, the WebSocket connection drops and incoming messages are lost. Use Webhook mode for on-demand sandboxes.
</Note>

```bash CLI icon="terminal" theme={"system"}
novitaclaw pair feishu \
  --app-id cli_xxx \
  --app-secret secret_xxx
```

After configuration, on the Feishu Open Platform Event Subscription page:

* Select Use Long Connection to Receive Events
* Add event: `im.message.receive_v1`

Ensure the gateway is running (`novitaclaw status <SANDBOX_ID>`) before saving, otherwise Feishu may fail to save the long connection configuration.

#### Optional Webhook Parameters

| Parameter | Default | Description |
| - | - | - |
| `--webhook-host` | `0.0.0.0` | Webhook server bind address |
| `--webhook-port` | `3000` | Webhook listen port |
| `--webhook-path` | `/feishu/events` | Webhook URL path |

#### Pairing Flow

Feishu uses a `pairing` strategy by default. On first conversation, the bot replies with a pairing code that must be approved via CLI:

```bash CLI icon="terminal" theme={"system"}
# List pending pairing requests
novitaclaw pair list --channel feishu

# Approve a pairing request
novitaclaw pair approve --channel feishu --code
```

#### Mode Comparison

| | Event | Webhook |
| - | - | - |
| Connection | WebSocket long-poll (outbound) | HTTP push (inbound) |
| Public port | Not required | Required (default 3000) |
| Setup complexity | Low (App ID + Secret only) | Medium (additional Token + Key) |
| On-demand auto-resume | Not supported | Supported (webhook triggers resume) |
| Recommended for | Always-on sandboxes, development | On-demand sandboxes, production IM |

## Service Reliability

All core services in the sandbox are managed by systemd for production-grade reliability:

| Service | Description | Auto-Restart |
| - | - | - |
| OpenClaw Gateway | Agent runtime and WebSocket server | ✅ |
| Web Terminal (ttyd) | Browser-based terminal access | ✅ |
| File Manager (gohttpserver) | Web-based file management | ✅ |

Crash auto-recovery: If the Gateway crashes repeatedly, the system automatically runs diagnostics, attempts repair, and restores the last known-good configuration from backup — no manual intervention required.

Config auto-backup: Every configuration write creates an automatic backup. If a bad config causes a crash, the recovery process restores from the most recent valid backup.


# OpenAI Agents SDK
Source: https://docs.novita.ai/guides/openai-agents-sdk

Seamlessly integrate Novita AI with OpenAI Agents SDK for building multi-agent workflows.

The [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) is a lightweight yet powerful framework for building multi-agent workflows. And the SDK is compatible with any model providers that support the OpenAI Chat Completions API format.

This guide will walk you through how to use Novita LLM API with OpenAI Agents SDK.

## Get Started

1. Set up your Python environment and install the Agents SDK.

```bash theme={"system"}
python -m venv env
source env/bin/activate
pip install openai-agents==0.0.7
```

2. Set up your Novita API key.

Go to [Key Management](https://novita.ai/settings/key-management?utm_source=getstarted) and create a new API key.

## Hello world example

<Warning>
  If running this, ensure you set the NOVITA\_API\_KEY and MODEL\_NAME environment variables.
</Warning>

```python theme={"system"}
import os
from openai import AsyncOpenAI
from agents import (
    Agent,
    Runner,
    set_default_openai_api,
    set_default_openai_client,
    set_tracing_disabled,
)

BASE_URL = "https://api.novita.ai/openai"
API_KEY = os.getenv("NOVITA_API_KEY")
MODEL_NAME = os.getenv("MODEL_NAME")

# Because Novita not support the responses API so we use the chat completions API instead.
set_default_openai_api("chat_completions")
set_default_openai_client(AsyncOpenAI(base_url=BASE_URL, api_key=API_KEY))
# Disable tracing for this example
# Refer to https://openai.github.io/openai-agents-python/tracing/#external-tracing-processors-list to use the custom spans.
set_tracing_disabled(disabled=True)

agent = Agent(name="Assistant",
              instructions="You are a helpful assistant", model=MODEL_NAME)

result = Runner.run_sync(
    agent, "Write a haiku about recursion in programming.")
print(result.final_output)

# Code within the code,
# Functions calling themselves,
# Infinite loop's dance.
```

## Handoffs example

<Warning>
  If running this, ensure you set the NOVITA\_API\_KEY and MODEL\_NAME environment variables.
</Warning>

```python theme={"system"}
import os
import asyncio
from openai import AsyncOpenAI
from agents import (
    Agent,
    Runner,
    set_default_openai_api,
    set_default_openai_client,
    set_tracing_disabled,
)

BASE_URL = "https://api.novita.ai/openai"
API_KEY = os.getenv("NOVITA_API_KEY")
MODEL_NAME = os.getenv("MODEL_NAME")

# Because Novita not support the responses API so we use the chat completions API instead.
set_default_openai_api("chat_completions")
set_default_openai_client(AsyncOpenAI(base_url=BASE_URL, api_key=API_KEY))
# Disable tracing for this example
# Refer to https://openai.github.io/openai-agents-python/tracing/#external-tracing-processors-list to use the custom spans.
set_tracing_disabled(disabled=True)

spanish_agent = Agent(
    name="Spanish agent",
    instructions="You only speak Spanish.",
    model=MODEL_NAME,
)

english_agent = Agent(
    name="English agent",
    instructions="You only speak English",
    model=MODEL_NAME,
)

triage_agent = Agent(
    name="Triage agent",
    instructions="Handoff to the appropriate agent based on the language of the request.",
    handoffs=[spanish_agent, english_agent],
    model=MODEL_NAME,
)


async def main():
    result = await Runner.run(triage_agent, input="Hola, ¿cómo estás?")
    print(result.final_output)
    # ¡Hola! Estoy bien, gracias por preguntar. ¿Y tú, cómo estás?


if __name__ == "__main__":
    asyncio.run(main())
```

## Functions example

<Warning>
  If running this, ensure you set the NOVITA\_API\_KEY and MODEL\_NAME environment variables.
</Warning>

```python theme={"system"}
import os
import asyncio
from openai import AsyncOpenAI
from agents import (
    Agent,
    Runner,
    set_default_openai_api,
    set_default_openai_client,
    set_tracing_disabled,
    function_tool,
)

BASE_URL = "https://api.novita.ai/openai"
API_KEY = os.getenv("NOVITA_API_KEY")
MODEL_NAME = os.getenv("MODEL_NAME")

# Because Novita not support the responses API so we use the chat completions API instead.
set_default_openai_api("chat_completions")
set_default_openai_client(AsyncOpenAI(base_url=BASE_URL, api_key=API_KEY))
# Disable tracing for this example
# Refer to https://openai.github.io/openai-agents-python/tracing/#external-tracing-processors-list to use the custom spans.
set_tracing_disabled(disabled=True)

@function_tool
def get_weather(city: str) -> str:
    return f"The weather in {city} is sunny."

agent = Agent(
    name="Hello world",
    instructions="You are a helpful agent.",
    tools=[get_weather],
    model=MODEL_NAME,
)

async def main():
    result = await Runner.run(agent, input="What's the weather in Tokyo?")
    print(result.final_output)
    # The weather in Tokyo is sunny.

if __name__ == "__main__":
    asyncio.run(main())
```


# OpenCode
Source: https://docs.novita.ai/guides/opencode

Connect OpenCode to Novita's OpenAI-compatible LLM API endpoint.

OpenCode is an AI coding tool that supports custom providers. You can configure OpenCode with Novita's OpenAI-compatible LLM API endpoint and use Novita models for coding, debugging, refactoring, and project Q\&A.

## Prerequisites

Before you begin, prepare the following values:

* **API key**: Create or copy an API key from [Key Management](https://novita.ai/settings/key-management).
* **Base URL**: `https://api.novita.ai/openai/v1`
* **Model ID**: Copy the model ID you want to use from the Model Hub, such as `deepseek/deepseek-v4.1-flash`.

## Install OpenCode

OpenCode provides command-line and desktop options. Choose one installation method.

### Linux / macOS / WSL2

Use one of the following methods:

```bash theme={"system"}
curl -fsSL https://opencode.ai/install | bash
```

```bash theme={"system"}
brew install anomalyco/tap/opencode
```

```bash theme={"system"}
npm i -g opencode-ai
```

### Desktop

You can also download the desktop app from:

```text theme={"system"}
https://opencode.ai/download
```

## Configure a custom provider

Open the OpenCode settings page and find the provider settings. Depending on your OpenCode version, this is usually under **Settings** → **Providers** → **Custom Provider**.

Fill in the following values:

| Field | Value |
| :- | :- |
| Display name | `Novita` |
| Base URL | `https://api.novita.ai/openai/v1` |
| API key | Your Novita API key |
| Model ID | For example, `deepseek/deepseek-v4.1-flash` |

<Tip>
  Make sure the Model ID exactly matches the value in Novita's Model Hub. Context length, pricing, and capabilities depend on the selected model.
</Tip>

## Verify the model

After saving the provider, select your Novita model in OpenCode and send a test prompt:

```text theme={"system"}
In one sentence, tell me what programming tasks you can help with.
```

If the model responds, the integration is working. If verification fails, check that:

* Your API key is correct and your account has available balance.
* The Base URL is `https://api.novita.ai/openai/v1`.
* The Model ID is correct.
* Your network can reach the Novita API endpoint.


# OWL
Source: https://docs.novita.ai/guides/owl

Optimize Your Workflow with Novita AI and OWL.

OWL is an open-source framework for multi-agent collaboration that specializes in real-world task automation, built on the CAMEL-AI Foundation. By integrating with various AI models, OWL combines dynamic agent interactions with powerful toolkits for online search, browser automation, document processing, and code execution. This framework enables more natural, efficient, and robust task automation across diverse domains, delivering actionable insights tailored to your specific needs.

This guide will enable you to seamlessly integrate Novita AI with OWL and elevate your AI-driven processes for faster, smarter outcomes.

## **How to Integrate Novita AI with RAGFlow**

### **Step 1: Pre-Configuration Setup**

1. **Access the API Endpoint**

* Use the official API endpoint: [https://api.novita.ai/openai](https://api.novita.ai/openai).

2. **Create an API Key**

* Visit the [key management page](https://novita.ai/settings/key-management) and select `Add New Key` to generate your API key.

  <img alt="Visitthekeymanagementpageandselect Add New Keytogenerateyour AP Ikey Pn" />
* Keep your API key safe.
  * **Note:** API keys are securely encrypted on the server. If you lose it, you’ll need to delete the old key and create a new one.

    <img alt="Keepyour AP Ikeysafe 1 Pn" />

    <img alt="Keepyour AP Ikeysafe 2 Pn" />

3. **Identify the Model ID**

* meta-llama/llama-4-scout-17b-16e-instruct

### **Step 2: Install OWL**

* To begin, download and install OWL by following the step-by-step instructions on the Github: [https://github.com/camel-ai/owl](https://github.com/camel-ai/owl).

### **Step 3: Configure OWL**

* Prepare OWL for use by setting up environment variables and adding your API key. Run the following commands in your terminal:

```bash theme={"system"}
cd owl
cp .env_template .env
```

* Start OWL and specify your task by executing:

```bash theme={"system"}
python examples/run_novita_ai.py
```

<img alt="Step3 Configure OWL Pn" />

### **Step 4: View Results**

1. **Terminal Output**

* Once launched, OWL will display the execution results directly in the terminal window.

  <img alt="Terminal Output 1 Pn" />

  <img alt="Terminal Output 2 Pn" />

2. **Web Interface**

* For a more intuitive experience, use OWL’s web-based interface. Launch it by running:

```bash theme={"system"}
cd owl
python webapp.py
```

<img alt="Web Interface 1 Pn" />

<img alt="Web Interface 2 Pn" />

* Steps to use the Web UI:
  * Select `run_novita_ai` from the left-hand menu and go to the `Environment Variable Management` tab on the right and input your `NOVITA_API_KEY`.

    <img alt="Stepstousethe Web UI 1 Pn" />
  * Click `Run` to execute your task.

    <img alt="Stepstousethe Web UI 2 Pn" />
  * To perform a new task, update the input field and click `Run` again.

    <img alt="Stepstousethe Web UI 3 Pn" />

    <img alt="Stepstousethe Web UI 4 Pn" />
  * Results will be displayed in the terminal, or a new file will be generated in the root directory, depending on the task.

    <img alt="Stepstousethe Web UI 5 Pn" />
  * Alternatively, you can directly input your desired task into the content box and click the `Run` button to execute it.

    <img alt="Stepstousethe Web UI 6 Pn" />


# Page Assist
Source: https://docs.novita.ai/guides/pageassist

Elevate Your Browsing Experience with Novita AI and Page Assist Integration.

Page Assist offers a seamless browser extension that integrates AI capabilities directly into your web interactions. By combining Novita AI with Page Assist, you can unlock a powerful AI-enhanced browsing experience that enhances productivity and insight generation.

This guide will walk you through the process of deploying and running Page Assist with Novita AI to supercharge your web interactions.

## **How to Integrate Novita AI with Page Assist**

You can find the GitHub repository of Page Assist here: [n4ze3m/page-assist](https://github.com/n4ze3m/page-assist).

### Step 1: Prepare Your Environment

* Install Bun or npm: Follow the installation guide for [<u>Bun</u>](https://bun.sh/) or use npm as an alternative.

### Step 2: Clone Page Assist Repository

1. Open your terminal and run:

```bash theme={"system"}
git clone https://github.com/n4ze3m/page-assist.git
cd page-assist
```

2. Install dependencies using Bun or npm:

```bash theme={"system"}
bun install
```

### Step 3: Build Page Assist Extension

* Build the extension for Chrome (default):

```bash theme={"system"}
bun run build
```

* For Firefox, use:

```bash theme={"system"}
bun build:firefox
```

### Step 4: Load the Extension

1. For Chrome:
   * Navigate to `chrome://extensions`.
   * Enable Developer Mode.
   * Click `Load unpacked` and select the `build/chrome-xxx` (e.g. `build/chrome-mv3`) directory.
2. For Firefox:
   * Go to `about:addons`.
   * Click `Extensions` tab.
   * Click `Manage Your Extensions`.
   * Select `Load Temporary Add-on` and choose the `manifest.json` file from in the `build/firefox-xxx` (e.g. `build/firefox-mv3`) directory.

### Step 5: Configure Novita AI as OpenAI API Compatible Endpoint

1. Obtain Your Novita AI API Key:
   * [Log in](https://novita.ai/user/login) to your Novita AI account.
   * Navigate to [the Key Management page](https://novita.ai/settings/key-management).
   * Generate a new API Key and copy it.
2. Set Up Novita AI Endpoint:

   * In your Page Assist Settings, enter `Open Compatible API` to add provider.

   <Frame>
     <img alt="Inyour Page Assist Settings,enter Open Compatible AP Itoaddprovider Jpe" />
   </Frame>

   * Choose Novita from the `Custom list`, and use your API key for authentication.

   <Frame>
     <img alt="Choose Novitafromthe Customlist,anduseyour AP Ikeyforauthentication Jpe" />
   </Frame>

### Step 6: Choose your Model and Test Page Assist with Novita AI

* Choose your model from the model list provided by Novita AI.

<Frame>
  <img alt="Chooseyourmodelfromthemodellistprovidedby Novita AI Jpe" />
</Frame>

* Interact with your Novita AI model by asking questions or analyzing web content.

<Frame>
  <img alt="Interactwithyour Novita A Imodelbyaskingquestionsoranalyzingwebcontent Jpe" />
</Frame>


# Portkey
Source: https://docs.novita.ai/guides/portkey

Streamline AI development by using Portkey AI Gateway with Novita AI for fast, secure, and reliable performance.

Portkey AI Gateway transforms how developers work with AI models like Novita AI, providing a unified interface for seamless access to multiple language models with fast, secure, and reliable routing. This integration simplifies AI development and improves application performance.

This guide will walk you through setting up Portkey AI Gateway and then integrating Novita AI API with Portkey.

## How to Set Up Portkey AI Gateway

Setting up Portkey AI Gateway is simple and efficient, requiring just three key steps: configuring the gateway, sending your first request, and optimizing routing and guardrails for seamless performance.

### Step 1: Setup your AI Gateway

To run the gateway locally, ensure Node.js and npm are installed on your system. Once ready, execute the following command:

```jason theme={"system"}
npx @portkey-ai/gateway
```

After the gateway starts, two key URLs will be displayed:

* The Gateway: `http://localhost:8787/v1`
* The Gateway Console: `http://localhost:8787/public/`

### Step 2: Make your first request

Begin by installing the Portkey AI Python library:

```python theme={"system"}
pip install -qU portkey-ai
```

Next, execute the following Python code to send your first request:

```python theme={"system"}
from portkey_ai import Portkey

# OpenAI compatible client
client = Portkey(
    provider="openai", # or 'anthropic', 'bedrock', 'groq', etc
    Authorization="sk-***" # the provider API key
)

# Make a request through your AI Gateway
client.chat.completions.create(
    messages=[{"role": "user", "content": "What's the weather like?"}],
    model="gpt-4o-mini"
)
```

Effortlessly monitor all your local logs in one centralized location using the Gateway Console at: `http://localhost:8787/public/`.

### Step 3: Routing & Guardrails

Portkey AI Gateway enables you to configure routing rules, add reliability features, and enforce guardrails. Below is an example configuration:

```python theme={"system"}
config = {
  "retry": {"attempts": 5},

  "output_guardrails": [{
    "default.contains": {"operator": "none", "words": ["Apple"]},
    "deny": True
  }]
}

# Attach the config to the client
client = client.with_options(config=config)

client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Reply randomly with Apple or Bat"}]
)

# In this example, the guardrail denies all replies containing "Apple", so the response would always be "Bat". The retry configuration would attempt the request up to 5 times before giving up.
```

<Frame>
  <img alt="" />
</Frame>

## How to Integrate Novita AI API with Portkey

To access the Novita AI API via the Portkey AI Gateway, follow these steps:

### Step 1: Install the Portkey SDK

Integrate the Portkey SDK into your application to seamlessly interact with Novita AI’s API through Portkey’s gateway.

**Node.JS**

```jason theme={"system"}
npm install --save portkey-ai
```

**Python**

```python theme={"system"}
pip install portkey-ai
```

### Step 2: Initialize Portkey with the Virtual Key

To integrate Novita AI with Portkey, retrieve your LLM API key from[ Novita AI ](https://novita.ai/settings/key-management)and add it to Portkey to generate the virtual key.

**Node.JS SDK**

```jason theme={"system"}
import Portkey from 'portkey-ai'

const portkey = new Portkey({
  apiKey: "PORTKEY_API_KEY",  // Replace with your Portkey API key
  virtualKey: "VIRTUAL_KEY"   // Replace with your virtual key for Novita AI
})
```

**Python SDK**

```python theme={"system"}
from portkey_ai import Portkey

portkey = Portkey(
    api_key="PORTKEY_API_KEY",  # Replace with your Portkey API key
    virtual_key="VIRTUAL_KEY"   # Replace with your virtual key for Novita AI
)
```

### Step 3: Invoke Chat Completions with Novita AI

Utilize the Portkey instance to send requests to Novita AI. If necessary, you can override the virtual key directly within the API call.

**Node.JS SDK**

```jason theme={"system"}
const chatCompletion = await portkey.chat.completions.create({
  messages: [{ role: 'user', content: 'Say this is a test' }],
  model: 'Nous-Hermes-2-Mixtral-8x7B-DPO'
});

console.log(chatCompletion.choices);
```

**Python SDK**

```python theme={"system"}
completion = portkey.chat.completions.create(
    messages= [{ "role": 'user', "content": 'Say this is a test' }],
    model= 'reka-core'
)

print(completion)
```


# Quickstart
Source: https://docs.novita.ai/guides/quickstart



Novita AI gives you hosted model APIs, agent sandboxes, and GPU infrastructure. Follow the path that matches your task and verify a working result before moving on to the deeper guides.

<Tip>
  If you have questions, please check our [FAQs](/guides/faq) first or [reach out to our support team on Discord](https://discord.gg/YyPRAzwp7P).
</Tip>

## 1. Sign in or create an account

Sign in to [novita.ai](https://novita.ai/user/login). You can use Google or GitHub authentication, or [sign up with your email address](https://novita.ai/user/register).

## 2. Create an API key

Novita AI authenticates API requests with a Bearer API key. Open [Key Management](https://novita.ai/settings/key-management) to create or manage a key, then follow [API Keys](/guides/llm-api-keys) to store it safely.

Pass your API key only through the `NOVITA_API_KEY` environment variable in these examples. Do not put a real API key in source code, documentation, agent chats, analytics, screenshots, or logs.

## 3. Confirm available credit

Check your available credit on [Billing and Payments](https://novita.ai/billing) before sending a request. Product credit and model access can affect whether a request succeeds. For longer-running use, see [Automatic Top-up](/guides/auto-top-up).

<div />

## 4. Send your first LLM request

Complete the account, API key, and credit steps above before running this example. It uses the OpenAI-compatible chat completions endpoint and the `deepseek/deepseek-v4.1-flash` model:

```bash theme={"system"}
export NOVITA_API_KEY="<Your API Key>"

curl "https://api.novita.ai/openai/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${NOVITA_API_KEY}" \
  -d '{
    "model": "deepseek/deepseek-v4.1-flash",
    "messages": [
      {
        "role": "user",
        "content": "Say hello in one sentence."
      }
    ],
    "max_tokens": 64
  }'
```

A successful request returns HTTP `200` and a non-empty `choices[0].message.content` value, such as `"Hello!"`. The exact wording can vary by model. Add `--include` to the cURL command to inspect the HTTP status as well as the response body. An HTTP `200` alone does not confirm a valid model response.

If the request fails, use the matching guide:

* `401 Unauthorized`: check the key format and [API key configuration](/guides/llm-api-keys).
* `403 Forbidden`: the Novita gateway may return `INVALID_API_KEY` for a missing or invalid key; check the key first. For a model access error, check [model access](/guides/llm-model-access) and whether identity verification is required.
* `429 Too Many Requests`: check [rate limits](/guides/llm-rate-limits).
* `5xx`: check [common error codes](/guides/error) and retry guidance.

For Python, JavaScript, streaming, and additional request options, continue to the [LLM API guide](/guides/llm-api).

<div />

## 5. Use your agent

Your agent can help you create the request and integrate Novita AI. Complete the account, API key, and credit steps above, and make `NOVITA_API_KEY` available in the environment where the agent runs the code. Ask it to read the Novita AI skill:

```text theme={"system"}
Read https://raw.githubusercontent.com/novitalabs/novita-skills/main/skills/novita-ai/SKILL.md and use Novita AI for this task.
```

If your agent supports skills, you can install it once:

```bash theme={"system"}
npx skills add novitalabs/novita-skills --skill novita-ai
```

[View the skill on GitHub →](https://github.com/novitalabs/novita-skills).

After the agent writes the integration, run the request and verify HTTP `200` with non-empty `choices[0].message.content`. If the agent path does not work, return to [the manual request](#manual-api-request).

## 6. Troubleshooting

If you cannot complete the first request, check these in order:

1. The `NOVITA_API_KEY` environment variable is set in the current shell.
2. The key is active and has access to `deepseek/deepseek-v4.1-flash`.
3. Your account has available credit and has completed any required verification.
4. The endpoint, model ID, and request body match the example.
5. The returned status code is covered by [Common Error Codes](/guides/error).

For a complete list of endpoints and parameters, see the [API Reference](/api-reference/api-reference-overview).


# Claude Code Agent
Source: https://docs.novita.ai/guides/sandbox-coding-agent-claude-code



Claude Code is Anthropic's coding agent. On Novita Sandbox, the `claude-code` template gives you a ready-to-use, isolated environment where Claude Code can read and edit code, run commands, and complete multi-step engineering tasks autonomously —  without directly modifying files on your local machine.

Typical use cases:

* **Autonomous coding tasks** — hand Claude Code a prompt (e.g. "add error handling to all API endpoints") and let it implement changes end to end.
* **Working on real repositories** — clone a Git repo into the sandbox and have Claude Code refactor, fix bugs, or add features.
* **Safe, unattended automation** — run the agent fully automatically inside an isolated sandbox, so file and command actions never affect your own environment.
* **Multi-step workflows** — start a session to plan, then resume it to carry out each step.

The `claude-code` template comes with Claude Code pre-installed, so you can spin up a sandbox and drive Claude Code in just a few lines. The example below runs Claude Code in **headless mode** — a non-interactive, one-shot run that takes a prompt, produces its output, and exits, with no interactive terminal session — which is exactly what you want when driving it programmatically inside a sandbox. Create a sandbox from the template, run the `claude` CLI through `commands.run`, stream its output, then kill the sandbox.

## Quick start

<Note>
  **`-p "<prompt>"`** (`--print`): runs Claude Code in non-interactive mode — it processes the prompt, prints the result to stdout, and exits, instead of opening an interactive session. This is what makes it scriptable inside a sandbox.
</Note>

<Note>
  **`--dangerously-skip-permissions`:** skips the interactive approval prompts Claude Code normally shows before running tools (editing files, executing commands, etc.). It lets the agent act fully unattended, which is convenient in an isolated sandbox — but as the flag name warns, use it only in trusted, sandboxed environments, since the agent can take file and command actions without asking.
</Note>

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  def main() -> None:
      novita = Novita(api_key=os.environ["NOVITA_API_KEY"])
      sandbox = novita.sandbox.create(
          "claude-code",
          envs={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
      )
      print("Sandbox created:", sandbox.sandbox_id)

      execution = sandbox.commands.run(
          'claude --dangerously-skip-permissions -p "Hello"',
          on_stdout=lambda data: print(data),
          on_stderr=lambda data: print(data),
      )
      print(execution)

      sandbox.kill()
      print("Sandbox killed")

  if __name__ == "__main__":
      main()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
  const sandbox = await novita.sandbox.create('claude-code', {
    envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
  })
  console.log('Sandbox created:', sandbox.sandboxId)

  const execution = await sandbox.commands.run(
    'claude --dangerously-skip-permissions -p "Hello"',
    {
      onStdout: (data) => console.log(data),
      onStderr: (data) => console.log(data),
    }
  )
  console.log(execution)

  await sandbox.kill()
  console.log('Sandbox killed')
  ```
</CodeGroup>

***

## Custom Claude configuration

To make Claude Code use a custom LLM (custom API token, base URL, or model), write the configuration to `~/.claude/settings.json` after creating the sandbox and before running `claude`. The `env` block in settings.json is injected into Claude Code as environment variables.

```python Python icon="python" theme={"system"}
import os
from novita_sandbox import Novita

def main() -> None:
    novita = Novita(api_key=os.environ["NOVITA_API_KEY"])
    sandbox = novita.sandbox.create("claude-code")
    print("Sandbox created:", sandbox.sandbox_id)

    sandbox.files.write(
        "~/.claude/settings.json",
        """{
  "env": {
    "ANTHROPIC_AUTH_TOKEN": "<your custom llm api token>",
    "ANTHROPIC_BASE_URL": "<your custom llm api url>",
    "ANTHROPIC_MODEL": "<your custom model>",
    "ANTHROPIC_REASONING_MODEL": "<your custom model>",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "<your custom model>",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "<your custom model>",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "<your custom model>"
  },
  "model": "<your custom model>"
}""",
    )

    execution = sandbox.commands.run(
        'claude --dangerously-skip-permissions -p "Hello"',
        on_stdout=lambda data: print(data),
        on_stderr=lambda data: print(data),
    )
    print(execution)

    sandbox.kill()
    print("Sandbox killed")

if __name__ == "__main__":
    main()
```

***

## Work on a cloned repository

A common workflow is to clone a Git repository into the sandbox and let Claude Code work on it. Use `sandbox.git.clone` to check out the repo (with credentials for private repos), then run `claude` from inside the cloned directory.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  novita = Novita(api_key=os.environ["NOVITA_API_KEY"])
  sandbox = novita.sandbox.create("claude-code")

  sandbox.git.clone(
      "https://github.com/your-org/your-repo.git",
      path="/home/user/repo",
      username="x-access-token",
      password=os.environ["GITHUB_TOKEN"],
      depth=1,
  )

  result = sandbox.commands.run(
      'cd /home/user/repo && claude --dangerously-skip-permissions -p "Add error handling to all API endpoints"',
      on_stdout=lambda data: print(data, end=""),
  )
  print(result)

  sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
  const sandbox = await novita.sandbox.create('claude-code')

  await sandbox.git.clone('https://github.com/your-org/your-repo.git', {
    path: '/home/user/repo',
    username: 'x-access-token',
    password: process.env.GITHUB_TOKEN,
    depth: 1,
  })

  const result = await sandbox.commands.run(
    'cd /home/user/repo && claude --dangerously-skip-permissions -p "Add error handling to all API endpoints"',
    { onStdout: (data) => process.stdout.write(data) }
  )
  console.log(result)

  await sandbox.kill()
  ```
</CodeGroup>

***

## Resume a session

Claude Code can continue a previous session, so you can run a multi-step workflow across several invocations. Start a session with `--output-format json` to get a `session_id` in the response, then pass it to `--resume` on the next run to continue where you left off.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os
  import json
  from novita_sandbox import Novita

  def main() -> None:
      novita = Novita(api_key=os.environ["NOVITA_API_KEY"])
      sandbox = novita.sandbox.create(
          "claude-code",
          envs={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
      )
      print("Sandbox created:", sandbox.sandbox_id)

      sandbox.git.clone(
          "https://github.com/your-org/your-repo.git",
          path="/home/user/repo",
          username="x-access-token",
          password=os.environ["GITHUB_TOKEN"],
          depth=1,
      )

      # Start a new session with JSON output to capture the session ID
      initial = sandbox.commands.run(
          'cd /home/user/repo && claude --dangerously-skip-permissions --output-format json -p "Analyze the codebase and create a refactoring plan"',
      )
      session_id = json.loads(initial.stdout)["session_id"]
      print("Session ID:", session_id)

      # Resume the session with a follow-up task
      follow_up = sandbox.commands.run(
          f'cd /home/user/repo && claude --dangerously-skip-permissions --resume {session_id} -p "Now implement step 1 of the plan"',
          on_stdout=lambda data: print(data, end=""),
      )
      print(follow_up)

      sandbox.kill()
      print("Sandbox killed")

  if __name__ == "__main__":
      main()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
  const sandbox = await novita.sandbox.create('claude-code', {
    envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
  })
  console.log('Sandbox created:', sandbox.sandboxId)

  await sandbox.git.clone('https://github.com/your-org/your-repo.git', {
    path: '/home/user/repo',
    username: 'x-access-token',
    password: process.env.GITHUB_TOKEN,
    depth: 1,
  })

  // Start a new session with JSON output to capture the session ID
  const initial = await sandbox.commands.run(
    'cd /home/user/repo && claude --dangerously-skip-permissions --output-format json -p "Analyze the codebase and create a refactoring plan"',
  )
  const sessionId = JSON.parse(initial.stdout).session_id
  console.log('Session ID:', sessionId)

  // Resume the session with a follow-up task
  const followUp = await sandbox.commands.run(
    `cd /home/user/repo && claude --dangerously-skip-permissions --resume ${sessionId} -p "Now implement step 1 of the plan"`,
    { onStdout: (data) => process.stdout.write(data) }
  )
  console.log(followUp)

  await sandbox.kill()
  console.log('Sandbox killed')
  ```
</CodeGroup>


# Codex Agent
Source: https://docs.novita.ai/guides/sandbox-coding-agent-codex



Codex is OpenAI's coding agent. On Novita Sandbox, the `codex` template gives you a ready-to-use, isolated environment where Codex can read and edit code, run commands, and complete multi-step engineering tasks autonomously — without directly modifying files on your local machine.

Typical use cases:

* **Autonomous coding tasks** — hand Codex a prompt (e.g. "add error handling to all API endpoints") and let it implement changes end to end.
* **Working on real repositories** — clone a Git repo into the sandbox and have Codex refactor, fix bugs, or add features.
* **Safe, unattended automation** — run the agent fully automatically inside an isolated sandbox, so file and command actions never affect your own environment.
* **Multi-step workflows** — start a session to plan, then resume it to carry out each step.

The `codex` template comes with Codex pre-installed, so you can spin up a sandbox and drive Codex in just a few lines. The examples below use the SDK's unified `Novita` client, create a sandbox from the `codex` template, run the `codex` CLI through `commands.run`, stream its output, then kill the sandbox.

## Quick start

<Note>
  **`codex exec "<prompt>"`:** the `exec` subcommand runs Codex non-interactively — it processes the prompt, prints the result, and exits, instead of opening an interactive session. This is what makes it scriptable inside a sandbox.
</Note>

<Note>
  **`--full-auto`:** runs Codex fully autonomously, automatically approving the file and command actions it would otherwise prompt for. Convenient in an isolated sandbox, but use it only in trusted, sandboxed environments. **`--skip-git-repo-check`** lets Codex run outside a Git repository (by default it expects to run inside one).
</Note>

```python Python icon="python" theme={"system"}
import os

from novita_sandbox import Novita


def main() -> None:
    novita = Novita(api_key=os.environ["NOVITA_API_KEY"])

    sandbox = novita.sandbox.create(
        "codex",
        timeout=3600,
        envs={"CODEX_API_KEY": os.environ["CODEX_API_KEY"]},
    )
    print("Sandbox created:", sandbox.sandbox_id)

    try:
        execution = sandbox.commands.run(
            'codex exec --full-auto --skip-git-repo-check "Hello"',
            on_stdout=lambda data: print(data, end=""),
            on_stderr=lambda data: print(data, end=""),
            timeout=0,
        )
        print(execution)
    finally:
        sandbox.kill()
        print("Sandbox killed")


if __name__ == "__main__":
    main()
```

***

## Custom Codex configuration

To make Codex use a custom LLM (custom API token, base URL, or model), write the credentials to `~/.codex/auth.json` and the provider settings to `~/.codex/config.toml` after creating the sandbox and before running `codex`.

```python Python icon="python" theme={"system"}
import os

from novita_sandbox import Novita


def main() -> None:
    novita = Novita(api_key=os.environ["NOVITA_API_KEY"])
    sandbox = novita.sandbox.create("codex", timeout=3600)
    print("Sandbox created:", sandbox.sandbox_id)

    try:
        sandbox.files.write(
            "~/.codex/auth.json",
            """{
  "OPENAI_API_KEY": "<your custom llm api token>"
}""",
        )
        sandbox.files.write(
            "~/.codex/config.toml",
            """model_provider = "custom"
model = "<your custom model>"
model_reasoning_effort = "high"
disable_response_storage = true
[model_providers]
[model_providers.custom]
name = "custom"
wire_api = "responses"
requires_openai_auth = true
base_url = "<your custom llm api url>"
""",
        )

        execution = sandbox.commands.run(
            'codex exec --full-auto --skip-git-repo-check "Hello"',
            on_stdout=lambda data: print(data, end=""),
            on_stderr=lambda data: print(data, end=""),
            timeout=0,
        )
        print(execution)
    finally:
        sandbox.kill()
        print("Sandbox killed")


if __name__ == "__main__":
    main()
```

***

## Work on a cloned repository

A common workflow is to clone a Git repository into the sandbox and let Codex work on it. Use `sandbox.git.clone` to check out the repo (with credentials for private repos), then run `codex` from inside the cloned directory.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os

  from novita_sandbox import Novita


  novita = Novita(api_key=os.environ["NOVITA_API_KEY"])
  sandbox = novita.sandbox.create("codex", timeout=3600)

  try:
      sandbox.git.clone(
          "https://github.com/your-org/your-repo.git",
          path="/home/user/repo",
          username="x-access-token",
          password=os.environ["GITHUB_TOKEN"],
          depth=1,
      )

      result = sandbox.commands.run(
          'codex exec --full-auto --skip-git-repo-check "Add error handling to all API endpoints"',
          cwd="/home/user/repo",
          on_stdout=lambda data: print(data, end=""),
          timeout=0,
      )
      print(result)
  finally:
      sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
  const sandbox = await novita.sandbox.create('codex', { timeoutMs: 3_600_000 })

  try {
    await sandbox.git.clone('https://github.com/your-org/your-repo.git', {
      path: '/home/user/repo',
      username: 'x-access-token',
      password: process.env.GITHUB_TOKEN,
      depth: 1,
    })

    const result = await sandbox.commands.run(
      'codex exec --full-auto --skip-git-repo-check "Add error handling to all API endpoints"',
      {
        cwd: '/home/user/repo',
        onStdout: (data) => process.stdout.write(data),
        timeoutMs: 0,
      }
    )
    console.log(result)
  } finally {
    await sandbox.kill()
  }
  ```
</CodeGroup>

***

## Resume a session

Codex can continue a previous session, so you can run a multi-step workflow across several invocations. Start a session with `--json` to capture the `thread_id` from the first event (`thread.started`), then pass it to `codex exec resume <thread_id>` to continue where you left off.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import json
  import os

  from novita_sandbox import Novita


  def main() -> None:
      novita = Novita(api_key=os.environ["NOVITA_API_KEY"])

      sandbox = novita.sandbox.create(
          "codex",
          timeout=3600,
          envs={"CODEX_API_KEY": os.environ["CODEX_API_KEY"]},
      )
      print("Sandbox created:", sandbox.sandbox_id)

      try:
          # Start a new session with JSON output to capture the thread ID.
          initial = sandbox.commands.run(
              'codex exec --full-auto --skip-git-repo-check --json "Create plan.md with a 3-step plan for a TODO CLI app"',
              timeout=0,
          )
          # The first --json event is thread.started.
          thread_id = json.loads(initial.stdout.strip().splitlines()[0])["thread_id"]
          print("Thread ID:", thread_id)

          # Resume the session with a follow-up task.
          follow_up = sandbox.commands.run(
              f'codex exec resume {thread_id} --full-auto --skip-git-repo-check "Now implement step 1 of the plan"',
              on_stdout=lambda data: print(data, end=""),
              timeout=0,
          )
          print(follow_up)
      finally:
          sandbox.kill()
          print("Sandbox killed")


  if __name__ == "__main__":
      main()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })

  const sandbox = await novita.sandbox.create('codex', {
    timeoutMs: 3_600_000,
    envs: { CODEX_API_KEY: process.env.CODEX_API_KEY },
  })
  console.log('Sandbox created:', sandbox.sandboxId)

  try {
    // Start a new session with JSON output to capture the thread ID.
    const initial = await sandbox.commands.run(
      'codex exec --full-auto --skip-git-repo-check --json "Create plan.md with a 3-step plan for a TODO CLI app"',
      { timeoutMs: 0 }
    )
    // The first --json event is thread.started.
    const threadId = JSON.parse(initial.stdout.trim().split('\n')[0]).thread_id
    console.log('Thread ID:', threadId)

    // Resume the session with a follow-up task.
    const followUp = await sandbox.commands.run(
      `codex exec resume ${threadId} --full-auto --skip-git-repo-check "Now implement step 1 of the plan"`,
      {
        onStdout: (data) => process.stdout.write(data),
        timeoutMs: 0,
      }
    )
    console.log(followUp)
  } finally {
    await sandbox.kill()
    console.log('Sandbox killed')
  }
  ```
</CodeGroup>


# OpenAI Agents API
Source: https://docs.novita.ai/guides/sandbox-coding-agent-openai-agents-api



The Agents API gives your application access to the Codex harness through an OpenAI-managed API.

OpenAI manages sessions, orchestration, context compaction, and recovery while your application provides tools and chooses its execution environment.

Agents can operate in a sandbox where they can execute code, edit files, connect to MCP servers, and produce artifacts. For the full API surface, see the [official Agents API documentation](https://developers.openai.com/api/docs/guides/agents-api/overview).

This guide walks you through using Novita Sandbox as the execution environment for the OpenAI Agents API.

***

## Application managed

### Prerequisites

* **OpenAI Agent Environment** — create one on the [Environments tab](https://platform.openai.com/agents?tab=environments) and save its API key as `OPENAI_ENVIRONMENT_KEY`.
* **Novita Sandbox** — install the [SDK or CLI](/guides/sandbox-installation), then set `NOVITA_API_KEY` in your environment.

### Create an Agent

Go to the [Agents page](https://platform.openai.com/agents) and create a new Agent. This Agent is what your application will invoke through the Agents API.

Give the Agent a name and description, then pick a model under **Model**. The values shown below are the ones used throughout this guide; the model and reasoning settings are yours to choose, but keep the Agent's description focused on operating inside a sandbox.

<Frame>
  <img />
</Frame>

### Start a Session

On the [Sessions tab](https://platform.openai.com/agents?tab=sessions), start a new Session. Select **Self-hosted** as the execution mode — this tells OpenAI to connect to an environment that you host and manage, which is where Novita Sandbox comes in.

Choose the Agent you just created and set the workspace directory (defaults to `/workspace`). This directory must exist inside your sandbox.

<Frame>
  <img />
</Frame>

#### Connect environment

Once the Session is created, open it and click **Connect environment**. The dialog walks you through connecting your self-hosted environment to the Session, starting with the code block you'll run later.

Save the **Connect an environment** command from that dialog — you will run it inside your sandbox in [Run the Executor](#run-the-executor). It looks like this:

```bash theme={"system"}
CODEX_API_KEY="$OPENAI_ENVIRONMENT_KEY" \
  codex exec-server \
    --remote '<remote_url>' \
    --environment-id '<environment_id>'
```

The `--remote` and `--environment-id` values are specific to this Session, so copy them from the dialog rather than from this page.

<Frame>
  <img />
</Frame>

### Launch the Sandbox

<Note>
  Only [v2 regions](/guides/sandbox-overview#regions) are supported.
</Note>

Next, start a sandbox from the [`openai-agents-api-executor`](https://github.com/novitalabs/openai-agents-api-executor) template with the Novita CLI. This template provides the self-hosted environment your session connects to.

```bash CLI icon="terminal" theme={"system"}
novita sandbox create openai-agents-api-executor --long-running --timeout 720h
```

By default a sandbox lives for at most one hour. `--long-running` lifts that cap so the sandbox can stay alive for the `--timeout` you set — here 720 hours (30 days), which lets the Session stay connected to the same environment without you having to recreate it.

<Note>
  A sandbox's own filesystem is tied to that sandbox, so it disappears with it. If you want your code and data to survive beyond the sandbox — and be readable by other agents — manage your code with Git, and/or mount a [volume](/guides/sandbox-volume) to keep the data outside the sandbox and share it across sandboxes.
</Note>

### Run the Executor

Start the Codex executor inside the sandbox, pointing it at your Session. This is the **Connect an environment** command you saved from [Connect environment](#connect-environment):

```bash theme={"system"}
CODEX_API_KEY="$OPENAI_ENVIRONMENT_KEY" \
  codex exec-server \
    --remote '<remote_url>' \
    --environment-id '<environment_id>'
```

`--remote` and `--environment-id` are specific to the Session you created — copy both values from its **Connect environment** dialog. `CODEX_API_KEY` is the API key of the OpenAI Agent Environment you created in the prerequisites.

### Confirm the Connection

The Session is now wired up. In the Session page, send the agent a message asking it to report its runtime environment — for example:

```text theme={"system"}
Check the runtime environment on my machine.
```

The agent inspects the workspace and reports back. If the setup is correct, it describes the environment **inside your sandbox** rather than your local machine — in the example below, Debian GNU/Linux 12 (Bookworm) on kernel Linux 6.1.158, x86\_64 — which confirms the Session is executing through the Novita Sandbox you launched.

<Frame>
  <img />
</Frame>

***

## Webhook managed

Novita Sandbox follows OpenAI's best practices and provides a long-running controller, [`openai-agents-api-executor`](https://github.com/novitalabs/openai-agents-api-executor), which manages the relationship between Sessions and sandboxes for you.

Compared with [Application managed](#application-managed), there is no manual sandbox creation and no `codex exec-server` registration step — Sessions are connected to Novita-managed environments automatically as they run.

### Prerequisites

#### Clone the executor repository

Clone the executor repository, which holds the webhook controller and client you deploy in the steps below:

```bash theme={"system"}
git clone git@github.com:novitalabs/openai-agents-api-executor.git
```

#### Envs

Create a `.env` file from the repository's [`.env.example`](https://github.com/novitalabs/openai-agents-api-executor/blob/main/.env.example), then fill in the variables below:

```bash theme={"system"}
cp .env.example .env
```

* **Novita Sandbox** — install the [SDK or CLI](/guides/sandbox-installation), then set `NOVITA_API_KEY` in your environment.
* **OpenAI application key** — create one on the [API keys page](https://platform.openai.com/api-keys), then set `OPENAI_API_KEY` to it. This key needs the `api.agents.read`, `api.agents.write`, and `api.responses.write` scopes; it stays in your application and never enters the sandbox.
* **OpenAI Agent Environment** — create one on the [Environments tab](https://platform.openai.com/agents?tab=environments) with every other permission set to `None`, and save its API key as `OPENAI_EXECUTOR_API_KEY`. This is the only key that enters the sandbox.
* **Agent ID** — set `OPENAI_AGENT_ID` to the ID printed by `--create-agent` in [Create the Agent](#create-the-agent). The controller serves only that Agent and ignores Sessions belonging to other Agents.
* **Webhook signing secret** — set `OPENAI_WEBHOOK_SECRET`, which you get when you [register the webhook](#register-the-webhook). Leave it empty for the first deploy, then fill it in and redeploy.

### Create the Agent

Create the Agent the controller will serve:

```bash theme={"system"}
npm run webhook:client -- --create-agent <name>
```

It prints the new agent ID:

```json theme={"system"}
{"agent_id":"agent_1c0960ecfe5e41bb8d289525a2446c72d2d44445f38647ff92"}
```

Put that ID in `.env` as `OPENAI_AGENT_ID`. Pass `--instructions` and `--model` to give the Agent its own role; `--update-agent <id>` edits them in place, which keeps the agent ID and so needs no redeploy.

### Get the Webhook URL

Deploy the controller, which receives OpenAI webhooks and starts or resumes a worker sandbox per Session:

```bash theme={"system"}
npm run webhook:deploy
```

It prints the webhook URL exposed by the controller's sandbox:

```text theme={"system"}
Reusing controller sandbox ir8oymt3mp5hu1qcfkjyl

Webhook: https://8000-ir8oymt3mp5hu1qcfkjyl.us-phx-1.sandbox.novita.ai/webhook
Controller log: /app/controller.log (inside ir8oymt3mp5hu1qcfkjyl)

Next: register that URL in the OpenAI platform under the project that owns
OPENAI_API_KEY, for these events:
  - agent.session.action_required
  - agent.session.failed
Then put the signing secret in OPENAI_WEBHOOK_SECRET and redeploy.

Until then the controller answers 503 "Webhook not configured".
```

The controller's sandbox ID is saved to `.controller.json`. Rerunning `webhook:deploy` reconnects to the same controller and restarts it with fresh environment variables. If that sandbox is gone, a new one is created and the URL changes, so the webhook registered in the OpenAI platform has to be updated to match.

### Register the Webhook

In the OpenAI platform, open **Project settings → Webhooks** and add a webhook pointing at the URL printed above. Subscribe it to these two events:

* `agent.session.action_required`
* `agent.session.failed`

<Frame>
  <img />
</Frame>

Register it in the project that owns `OPENAI_API_KEY` — a webhook registered in another project of the same organization is never delivered.

### Redeploy the Controller

Add the signing secret to `.env` as `OPENAI_WEBHOOK_SECRET`, then redeploy so the controller picks it up:

```bash theme={"system"}
npm run webhook:deploy
```

### Send Input

Send the Agent a message through the client, which talks to the Agent running in the worker sandbox:

```bash theme={"system"}
npm run webhook:client -- --input "Check the runtime environment on my machine."
```

The agent inspects its workspace and reports the environment back. If the setup is correct, it describes the environment **inside the sandbox** — confirming the Session is executing through a Novita-managed worker, with no sandbox of your own to create or register.

<Frame>
  <img />
</Frame>

The first line of output is the session ID; pass it back with `--session-id` to continue in the same workspace:

```bash theme={"system"}
npm run webhook:client -- --session-id "$SESSION_ID" --input "Read it again."
```


# Connect to sandbox
Source: https://docs.novita.ai/guides/sandbox-connect



You can use the `novita.sandbox.connect()` method to connect to a running or paused sandbox and continue controlling it with the SDK.

This is useful for reusing the same sandbox instance across serverless function invocations, restoring a user's web app session, reconnecting to long-running work, or resuming a paused sandbox after a short period of inactivity.

## Connect to the sandbox

Before connecting, you need the sandbox ID. You can obtain it with `novita.sandbox.list()`, then pass it to `novita.sandbox.connect()`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita, SandboxState } from 'novita-sandbox'

  const novita = new Novita()

  // --- Get the sandbox ID ---
  // Get all running sandboxes
  const runningSandboxesPaginator = await novita.sandbox.list({
    query: {
      state: [SandboxState.RUNNING],
    },
  })

  const runningSandboxes = await runningSandboxesPaginator.nextItems()
  if (runningSandboxes.length === 0) {
    throw new Error('No running sandboxes found')
  }
  const runningSandboxId = runningSandboxes[0].sandboxId

  // --- Connect to the sandbox ---
  const sandbox = await novita.sandbox.connect(runningSandboxId)
  console.log('connected to sandbox: ', sandbox.sandboxId)

  // Now you can use the sandbox as usual
  // ...
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxQuery, SandboxState

  novita = Novita()

  # --- Get the sandbox ID ---
  # Get all running sandboxes
  running_sandboxes_paginator = novita.sandbox.list(query=SandboxQuery(state=[SandboxState.RUNNING]))

  running_sandboxes = running_sandboxes_paginator.next_items()
  if len(running_sandboxes) == 0:
      raise Exception("No running sandboxes found")
  running_sandbox_id = running_sandboxes[0].sandbox_id

  # --- Connect to the sandbox ---
  sandbox = novita.sandbox.connect(running_sandbox_id)
  print("connected to sandbox: ", sandbox.sandbox_id)

  # Now you can use the sandbox as usual
  # ...

  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Attach an interactive terminal to a running sandbox (alias: sandbox cn)
  novita sandbox connect <sandboxID>
  ```
</CodeGroup>


# Web Console
Source: https://docs.novita.ai/guides/sandbox-console



[Agent Sandbox console](https://novita.ai/zh/sandbox-console/view) helps you view and manage sandboxes, templates, and usage statistics.

## Sandbox Management

* View currently running sandboxes.
* Search for a sandbox by ID.
* Filter sandboxes by creation time, template, vCPUs, memory, and other attributes.

<img alt="Sandbox Management" />

## Template Management

* Display the list of available templates, including official templates provided by Novita and your [custom templates](/guides/sandbox-template-build-operations).
* Support searching for a template by ID.
* Support filtering templates by visibility (public, private, or all), vCPUs, and memory.

<img alt="Template Management" />

## Usage Statistics

The console provides daily resource usage and cost analysis to help you monitor and optimize your consumption.

* **vCPU Hours**\
  Displays the total vCPU runtime across all sandboxes for the current day (unit: hours).

  * This data is calculated with second-level precision and can help you evaluate compute usage.
* **Memory Hours**\
  Displays the total memory usage across all sandboxes for the current day (unit: GiB-hours).

  * The system calculates this based on each sandbox's allocated memory and runtime.
* **Usage Costs**\
  Displays the total resource cost for the current day (unit: USD).

  * This includes vCPU and memory costs. For detailed billing rules, see [Pricing](/guides/sandbox-pricing).
* All resources are billed by the second and displayed at daily granularity.
* The chart shows usage trends over the past 30 days and can be used to monitor changes in resource consumption.
* Usage data may not include the most recent usage and may differ slightly from your final bill. Final charges are based on the [Billing Details](https://novita.ai/billing/details) page.

<img alt="Usage Statistics" />


# Create Sandbox
Source: https://docs.novita.ai/guides/sandbox-create



## Create Sandbox

The simplest way to create a sandbox is to call `novita.sandbox.create()` without any arguments.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import 'dotenv/config'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from dotenv import load_dotenv
  from novita_sandbox import Novita

  load_dotenv()

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a sandbox from the base template and connect a terminal (alias: sandbox cr)
  novita sandbox create base

  # Create without connecting a terminal (detached), returns the sandbox ID
  novita sandbox create base -d
  ```
</CodeGroup>

## From Template

You can create a sandbox from a template by passing the template name or ID.

```python Python icon="python" theme={"system"}
from dotenv import load_dotenv
from novita_sandbox import Novita

load_dotenv()

novita = Novita()

# Create a sandbox from a template name or template ID.
sandbox = novita.sandbox.create(
    "my-python-app",   # template name or ID
    timeout=300,        # seconds, optional
    metadata={"env": "demo"},
    envs={"KEY": "value"},
)

print("Sandbox ID:", sandbox.sandbox_id)

# Use it.
result = sandbox.commands.run("python3 --version")
print(result.stdout)

sandbox.kill()
```

Use a context manager for automatic cleanup:

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  with novita.sandbox.create("my-python-app") as sandbox:
      print(sandbox.commands.run("echo hello").stdout)
  # Automatically killed when exiting the with block.
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import 'dotenv/config'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Create a sandbox from a template name or template ID.
  const sandbox = await novita.sandbox.create('my-python-app', {
    timeoutMs: 300_000, // milliseconds, optional
    metadata: { env: 'demo' },
    envs: { KEY: 'value' },
  })

  console.log('Sandbox ID:', sandbox.sandboxId)

  // Use it.
  const result = await sandbox.commands.run('python3 --version')
  console.log(result.stdout)

  await sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a sandbox from a specific template (by name or template ID)
  novita sandbox create <template>

  # Detached (no terminal), prints the new sandbox ID
  novita sandbox create <template> -d
  ```
</CodeGroup>

## Environment Variables

You can set environment variables when creating a sandbox, and override or append them on a single command.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from dotenv import load_dotenv
  from novita_sandbox import Novita

  load_dotenv()

  novita = Novita()

  # Set environment variables when creating the sandbox.
  sandbox = novita.sandbox.create(
      "my-python-app",
      envs={
          "API_KEY": "secret-123",
          "LOG_LEVEL": "debug",
          "PORT": "8000",
      },
  )

  # Verify.
  result = sandbox.commands.run("echo $API_KEY")
  print(result.stdout)   # secret-123

  # You can also override/append envs on a single command.
  result = sandbox.commands.run(
      "echo $LOG_LEVEL",
      envs={"LOG_LEVEL": "info"},
  )
  print(result.stdout)   # info

  sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import 'dotenv/config'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Set environment variables when creating the sandbox.
  const sandbox = await novita.sandbox.create('my-python-app', {
    envs: {
      API_KEY: 'secret-123',
      LOG_LEVEL: 'debug',
      PORT: '8000',
    },
  })

  // Verify.
  let result = await sandbox.commands.run('echo $API_KEY')
  console.log(result.stdout) // secret-123

  // You can also override/append envs on a single command.
  result = await sandbox.commands.run('echo $LOG_LEVEL', {
    envs: { LOG_LEVEL: 'info' },
  })
  console.log(result.stdout) // info

  await sandbox.kill()
  ```
</CodeGroup>

## Metadata

Sandbox metadata lets you attach custom key-value information to a sandbox.

Common uses include:

* Connecting a sandbox to a user session.
* Saving custom user-related data for a sandbox, such as API keys.
* Linking a sandbox to a user ID so it can be reconnected later.

Metadata is provided when the sandbox is created. Later, it can be read when listing active sandboxes with `novita.sandbox.list()`. Sandboxes can also be filtered using metadata when listing.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita, SandboxState } from 'novita-sandbox'

  const novita = new Novita()

  // Start a sandbox and attach custom metadata.
  const sbx = await novita.sandbox.create({
    metadata: {
      userId: '123',
    },
  })

  // Retrieve active sandboxes and inspect their metadata.
  const page = await novita.sandbox.list()
  const active = await page.nextItems()

  console.log(active[0].metadata)
  // Example output:
  // { userId: '123' }

  // Filter by metadata.
  let sandboxes = await novita.sandbox.list({
    query: { metadata: { env: 'demo' } },
  })

  // Combine metadata and state filters (multiple conditions are AND).
  sandboxes = await novita.sandbox.list({
    query: {
      metadata: { env: 'demo' },
      state: [SandboxState.RUNNING],
    },
  })

  for (const sandbox of sandboxes) {
    console.log(sandbox.sandboxId, sandbox.state, sandbox.metadata)
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxQuery, SandboxState

  novita = Novita()

  # Start a sandbox and attach custom metadata.
  sbx = novita.sandbox.create(
      metadata={
          "userId": "123",
      },
  )

  # Retrieve active sandboxes and inspect their metadata.
  page = novita.sandbox.list()
  active = page.next_items()

  print(active[0].metadata)
  # Example output:
  # {"userId": "123"}

  # Filter by metadata.
  sandboxes = novita.sandbox.list(
      query=SandboxQuery(metadata={"env": "demo"})
  )

  # Combine metadata and state filters (multiple conditions are AND).
  sandboxes = novita.sandbox.list(
      query=SandboxQuery(
          metadata={"env": "demo"},
          state=[SandboxState.RUNNING],
      )
  )

  for sandbox in sandboxes:
      print(sandbox.sandbox_id, sandbox.state, sandbox.metadata)
  ```
</CodeGroup>


# Sandbox Events
Source: https://docs.novita.ai/guides/sandbox-events



Sandbox events provide a queryable timeline of key sandbox lifecycle operations. You can use events for troubleshooting, auditing, usage analysis, and billing reconciliation.

## Query Sandbox Events

Use the events API to list sandbox events for your account. You can filter events by sandbox, template, event type, status, and time range.

### Query events using the SDKs

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const result = await novita.sandbox.getEvents({
    sandboxID: 'iq721h44siv24wmyrh5oc',
    events: ['create', 'pause', 'resume', 'connect', 'timeout', 'delete'],
    limit: 10,
    offset: 0,
  })

  console.log('Events:', result.items)

  if (result.hasMore) {
    const nextPage = await novita.sandbox.getEvents({
      sandboxID: 'iq721h44siv24wmyrh5oc',
      limit: 10,
      offset: 10,
    })

    console.log('Next page:', nextPage.items)
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  result = novita.sandbox.get_events(
      sandbox_id="iq721h44siv24wmyrh5oc",
      events=["create", "pause", "resume", "connect", "timeout", "delete"],
      limit=10,
      offset=0,
  )

  print("Events:", result.items)

  if result.has_more:
      next_page = novita.sandbox.get_events(
          sandbox_id="iq721h44siv24wmyrh5oc",
          limit=10,
          offset=10,
      )

      print("Next page:", next_page.items)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # List events for a sandbox
  novita sandbox events <sandboxID>

  # Filter by event names / state
  novita sandbox events <sandboxID> --events create,pause,resume --state success
  ```
</CodeGroup>

### Query events for a sandbox instance

If you already have a sandbox instance, use the instance-level events method to query events for that sandbox.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()
  const result = await sandbox.getEvents({
    events: ['create', 'pause', 'resume', 'connect', 'timeout', 'delete'],
    limit: 10,
  })

  console.log(result.items)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()
  result = sandbox.get_events(
      events=["create", "pause", "resume", "connect", "timeout", "delete"],
      limit=10,
  )

  print(result.items)
  ```
</CodeGroup>

## Filter Events

Use query parameters to narrow the event timeline.

| Option | Type | Required | Default / Limit | Description |
| - | - | - | - | - |
| `startTime` | `int64` | No | Defaults to `endTime - 30 days` | Query start time as a Unix timestamp in seconds. The range is inclusive. |
| `endTime` | `int64` | No | Defaults to the current time | Query end time as a Unix timestamp in seconds. Must be greater than `startTime`. |
| `sandboxID` | `string` | No | - | Filter by sandbox ID. Partial matching is supported. |
| `templateID` | `string` | No | - | Filter by template ID. Partial matching is supported. |
| `events` | `string` | No | Defaults to the key event set | Comma-separated event types. The current key event set is `create`, `pause`, `resume`, `connect`, `timeout`, and `delete`. |
| `state` | `string` | No | - | Filter by event status, such as `success` or a failure status. |
| `orderAsc` | `bool` | No | `false` | Sort events by `recordAt` in ascending order. By default, events are returned in descending order. |
| `offset` | `int32` | No | Default `0`, minimum `0` | Number of events to skip. |
| `limit` | `int32` | No | Default `10`, minimum `1`, maximum `100` | Maximum number of events to return. |

Time fields use Unix timestamps in seconds. Do not pass millisecond timestamps. The maximum query time range is 60 days. For longer timelines, split requests into multiple smaller time ranges.

## Pagination

Events use offset-based pagination. Start with `offset: 0`; if `hasMore` is `true`, request the next page with `offset + limit`. The maximum `limit` is 100.

## Response Fields

The events response includes an `items` array and pagination metadata.

| Field | Description |
| - | - |
| `items` | List of sandbox events that match the query. |
| `total` | Total number of events that match the query, before pagination. |
| `hasMore` | Whether more events are available after the current page. |

Each item in `items` has the following fields.

| Field | Description |
| - | - |
| `eventID` | Unique event ID. |
| `recordAt` | Event record time as a Unix timestamp in seconds. |
| `templateID` | Template ID used by the sandbox. |
| `templateName` | Template name used by the sandbox. |
| `sandboxID` | Sandbox ID. |
| `eventName` | Event type, such as `create`, `pause`, `resume`, `connect`, `timeout`, or `delete`. |
| `state` | Event status. |
| `errorMsg` | Error message when the event failed or encountered an exception. Usually empty when there is no error. |
| `statusCode` | HTTP status code associated with the operation, when available. |

## Example Response

```json JSON icon="code" theme={"system"}
{
  "items": [
    {
      "eventID": "evt_123",
      "recordAt": 1766650000,
      "templateID": "tmpl_abc",
      "templateName": "ubuntu-base",
      "sandboxID": "i3vrzzatkxojecvfuesnz",
      "eventName": "create",
      "state": "success",
      "errorMsg": "",
      "statusCode": 200
    }
  ],
  "total": 1,
  "hasMore": false
}
```


# Idle timeout
Source: https://docs.novita.ai/guides/sandbox-idle-timeout



You can configure an idle timeout for your sandboxes to automatically stop or pause when no active connections are detected.

The idle timeout is configured via the `metadata` field when creating a sandbox. The key is `idle_timeout` and the value is the number of seconds as a string.

## Basic usage

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Create a sandbox that will be automatically killed after 60 seconds of inactivity.
  const sandbox = await novita.sandbox.create({
    metadata: {
      idle_timeout: '60',
    },
  })

  // The sandbox is running...
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # Create a sandbox that will be automatically killed after 60 seconds of inactivity.
  sandbox = novita.sandbox.create(
      metadata={
          'idle_timeout': '60',
      },
  )

  # The sandbox is running...
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a sandbox that will be automatically killed after 60 seconds of inactivity (detached mode).
  novita sandbox create base --idle-timeout 60 -d
  ```
</CodeGroup>

## Pausing instead of killing

If you want the sandbox to be paused instead so you can resume it later, enable the auto-pause option.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const sandbox = await novita.sandbox.create({
    metadata: {
      idle_timeout: '60',
    },
    autoPause: true,
  })

  // After 60 seconds of inactivity, the sandbox will be paused.
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  sandbox = novita.sandbox.create(
      metadata={
          'idle_timeout': '60',
      },
      auto_pause=True,
  )

  # After 60 seconds of inactivity, the sandbox will be paused.
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # --on-timeout pause is equivalent to the SDK's auto_pause option
  novita sandbox create base --idle-timeout 60 --on-timeout pause -d
  ```
</CodeGroup>

## Combining with other metadata

The `idle_timeout` metadata key can be combined with other metadata keys you may already use.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const sandbox = await novita.sandbox.create({
    metadata: {
      idle_timeout: '120',
      env: 'production',
      userId: 'user-123',
    },
  })

  console.log('Sandbox ID:', sandbox.sandboxId)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  sandbox = novita.sandbox.create(
      metadata={
          'idle_timeout': '120',
          'env': 'production',
          'user_id': 'user-123',
      },
  )

  print('Sandbox ID:', sandbox.sandbox_id)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # --idle-timeout can be combined with other --metadata keys
  novita sandbox create base --idle-timeout 120 \
    --metadata env=production \
    --metadata user_id=user-123 \
    -d
  ```
</CodeGroup>

## Disabling idle timeout

To explicitly disable the idle timeout for a sandbox, simply omit the `idle_timeout` key from the metadata, or set it to `0`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // No idle timeout — sandbox runs until its maximum lifetime.
  const sandbox = await novita.sandbox.create({
    metadata: {
      idle_timeout: '0',
    },
  })

  // Alternatively, omit idle_timeout entirely.
  const sandbox2 = await novita.sandbox.create()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # No idle timeout — sandbox runs until its maximum lifetime.
  sandbox = novita.sandbox.create(
      metadata={
          'idle_timeout': '0',
      },
  )

  # Alternatively, omit idle_timeout entirely.
  sandbox2 = novita.sandbox.create()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # No idle timeout — sandbox runs until its maximum lifetime.
  novita sandbox create base --idle-timeout 0 -d

  # Alternatively, omit --idle-timeout entirely.
  novita sandbox create base -d
  ```
</CodeGroup>


# Installing
Source: https://docs.novita.ai/guides/sandbox-installation



Novita Sandbox provides official SDKs for multiple programming languages, along with a CLI for performing sandbox operations from the terminal.

Currently supported:

* Python
* JavaScript & TypeScript
* CLI

You can install the Novita SDK for application code and the CLI for terminal-based sandbox operations.

## Install SDK

You can install the Novita SDK by executing the following commands.

<Note>
  **Prerequisites:**

  * JS: Node.js 20+
  * Python: Python 3.9+
</Note>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  npm i novita-sandbox
  ```

  ```python Python icon="python" theme={"system"}
  pip install novita-sandbox
  ```
</CodeGroup>

## Install CLI

The Novita Sandbox CLI helps you manage sandboxes, run commands, build templates, and perform common development tasks directly from the terminal.

```bash CLI icon="terminal" theme={"system"}
npm i -g novita-sandbox-cli
```

## Install Agent Skills

If your agent supports skills, install the Novita Sandbox skill with the following command:

```bash theme={"system"}
npx skills add novitalabs/novita-skills --skill novita-sandbox-skills
```

[View the skill on GitHub →](https://github.com/novitalabs/novita-skills).


# Browser Use Integration
Source: https://docs.novita.ai/guides/sandbox-integrations-browser-use



[BrowserUse](https://github.com/browser-use/browser-use) is a powerful AI browser agent. Combined with the  isolated sandbox environment provided by Novita Sandbox, you can build high-concurrency, multi-task browser AI agents.

This document provides detailed instructions on how to run BrowserUse projects based on Novita Sandbox service.

The document uses the `browser-chromium` sandbox template released by Novita. If you want to create your own template based on this or view more complete example code, visit [example](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/browser-use) in github.

### 1. Get Novita API Key

### 2. Install Dependencies

Install the required Python packages:

```bash CLI icon="terminal" theme={"system"}
pip install browser-use
```

### 3. Example Code

Before getting started, you need to configure the necessary environment variables:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=""
export LLM_API_KEY=""
export LLM_BASE_URL=https://api.novita.ai/openai
export LLM_MODEL=""
```

Save the following code:

**agent.py**

```python Python icon="python" theme={"system"}
import asyncio
import base64
import os
import time

import dotenv
dotenv.load_dotenv(override=True)

from browser_use import Agent, BrowserSession
from browser_use.llm import ChatOpenAI
from novita_sandbox import Novita

async def screenshot(agent: Agent):
    # Screenshot function
    print("Taking screenshot...")
    page = await agent.browser_session.get_current_page()
    screenshot_bytes = await page.screenshot(format='png')
    # screenshot method returns the binary data of the image, we should save it as a PNG file
    screenshots_dir = os.path.join(".", "screenshots")
    os.makedirs(screenshots_dir, exist_ok=True)
    screenshot_path = os.path.join(screenshots_dir, f"{time.time()}.png")
    if isinstance(screenshot_bytes, str):
        screenshot_data = base64.b64decode(screenshot_bytes)
    else:
        screenshot_data = screenshot_bytes
    with open(screenshot_path, "wb") as f:
        f.write(screenshot_data)
    print(f"Screenshot saved to {screenshot_path}")

async def main():
    novita = Novita(api_key=os.environ["NOVITA_API_KEY"])

    # Create Novita sandbox instance
    sandbox = novita.sandbox.create(
        "browser-chromium",  # This template contains chromium browser and exposes port 9223 for remote connection
        timeout=600,  # Timeout in seconds
    )
    try:
        # Get Chrome debug port address from sandbox
        host = sandbox.get_host(9223)  # Get sandbox port 9223 address
        cdp_url = f"https://{host}"
        print(f"Chrome Debug Protocol URL: {cdp_url}")
        # Create BrowserUse session
        browser_session = BrowserSession(cdp_url=cdp_url)
        await browser_session.start()
        print("BrowserUse session created successfully")
        # Create AI Agent
        agent = Agent(
            task="Go to hackernews and find the top 3 stories",
            llm=ChatOpenAI(
                api_key=os.getenv("LLM_API_KEY"),
                base_url=os.getenv("LLM_BASE_URL"),
                model=os.getenv("LLM_MODEL"),
                temperature=1
            ),
            browser_session=browser_session,
        )
        # Run Agent task
        print("Starting Agent task execution...")
        await agent.run(
            on_step_end=screenshot,  # Take screenshot after each step
        )
        # Close browser session
        await browser_session.kill()
        print("Task execution completed")
    finally:
        # Clean up sandbox resources
        sandbox.kill()
        print("Sandbox resources cleaned up")

if __name__ == "__main__":
    asyncio.run(main())
```

### 4. Run the Agent

After Installing the dependencies and setting up the environment variables, you can run the example code. You can see the output in the terminal as below if everything goes well. Browser-use is running tasks in the remote browser inside your sandbox.

<img alt="Output" />

It will generate screenshots like below:

<img alt="screenshot1" />

To run a more complete demo, please refer to [here](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/browser-use).


# VNC Desktop
Source: https://docs.novita.ai/guides/sandbox-integrations-desktop



***Novita Desktop***  provides an isolated virtual desktop environment for building and testing Computer Use Agents. This solution provides a comprehensive, isolated desktop environment that enables AI agents to securely interact with desktop applications through controlled virtualization.  This guide explains how to run Novita Desktop on the Novita Sandbox service.

***

### 1. Get Novita API Key

### 2. Environment Configuration

Before you start using the service, you need to configure the necessary environment variables:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<Your API key obtained in the previous step>
```

### 3. SDK Installation

Select the appropriate installation method corresponding to your development environment and programming language:

```bash CLI icon="terminal" theme={"system"}
npm i novita_sandbox
```

```bash CLI icon="terminal" theme={"system"}
pip install novita_sandbox
```

### 4. Implementation Examples

#### Get Virtual Desktop Stream VNC URL

The following implementation demonstrates the instantiation of a virtual desktop environment and the retrieval of VNC access endpoints for remote desktop interaction:

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  // demo.ts implementation
  import { Novita } from 'novita-sandbox'

  async function main() {
    const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })

    // Initialize virtual desktop sandbox instance
    const desktop = await novita.desktop.create({ timeoutMs: 3_600_000 })

    // Activate desktop streaming service
    await desktop.stream.start()

    // Retrieve interactive VNC endpoint URL
    const url = desktop.stream.getUrl()
    console.log(url)

    // Expected output format:
    // Browser-accessible URL for interactive virtual desktop session. Suitable for application integration.
    // https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&resize=scale

    // Retrieve read-only VNC endpoint URL (interaction disabled)
    const urlDisabledInteraction = desktop.stream.getUrl({ viewOnly: true })
    console.log(urlDisabledInteraction)
    // Expected output format:
    // Browser-accessible URL for view-only virtual desktop session (non-interactive mode). Suitable for monitoring applications.
    // https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&view_only=true&resize=scale

    // Keep the program running
    console.log("Desktop stream started, press Ctrl+C to stop the program...")
    const interval = setInterval(() => {}, 1000)

    // Register interrupt signal handlers for resource cleanup
    let isCleaning = false
    const cleanup = async () => {
      if (isCleaning) return
      isCleaning = true

      console.log("\\nProgram interrupted, cleaning up resources...")
      clearInterval(interval)
      try {
        await desktop.stream.stop() // Terminate streaming service
        await desktop.kill()        // Deallocate sandbox instance
      } catch (err) {
        console.error("Error cleaning up resources:", err)
      }
      process.exit(0)
    }

    process.on('SIGINT', cleanup)
    process.on('SIGTERM', cleanup)
  }

  main().catch(console.error)
  ```

  ```python Python icon="python" theme={"system"}
  # demo.py implementation
  import os
  import time

  from novita_sandbox import Novita

  novita = Novita(api_key=os.environ["NOVITA_API_KEY"])

  # Initialize virtual desktop sandbox instance
  desktop = novita.desktop.create(timeout=3600)

  # Activate desktop streaming service
  desktop.stream.start()

  # Retrieve interactive VNC endpoint URL
  url = desktop.stream.get_url()
  print(url)
  # Expected output format:
  # Browser-accessible URL for interactive virtual desktop session. Suitable for application integration.
  # https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&resize=scale

  # Retrieve read-only VNC endpoint URL (interaction disabled)
  url_readonly = desktop.stream.get_url(view_only=True)
  print(url_readonly)
  # Expected output format:
  # Browser-accessible URL for view-only virtual desktop session (non-interactive mode). Suitable for monitoring applications.
  # https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&view_only=true&resize=scale

  # Implement keyboard interrupt handling for graceful shutdown
  try:
      print("Desktop stream started, press Ctrl+C to stop the program...")
      while True:
          time.sleep(0.1)
  except KeyboardInterrupt:
      print("\\nProgram interrupted, cleaning up resources...")

  # Execute resource deallocation procedures
  desktop.stream.stop()  # Terminate streaming service
  desktop.kill()        # Deallocate sandbox instance
  ```
</CodeGroup>

Running example:

<img alt="Integrations Desktop" />

Access virtual desktop stream VNC URL:

<img alt="Integrations Desktop 2" />


# Harbor Integration
Source: https://docs.novita.ai/guides/sandbox-integrations-harbor



[Harbor](https://github.com/harbor-framework/harbor) is an open-source framework for evaluating and optimizing agents and language models in sandboxed environments. It can run benchmark tasks against different agents and execution backends, collect trajectories, verify task results, and summarize benchmark scores.

Novita Sandbox is integrated into Harbor as a cloud environment provider. After installing Harbor with the Novita extra, users can run Harbor tasks on Novita-hosted sandboxes by selecting `--env novita`. The Harbor CLI still runs on your local machine or server, while the task environment, agent process, command execution, and verification run inside the Novita Sandbox.

Install Harbor with the `novita` extra so the Novita Sandbox SDK and related dependencies are available.

```bash CLI icon="terminal" theme={"system"}
uv tool install 'harbor[novita]'

# Or, if you use pip:
pip install 'harbor[novita]'
```

Harbor requires **Python 3.12** or later. The `[novita]` extra installs the optional dependencies needed by Harbor's Novita environment provider; it does not change the Harbor command syntax.

Set `NOVITA_API_KEY` so Harbor can create Novita templates and sandboxes. If you also want the agent to call Novita's OpenAI-compatible LLM API, pass the same key through the OpenAI-compatible environment variables.

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<YOUR_NOVITA_API_KEY>
export OPENAI_API_KEY="$NOVITA_API_KEY"
export OPENAI_BASE_URL="https://api.novita.ai/openai"
export OPENAI_API_BASE="https://api.novita.ai/openai"
```

`OPENAI_BASE_URL` is used by OpenAI-compatible clients. `OPENAI_API_BASE` is included because some agent runtimes and LiteLLM-based tools read that variable name instead.

The following command runs Harbor's registry dataset `hello-world@1.0` on Novita Sandbox using `mini-swe-agent` and the Novita model `moonshotai/kimi-k3`. This example does not require cloning the Harbor source repository.

```bash CLI icon="terminal" theme={"system"}
harbor run \
  --dataset hello-world@1.0 \
  --n-tasks 1 \
  --agent mini-swe-agent \
  --model openai/moonshotai/kimi-k3 \
  --n-concurrent 1 \
  --env novita \
  --ae OPENAI_API_KEY="$OPENAI_API_KEY" \
  --ae OPENAI_BASE_URL="$OPENAI_BASE_URL" \
  --ae OPENAI_API_BASE="$OPENAI_API_BASE" \
  --agent-timeout-multiplier 5
```

You can also use Harbor as a Python package instead of shelling out to the CLI. The key setting is `EnvironmentConfig(type=EnvironmentType.NOVITA)`, which is the code equivalent of `--env novita`. Harbor still manages the dataset, agent execution, verifier, job output, and Novita Sandbox lifecycle.

```bash CLI icon="terminal" theme={"system"}
pip install "harbor[novita]"
```

The following Python script is equivalent to the CLI smoke test above:

```python Python icon="python" theme={"system"}
import asyncio
import os

from harbor.job import Job
from harbor.models.environment_type import EnvironmentType
from harbor.models.job.config import DatasetConfig, JobConfig
from harbor.models.trial.config import AgentConfig, EnvironmentConfig


async def main():
    # Harbor uses NOVITA_API_KEY on the host process to create Novita sandboxes.
    if "NOVITA_API_KEY" not in os.environ:
        raise RuntimeError("NOVITA_API_KEY is required")

    # These environment variables are passed to the agent for model calls.
    os.environ.setdefault("OPENAI_API_KEY", os.environ["NOVITA_API_KEY"])
    os.environ.setdefault("OPENAI_BASE_URL", "https://api.novita.ai/openai")
    os.environ.setdefault("OPENAI_API_BASE", "https://api.novita.ai/openai")

    config = JobConfig(
        datasets=[
            DatasetConfig(
                name="hello-world",
                version="1.0",
                n_tasks=1,
            )
        ],
        agents=[
            AgentConfig(
                name="mini-swe-agent",
                model_name="openai/moonshotai/kimi-k3",
                env={
                    "OPENAI_API_KEY": "${OPENAI_API_KEY}",
                    "OPENAI_BASE_URL": "${OPENAI_BASE_URL}",
                    "OPENAI_API_BASE": "${OPENAI_API_BASE}",
                },
            )
        ],
        environment=EnvironmentConfig(
            type=EnvironmentType.NOVITA,
        ),
        n_concurrent_trials=1,
        agent_timeout_multiplier=5,
    )

    job = await Job.create(config)
    result = await job.run()
    print(result.model_dump_json(indent=2))


if __name__ == "__main__":
    asyncio.run(main())
```

For a local task path, replace `datasets=[...]` with `tasks=[TaskConfig(path=Path("/path/to/task"))]` and import `TaskConfig` from `harbor.models.trial.config`. Keep the same Novita environment config and agent model settings.

Do not put the Novita Sandbox key only inside `AgentConfig.env`. `NOVITA_API_KEY` must be available to the host Python process so Harbor can create the sandbox. The OpenAI-compatible model settings belong in `AgentConfig.env`, because those values are forwarded to the agent running inside the sandbox.

The smoke test above uses Harbor's registry dataset, so it does not need local Harbor source code. If you use `--path`, Harbor treats the value as a local file-system path and the directory must exist on the machine running the Harbor CLI.

```bash CLI icon="terminal" theme={"system"}
harbor run \
  --path /absolute/path/to/your/harbor-task \
  --agent mini-swe-agent \
  --model openai/moonshotai/kimi-k3 \
  --n-concurrent 1 \
  --env novita \
  --ae OPENAI_API_KEY="$OPENAI_API_KEY" \
  --ae OPENAI_BASE_URL="$OPENAI_BASE_URL" \
  --ae OPENAI_API_BASE="$OPENAI_API_BASE"
```

Use `--dataset hello-world@1.0` for the simplest installed-package smoke test. Use `--path` only when you already have a local Harbor task directory.

When `--env novita` is selected, Harbor resolves the execution backend to its Novita environment provider. For a standard single-container task, Harbor builds or reuses a Novita template for the task environment, creates a Novita Sandbox from that template, uploads task files, and runs the agent and verifier commands inside the sandbox.

The agent is installed and executed inside the sandbox. On first use, agent setup may install runtime dependencies such as Python packages or Node.js tooling inside the sandbox. Harbor writes agent logs and task results back to the local `jobs/` directory after the run.

| Setting | Description |
| - | - |
| `NOVITA_API_KEY` | Required. API key used by Harbor to create Novita templates and sandboxes. |
| `OPENAI_API_KEY` | Required when the selected agent calls Novita's OpenAI-compatible LLM API. |
| `OPENAI_BASE_URL` | OpenAI-compatible base URL. Use `https://api.novita.ai/openai`. |
| `OPENAI_API_BASE` | Compatibility alias for agent runtimes that read this variable instead of `OPENAI_BASE_URL`. |
| `--env novita` | Selects Novita Sandbox as the Harbor execution environment. |
| `--ae KEY=VALUE` | Passes an environment variable to the agent process inside the sandbox. |
| `--agent-timeout-multiplier` | Optional multiplier for agent execution timeout. Increase it for slow models or slow first-time agent setup. |
| `--override-cpus` | Optional Harbor runtime override for sandbox CPU allocation. |
| `--override-memory-mb` | Optional Harbor runtime override for sandbox memory allocation in MB. |
| `--force-build` | Forces Harbor to rebuild the environment template instead of reusing a cached template. |

Use Novita Sandbox when you want Harbor workloads to run in remote, isolated environments instead of relying on local Docker capacity. This is useful for CI, shared development servers, high-concurrency benchmark runs, and experiments that need reproducible sandbox lifecycle management across many trials.


# Hugging Face OpenEnv
Source: https://docs.novita.ai/guides/sandbox-integrations-openenv



[OpenEnv](https://github.com/huggingface/OpenEnv) is an end-to-end framework for creating, deploying, and using isolated execution environments for agentic reinforcement learning (RL) training. It uses simple, Gymnasium-style APIs so training code and agents can interact with many kinds of environments through a consistent interface.

Novita Sandbox is now integrated with OpenEnv as a provider for running OpenEnv server environments in isolated cloud sandboxes.

## Run a Tbench2Env Server

The following example uses `NovitaSandboxProvider` to build and start the Tbench2 OpenEnv server in a Novita Sandbox. It then connects to the server with `Tbench2Env`, resets a task, executes commands, and releases the sandbox when the run finishes.

The example is based on [`examples/novita_tbench2_simple.py`](https://github.com/huggingface/OpenEnv/blob/main/examples/novita_tbench2_simple.py) in the OpenEnv repository.

Set `NOVITA_API_KEY` and run the example from the root of a local OpenEnv checkout:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY="<YOUR_NOVITA_API_KEY>"
PYTHONPATH=src:envs uv run python examples/novita_tbench2_simple.py
```

```python Python icon="python" theme={"system"}
import asyncio
import os

from openenv.core.containers.runtime.novita_provider import NovitaSandboxProvider
from tbench2_env import Tbench2Action, Tbench2Env


async def main() -> int:
    tasks_dir = os.environ.get("TB2_TASKS_DIR")
    if not tasks_dir:
        print("TB2_TASKS_DIR not set. TB2 repo will be downloaded.")

    task_id = os.environ.get("TB2_TASK_ID", "headless-terminal")

    image = NovitaSandboxProvider.image_from_dockerfile(
        "envs/tbench2_env/server/Dockerfile",
    )
    provider = NovitaSandboxProvider()
    # The first start builds a Novita template; later starts reuse the cache.
    base_url = provider.start_container(image=image)
    provider.wait_for_ready(base_url, timeout_s=300)

    try:
        async with Tbench2Env(base_url=base_url, provider=provider) as env:
            result = await env.reset(task_id=task_id)
            print("Instruction head:")
            print(result.observation.instruction[:200])

            result = await env.step(
                Tbench2Action(action_type="exec", command="ls -la")
            )
            print("Command output:")
            print(result.observation.output)

            result = await env.step(
                Tbench2Action(
                    action_type="exec", command="curl -v http://127.0.0.1:8000"
                )
            )
            print("Command output:")
            print(result.observation.output)
    finally:
        provider.stop_container()

    return 0


if __name__ == "__main__":
    raise SystemExit(asyncio.run(main()))
```

For a complete end-to-end evaluation, see [`examples/novita_tbench2_e2e_eval.py`](https://github.com/huggingface/OpenEnv/blob/main/examples/novita_tbench2_e2e_eval.py):

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY="<YOUR_NOVITA_API_KEY>"
export API_KEY="<YOUR_MODEL_API_KEY>"
export API_BASE_URL="<YOUR_MODEL_BASE_URL>"

PYTHONPATH=src:envs uv run python examples/novita_tbench2_e2e_eval.py \
  --task regex-log \
  --model "<YOUR_MODEL_NAME>" \
  --max-steps 20 \
  --verbose
```


# Internet access
Source: https://docs.novita.ai/guides/sandbox-internet-access



Internet access is enabled in every sandbox by default, but you can disable it for isolated workloads.

Services running inside a sandbox can also be exposed through a public URL.

## Toggling internet access

When creating a sandbox, you can use the `allowInternetAccess` / `allow_internet_access` parameter to configure internet connectivity. Internet access is turned on by default, but it can be disabled for workloads with stricter security requirements.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Create sandbox with internet access enabled (default).
  const sandbox = await novita.sandbox.create({ allowInternetAccess: true })

  // Create sandbox without internet access.
  const isolatedSandbox = await novita.sandbox.create({ allowInternetAccess: false })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # Create sandbox with internet access enabled (default).
  sandbox = novita.sandbox.create(allow_internet_access=True)

  # Create sandbox without internet access.
  isolated_sandbox = novita.sandbox.create(allow_internet_access=False)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a sandbox with internet access enabled (default behavior, no extra arguments)
  novita sandbox create base -d

  # Create a sandbox without internet access
  novita sandbox create base --no-internet -d
  ```
</CodeGroup>

## Fine-grained network control

Network configuration provides finer-grained control over outbound traffic by allowing you to define allowlists and denylists.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Deny all traffic except specific IPs.
  const sandbox = await novita.sandbox.create({
    network: {
      denyOut: ['0.0.0.0/0'],
      allowOut: ['1.1.1.1', '8.8.8.0/24'],
    },
  })

  // Allow only traffic to selected domains.
  const domainSandbox = await novita.sandbox.create({
    network: {
      allowOut: ['api.example.com', '*.github.com'],
      denyOut: ['0.0.0.0/0'],
    },
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # Deny all traffic except specific IPs.
  sandbox = novita.sandbox.create(
      network={
          'deny_out': ['0.0.0.0/0'],
          'allow_out': ['1.1.1.1', '8.8.8.0/24'],
      }
  )

  # Allow only traffic to selected domains.
  domain_sandbox = novita.sandbox.create(
      network={
          'allow_out': ['api.example.com', '*.github.com'],
          'deny_out': ['0.0.0.0/0'],
      }
  )
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Deny all traffic except the allowed IPs
  # (--allow-out / --deny-out accept comma-separated CIDR lists and can be repeated)
  novita sandbox create base -d \
    --deny-out 0.0.0.0/0 \
    --allow-out 1.1.1.1,8.8.8.0/24
  ```
</CodeGroup>

## Sandbox public URL

Services in a sandbox can be accessed by using the sandbox's public URL.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const host = sandbox.getHost(3000)
  console.log(`https://${host}`)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  host = sandbox.get_host(3000)
  print(f'https://{host}')
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a sandbox and allow public traffic to access its URL directly
  # (--public-traffic makes the sandbox URL accessible without an access token)
  novita sandbox create base --public-traffic -d
  ```
</CodeGroup>

## Connecting to a server running inside the sandbox

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const process = await sandbox.commands.run('python -m http.server 3000', { background: true })
  const url = `https://${sandbox.getHost(3000)}`
  console.log('Server started at:', url)

  const response = await fetch(url)
  const data = await response.text()
  console.log('Response from server inside sandbox:', data)

  await process.kill()
  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  import requests
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  process = sandbox.commands.run('python -m http.server 3000', background=True)
  url = f'https://{sandbox.get_host(3000)}'
  print('Server started at:', url)

  response = requests.get(url)
  data = response.text
  print('Response from server inside sandbox:', data)

  process.kill()
  sandbox.kill()
  ```
</CodeGroup>

## Masking request host headers

You can use the `maskRequestHost` / `mask_request_host` option to customize the Host header sent to services running inside the sandbox.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({
    network: {
      maskRequestHost: 'localhost:${PORT}',
    },
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(
      network={
          'mask_request_host': 'localhost:${PORT}',
      }
  )
  ```
</CodeGroup>


# Kill Sandbox
Source: https://docs.novita.ai/guides/sandbox-kill



Killing a sandbox shuts it down and permanently removes it. A killed sandbox cannot be resumed or reconnected. You can kill sandboxes from the CLI or programmatically through the SDK.

## Kill a connected sandbox

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  // ... use the sandbox ...

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  # ... use the sandbox ...

  sandbox.kill()
  ```
</CodeGroup>

## Kill by sandbox ID

Use the namespace `kill` method when you only have the sandbox ID. It returns `true` if the sandbox was found and killed, and `false` if no sandbox with that ID exists.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const killed = await novita.sandbox.kill('sbx-123')
  if (!killed) {
    console.log('Sandbox not found')
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  killed = novita.sandbox.kill("sbx-123")
  if not killed:
      print("Sandbox not found")
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Kill a sandbox by ID (alias: sandbox kl)
  novita sandbox kill <sandboxID>
  ```
</CodeGroup>

## Kill all sandboxes

The CLI can kill every running sandbox at once with `-a` / `--all`, optionally filtered by state or metadata.

```bash CLI icon="terminal" theme={"system"}
# Kill all running sandboxes
novita sandbox kill -a

# Filter which ones to kill by state or metadata
novita sandbox kill -a --state running
novita sandbox kill -a --metadata env=test
```


# List sandboxes
Source: https://docs.novita.ai/guides/sandbox-list



You can use the `novita.sandbox.list()` method to list sandboxes.

Once you have information about a running sandbox, you can [connect](/guides/sandbox-connect) to it using the `novita.sandbox.connect()` method.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita, SandboxState } from 'novita-sandbox'

  const novita = new Novita()

  // Create a sandbox.
  const sandbox = await novita.sandbox.create({
    metadata: {
      name: 'My Sandbox',
    },
  })

  // List all running sandboxes.
  const runningSandboxesPaginator = await novita.sandbox.list({
    query: {
      state: [SandboxState.RUNNING],
    },
  })

  const runningSandboxes = await runningSandboxesPaginator.nextItems()
  const runningSandbox = runningSandboxes[0]

  console.log('Running sandbox metadata:', runningSandbox.metadata)
  console.log('Running sandbox id:', runningSandbox.sandboxId)
  console.log('Running sandbox started at:', runningSandbox.startedAt)
  console.log('Running sandbox ends at:', runningSandbox.endAt)
  console.log('Running sandbox template id:', runningSandbox.templateId)
  console.log('Running sandbox state:', runningSandbox.state)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxQuery, SandboxState

  novita = Novita()

  sandbox = novita.sandbox.create(
      metadata={
          'name': 'My Sandbox',
      },
  )

  # List all running sandboxes.
  running_sandboxes_paginator = novita.sandbox.list(
      query=SandboxQuery(
          state=[SandboxState.RUNNING],
      ),
  )

  running_sandboxes = running_sandboxes_paginator.next_items()

  running_sandbox = running_sandboxes[0]
  print('Running sandbox metadata:', running_sandbox.metadata)
  print('Running sandbox id:', running_sandbox.sandbox_id)
  print('Running sandbox started at:', running_sandbox.started_at)
  print('Running sandbox end at:', running_sandbox.end_at)
  print('Running sandbox template id:', running_sandbox.template_id)
  print('Running sandbox state:', running_sandbox.state)

  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # List running sandboxes (alias: sandbox ls)
  novita sandbox list

  # Filter by metadata
  novita sandbox list --metadata key1=value1

  # JSON output
  novita sandbox list --output json
  ```
</CodeGroup>

## Filtering sandboxes

You can filter sandboxes by specifying [Metadata](/guides/sandbox-create) key-value pairs.

Specifying multiple key-value pairs returns sandboxes that match all of them.

This can be useful when you have a large number of sandboxes and want to find only specific ones. The filtering is performed on the server.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Create a sandbox with metadata.
  const sandbox = await novita.sandbox.create({
    metadata: {
      env: 'dev',
      app: 'my-app',
      userId: '123',
    },
  })

  // List all running sandboxes that have the `userId` key with value `123` and the `env` key with value `dev`.
  const runningSandboxesPaginator = await novita.sandbox.list({
    query: {
      metadata: { userId: '123', env: 'dev' },
    },
  })

  const runningSandboxes = await runningSandboxesPaginator.nextItems()
  for (const runningSandbox of runningSandboxes) {
    console.log(
      `list running sandbox (${runningSandbox.sandboxId}) metadata:`,
      runningSandbox.metadata,
    )
  }

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxQuery

  novita = Novita()

  # Create sandbox with metadata.
  sandbox = novita.sandbox.create(
      metadata={
          'env': 'dev',
          'app': 'my-app',
          'user_id': '123',
      },
  )

  # List all running sandboxes that have the `user_id` key with value `123` and the `env` key with value `dev`.
  paginator = novita.sandbox.list(
      query=SandboxQuery(
          metadata={
              'user_id': '123',
              'env': 'dev',
          }
      ),
  )

  for running_sandbox in paginator.next_items():
      print(
          'list running sandbox (%s) metadata:' % running_sandbox.sandbox_id,
          running_sandbox.metadata,
      )

  sandbox.kill()
  ```
</CodeGroup>

### Listing sandboxes

Pagination is now supported by the `novita.sandbox.list()` method. To learn more about pagination techniques with the updated method, refer to the advanced pagination section.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()
  const paginator = await novita.sandbox.list()

  // Get the first page of sandboxes (running and paused)
  const firstPage = await paginator.nextItems()
  if (paginator.hasNext) {
    // Get the next page of sandboxes
    const nextPage = await paginator.nextItems()
  }

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  # List all sandboxes (running and paused)
  paginator = novita.sandbox.list()

  first_page = paginator.next_items()
  if paginator.has_next:
      next_page = paginator.next_items()

  sandbox.kill()
  ```
</CodeGroup>

### Filtering sandboxes

You can filter sandboxes based on their current status. The state parameter accepts `running`, `paused`, or both to return sandboxes in the specified states.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita, SandboxState } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  // List all sandboxes that are running or paused.
  const paginator = await novita.sandbox.list({
    query: {
      state: [SandboxState.RUNNING, SandboxState.PAUSED],
    },
  })

  const sandboxes = await paginator.nextItems()

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxQuery, SandboxState

  novita = Novita()
  sandbox = novita.sandbox.create()

  # List all sandboxes that are running or paused.
  paginator = novita.sandbox.list(
      query=SandboxQuery(
          state=[SandboxState.RUNNING, SandboxState.PAUSED],
      ),
  )

  # Get the first page of sandboxes (running and paused)
  sandboxes = paginator.next_items()

  sandbox.kill()
  ```
</CodeGroup>

You can specify metadata key-value pairs during sandbox creation and later use them when listing sandboxes.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Create sandbox with metadata.
  const sandbox = await novita.sandbox.create({
    metadata: {
      env: 'dev',
      app: 'my-app',
      userId: '123',
    },
  })

  // List all sandboxes that have the `userId` key with value `123` and the `env` key with value `dev`.
  const paginator = await novita.sandbox.list({
    query: {
      metadata: { userId: '123', env: 'dev' },
    },
  })

  const sandboxes = await paginator.nextItems()

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxQuery

  novita = Novita()

  # Create sandbox with metadata.
  sandbox = novita.sandbox.create(
      metadata={
          'env': 'dev',
          'app': 'my-app',
          'user_id': '123',
      },
  )

  # List all sandboxes that have the `user_id` key with value `123` and the `env` key with value `dev`.
  paginator = novita.sandbox.list(
      query=SandboxQuery(
          metadata={
              'user_id': '123',
              'env': 'dev',
          }
      ),
  )

  sandboxes = paginator.next_items()

  sandbox.kill()
  ```
</CodeGroup>

### Advanced pagination

For finer control over pagination, specify the number of items to return per page (the default and maximum is 100) and provide an offset parameter (`nextToken` or `next_token`) to indicate where pagination should begin.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const paginator = await novita.sandbox.list({
    limit: 100,
    // nextToken: '',
  })

  // Fetch the next page
  await paginator.nextItems()

  // Additional paginator properties
  // Whether there is a next page
  console.log('paginator.hasNext: ', paginator.hasNext)

  // Next page token
  console.log('paginator.nextToken: ', paginator.nextToken)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  paginator = novita.sandbox.list(
      limit=100,
      # next_token='',
  )

  # Fetch the next page
  paginator.next_items()

  # Whether there is a next page
  print('paginator.has_next: ', paginator.has_next)

  # Next page offset parameter
  print('paginator.next_token: ', paginator.next_token)

  sandbox.kill()
  ```
</CodeGroup>

You can retrieve all pages by iterating through the paginator. `hasNext` / `has_next` is initially true before the first page is fetched and becomes false after the last page is returned.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita, SandboxInfo } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()
  const paginator = await novita.sandbox.list()

  const sandboxes: SandboxInfo[] = []
  while (paginator.hasNext) {
    const items = await paginator.nextItems()
    sandboxes.push(...items)
  }

  for (const sandbox of sandboxes) {
    console.log(`list sandbox (${sandbox.sandboxId})`)
  }

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita, SandboxInfo

  novita = Novita()
  sandbox = novita.sandbox.create()

  paginator = novita.sandbox.list()

  # Get all sandboxes
  sandboxes: list[SandboxInfo] = []
  while paginator.has_next:
      items = paginator.next_items()
      sandboxes.extend(items)

  sandbox.kill()
  ```
</CodeGroup>


# Long-running sandboxes
Source: https://docs.novita.ai/guides/sandbox-long-running

Enable long-running mode to keep a Novita sandbox alive beyond the default 1-hour timeout cap for persistent services and background agent jobs.

By default, a sandbox `timeout` is effective for **at most 1 hour** — any larger value is capped. Long-running mode lifts this cap: with it enabled, the `timeout` you pass when creating the sandbox can exceed 1 hour. This is useful for workloads that need to stay alive well beyond a typical session, such as persistent services, long-running agent tasks, or background jobs.

Long-running mode is configured via the `metadata` field when creating a sandbox. The key is `long_running` and the value is `"true"` (as a string).

<Tip>
  `long_running` works **together with** `timeout`: set the timeout you actually need when creating the sandbox — with `long_running` enabled, it can exceed the default 1-hour cap. Without `long_running`, `timeout` is effective for at most 1 hour. **If `timeout` is not set, it defaults to 5 mins.**
</Tip>

<Warning>
  Long-running mode does not disable [idle timeout](/guides/sandbox-idle-timeout): if you set one, it still applies, and an inactive sandbox will be paused or killed accordingly.
</Warning>

## Basic usage

Pass the `long_running` key in the `metadata` object when creating a sandbox.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({
    apiKey: process.env.NOVITA_API_KEY || '',
  })

  // Create a long-running code-interpreter sandbox.
  // With long_running enabled, timeoutMs can exceed the default 1-hour cap.
  const sandbox = await novita.codeInterpreter.create({
    metadata: {
      long_running: 'true',
    },
    timeoutMs: 86_400_000, // 24 hours
  })
  ```

  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  novita = Novita(api_key=os.environ['NOVITA_API_KEY'])

  # Create a long-running code-interpreter sandbox.
  # With long_running enabled, timeout can exceed the default 1-hour cap.
  sandbox = novita.code_interpreter.create(
      metadata={
          'long_running': 'true',
      },
      timeout=86_400,  # 24 hours
  )
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # --long-running writes long_running=true to the metadata
  novita sandbox create base --long-running --timeout 24h -d
  ```
</CodeGroup>

## How it works

* On `create` (and `resume`), when `long_running` is set, the `timeout` you pass can exceed the default 1-hour maximum running time.

## Scope and inheritance

* Only `create` accepts the `long_running` parameter.
* `resume` (including auto-resume): a sandbox created with `long_running` keeps the behavior after being resumed from a snapshot.
* `clone` inherits the parameter, and the sandbox keeps it when it continues running after a `reset`.

## Examples

Create a long-running sandbox that stays alive for up to **7 days**:

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita({
    apiKey: process.env.NOVITA_API_KEY || '',
  })

  // 7 days = 7 * 24 * 60 * 60 * 1000 ms
  const sandbox = await novita.codeInterpreter.create({
    metadata: {
      long_running: 'true',
    },
    timeoutMs: 604_800_000,
  })
  ```

  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  novita = Novita(api_key=os.environ['NOVITA_API_KEY'])

  # 7 days = 7 * 24 * 60 * 60 seconds
  sandbox = novita.code_interpreter.create(
      metadata={
          'long_running': 'true',
      },
      timeout=604_800,
  )
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a long-running sandbox that stays alive for up to 7 days
  novita sandbox create base --long-running --timeout 168h -d
  ```
</CodeGroup>


# Sandbox Metrics
Source: https://docs.novita.ai/guides/sandbox-metrics



Sandbox metrics provide timestamped CPU, memory, and disk utilization data for a sandbox.

## Getting sandbox metrics

Calling the metrics API returns a list of timestamped metric records. Metrics are sampled every 5 seconds.

### Getting sandbox metrics using the SDKs

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sbx = await novita.sandbox.create()
  console.log('Created sandbox:', sbx.sandboxId)

  // Allow time for metrics collection.
  await new Promise((resolve) => setTimeout(resolve, 10_000))

  const metrics = await sbx.getMetrics()

  // Alternative by ID:
  // const metrics = await novita.sandbox.getMetrics(sbx.sandboxId)

  console.log('Metrics:', metrics)
  ```

  ```python Python icon="python" theme={"system"}
  from time import sleep
  from novita_sandbox import Novita

  novita = Novita()
  sbx = novita.sandbox.create()
  print('Created sandbox:', sbx.sandbox_id)

  # Allow time for metrics collection.
  sleep(10)

  metrics = sbx.get_metrics()

  # Alternative by ID:
  # metrics = novita.sandbox.get_metrics(sbx.sandbox_id)

  print('Metrics:', metrics)
  ```
</CodeGroup>

### Getting sandbox metrics using the CLI

```bash CLI icon="terminal" theme={"system"}
novita sandbox metrics <sandboxID>

# Example output:
# Metrics for sandbox
#
# [2025-07-25 14:05:55Z] CPU: 8.27% / 2 Cores | Memory: 31 / 484 MiB | Disk: 1445 / 2453 MiB
# [2025-07-25 14:06:00Z] CPU: 0.5% / 2 Cores | Memory: 32 / 484 MiB | Disk: 1445 / 2453 MiB
# [2025-07-25 14:06:05Z] CPU: 0.1% / 2 Cores | Memory: 32 / 484 MiB | Disk: 1445 / 2453 MiB
# [2025-07-25 14:06:10Z] CPU: 0.3% / 2 Cores | Memory: 32 / 484 MiB | Disk: 1445 / 2453 MiB
```

Use `--follow` to stream metrics continuously in real time:

```bash CLI icon="terminal" theme={"system"}
novita sandbox metrics <sandboxID> --follow
```

Use `--format json` to output machine-readable metrics:

```bash CLI icon="terminal" theme={"system"}
novita sandbox metrics <sandboxID> --format json
```

Metrics may not appear immediately after sandbox creation; before the first sample, the response may be empty.


# Mount S3 Storage to Sandbox
Source: https://docs.novita.ai/guides/sandbox-mount-cloudstorage



Each sandbox instance is provisioned with 20GB of ephemeral system disk storage by default for temporary data operations. Upon sandbox termination or timeout, all data within this storage space is automatically purged. Consequently, persistent data requiring long-term retention should be stored in external cloud storage services.

Sandbox storage allocation specifications are subject to modification. Consult the [Pricing](/guides/sandbox-pricing) documentation for current resource allocations and associated costs.

Object storage buckets can be used from a sandbox in two common ways:

1. Use the cloud provider's SDK, CLI, or REST API directly.
2. Mount the bucket with a FUSE-based filesystem so applications can access objects through file paths.

[FUSE (Filesystem in Userspace)](https://www.kernel.org/doc/html/next/filesystems/fuse.html) is a kernel module and userspace library that enables the implementation of fully functional filesystems in userspace applications. This framework provides an abstraction layer that presents remote cloud storage services as standard filesystem hierarchies, enabling transparent file operations through conventional POSIX interfaces.

This documentation provides comprehensive guidance for integrating object storage buckets from leading cloud service providers into sandbox environments through filesystem mounting techniques.

<Note>
  FUSE-based object storage mounts introduce significant I/O performance overhead due to network latency and protocol translation layers. Applications with stringent performance requirements should avoid this approach. Furthermore, FUSE filesystem operations lack atomicity guarantees inherent to native object storage APIs, creating potential race conditions where local filesystem operations may succeed while corresponding remote operations fail, resulting in data inconsistency. This mounting approach is optimal for read-heavy workloads with infrequent write operations and relaxed performance constraints. For performance-critical applications or frequent write patterns, direct integration using cloud provider SDKs or native REST APIs is strongly recommended.
</Note>

## Amazon S3

Amazon S3 buckets can be mounted as POSIX-compliant filesystems using [s3fs-fuse](https://github.com/s3fs-fuse/s3fs-fuse), a FUSE-based filesystem implementation that provides S3 bucket access through standard file operations.

The s3fs-fuse package can be integrated during [sandbox template](/guides/sandbox-template-build-operations) creation by incorporating installation commands in the `novita.Dockerfile`, or installed dynamically within active sandbox instances for ad-hoc requirements.

The following `novita.Dockerfile` demonstrates the integration of s3fs-fuse during template building:

**novita.Dockerfile**

```dockerfile theme={"system"}
# Compatible with Debian-based distributions
FROM ubuntu:latest

# Critical: s3fs versions below 1.93 contain known mounting issues. Ensure version compatibility.
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y s3fs
```

The following implementation demonstrates programmatic S3 bucket mounting within sandbox environments using s3fs-fuse:

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const NOVITA_API_KEY = process.env.NOVITA_API_KEY
  const TEMPLATE_ID = process.env.NOVITA_TEMPLATE_ID
  const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID
  const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY
  const AWS_BUCKET_NAME = process.env.AWS_BUCKET_NAME
  const AWS_REGION = process.env.AWS_REGION
  if (!NOVITA_API_KEY || !TEMPLATE_ID || !AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || !AWS_BUCKET_NAME || !AWS_REGION) {
    throw new Error('Required environment variables not configured: NOVITA_API_KEY, NOVITA_TEMPLATE_ID, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET_NAME, AWS_REGION')
  }

  const novita = new Novita({ apiKey: NOVITA_API_KEY })
  const MOUNT_DIRECTORY = '/mnt/s3-bucket'

  const sandbox = await novita.sandbox.create(TEMPLATE_ID)

  // Initialize mount point directory structure
  await sandbox.files.makeDir(MOUNT_DIRECTORY)

  // Configure s3fs credentials using standard credential file location
  // s3fs-fuse reads AWS credentials from /root/.passwd-s3fs by default
  await sandbox.files.write('/root/.passwd-s3fs', `${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}`)

  // Enforce secure credential file permissions (owner read-only)
  await sandbox.commands.run('sudo chmod 600 /root/.passwd-s3fs')

  // Execute S3 bucket mount operation with optimized parameters
  // Configuration parameters:
  // - allow_other: Enable cross-user filesystem access
  // - endpoint: Specify the AWS region endpoint for your bucket
  // Reference: https://manpages.ubuntu.com/manpages/noble/en/man1/s3fs.1.html
  const mountOptions = `allow_other,endpoint=${AWS_REGION}`
  await sandbox.commands.run(`sudo s3fs ${AWS_BUCKET_NAME} ${MOUNT_DIRECTORY} -o ${mountOptions}`)

  // Validate mount functionality with write operation
  await sandbox.files.write(`${MOUNT_DIRECTORY}/test-file.txt`, 'test-file-content')

  // Verify mount integrity through read operation
  const content = await sandbox.files.read(`${MOUNT_DIRECTORY}/test-file.txt`)
  console.log(content)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  NOVITA_API_KEY = os.environ.get('NOVITA_API_KEY')
  TEMPLATE_ID = os.environ.get('NOVITA_TEMPLATE_ID')
  AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
  AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
  AWS_BUCKET_NAME = os.environ.get('AWS_BUCKET_NAME')
  AWS_REGION = os.environ.get('AWS_REGION')
  if not NOVITA_API_KEY or not TEMPLATE_ID or not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY or not AWS_BUCKET_NAME or not AWS_REGION:
      raise ValueError('Required environment variables not configured: NOVITA_API_KEY, NOVITA_TEMPLATE_ID, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET_NAME, AWS_REGION')

  novita = Novita(api_key=NOVITA_API_KEY)
  MOUNT_DIRECTORY = '/mnt/s3-bucket'

  sandbox = novita.sandbox.create(TEMPLATE_ID)

  # Initialize mount point directory structure
  sandbox.files.makeDir(MOUNT_DIRECTORY)

  # Configure s3fs credentials using standard credential file location
  # s3fs-fuse reads AWS credentials from /root/.passwd-s3fs by default
  # Custom credential paths require explicit specification via -o passwd_file parameter
  sandbox.files.write('/root/.passwd-s3fs', f'{AWS_ACCESS_KEY_ID}:{AWS_SECRET_ACCESS_KEY}')

  # Enforce secure credential file permissions (owner read-only)
  sandbox.commands.run('sudo chmod 600 /root/.passwd-s3fs')

  # Execute S3 bucket mount operation with optimized parameters
  # Configuration parameters:
  # - allow_other: Enable cross-user filesystem access
  # - endpoint: Specify the AWS region endpoint for your bucket
  # Reference: https://manpages.ubuntu.com/manpages/noble/en/man1/s3fs.1.html
  mount_options = f'allow_other,endpoint={AWS_REGION}'
  sandbox.commands.run(f'sudo s3fs {AWS_BUCKET_NAME} {MOUNT_DIRECTORY} -o {mount_options}')

  # Validate mount functionality with write operation
  sandbox.files.write(f'{MOUNT_DIRECTORY}/test-file.txt', 'test-file-content')

  # Verify mount integrity through read operation
  content = sandbox.files.read(f'{MOUNT_DIRECTORY}/test-file.txt')
  print('File content:', content)

  sandbox.kill()
  ```
</CodeGroup>


# Novita Sandbox Overview
Source: https://docs.novita.ai/guides/sandbox-overview



<Note>
  Novita Sandbox offers isolated, stateful execution environments designed for AI agents. It enables running code, installing dependencies, accessing files, using browsers, and preserving state across sessions without infrastructure management.
</Note>

Novita Sandbox is a managed execution environment for AI agents that need to run code, use browsers, work with files, and preserve state across sessions. It gives each agent an isolated runtime without requiring you to manage infrastructure.

***

The platform is built on the following core concepts:

## Core Concepts

### Sandbox

An isolated execution environment where agents perform tasks. Each has its own filesystem and runtime, allowing agents to run commands, manage files, install dependencies, and handle browser-based workflows.

#### Lifecycle

<Frame>
  <img alt="Sandbox lifecycle: transitions between running, paused, and killed" />
</Frame>

A sandbox moves through three states: Running, Paused, and Killed.

* **Running** — the sandbox is active and can execute commands and serve connections.
* **Paused** — the sandbox is suspended; its filesystem and memory state are preserved and billing for compute stops. It can be resumed back to Running (typically in about a second).
* **Killed** — the sandbox is terminated and its resources are released. A killed sandbox cannot be resumed.

Two settings drive the transitions out of Running:

* **Timeout** — the maximum lifetime of the sandbox. It counts down from creation regardless of activity; when it expires the sandbox is stopped by default, or paused if you configure the lifecycle accordingly. See [Timeout Handle](/guides/sandbox-timeout-handle).
* **Idle timeout** — fires only when no client has been connected for the configured duration, automatically killing or pausing the sandbox. See [Idle Timeout Handle](/guides/sandbox-idle-timeout).

On timeout or idle timeout you can choose **pause** (so the sandbox can be resumed later, optionally with auto-resume) instead of **kill**. When paused, resuming restores the sandbox to Running with its state intact; when killed, the sandbox and its data are gone.

### Template

Defines the starting environment for a Sandbox. Templates ensure consistency across multiple Sandboxes with predefined dependencies, configuration, and setup steps.

### Snapshot

A Snapshot saves the current state of a Sandbox as a reusable environment. Snapshots are useful after you have configured a Sandbox, installed dependencies, or generated files that you want to reuse. New Sandboxes can be created from a Snapshot without repeating the same setup steps.

### Secret

Secrets are a secure way to store and use sensitive values — such as API keys, tokens, and passwords — inside a Novita Sandbox. They are encrypted and managed within the scope of the current team, letting code running in a sandbox authenticate to external services without hardcoding credentials in source code, snapshots, templates, or sandbox environment variables.

### Regions

Novita Sandbox currently provides access through the following regions:

| Name | Version | Domain | Description |
| - | - | - | - |
| us-phx-1 | v2 | `us-phx-1.sandbox.novita.ai` | Default region (used by the current SDK / CLI) |
| us-virginia-1 | v1 | `sandbox.novita.ai` | Specified via environment variable |

The **v2** region (us-phx-1) is the **default** used by the current SDK and CLI. It adds capabilities such as **Secrets** and **Snapshots** on top of v1 and provides a more complete feature set, so it is recommended to use v2 first.

The SDK and CLI documented here are compatible with both v1 and v2 regions. To use the **v1** legacy region instead, configure the environment variable `NOVITA_DOMAIN`:

```bash theme={"system"}
export NOVITA_DOMAIN=sandbox.novita.ai
```

***

## When to use Novita Sandbox

Intended for workloads requiring isolated environments to execute tasks, maintain state, or reuse prepared setups. Common use cases include:

* Coding agents that run code, install packages, and execute tests
* Browser agents that automate web workflows
* Data analysis agents that process files and generate reports
* Research or RL workloads that need many isolated environments

***

## Pricing

Billing is per-second for CPU and RAM. Storage—covering templates, paused sandboxes, and snapshots—incurs daily charges. Additional details are available on the [pricing page](/guides/sandbox-pricing).

***

## Quick start

Sandboxes can be started and managed via SDK or CLI. The [quick start](/guides/sandbox-your-first-agent-sandbox) page provides further guidance.


# Pause & Resume Sandbox
Source: https://docs.novita.ai/guides/sandbox-pause-resume



Sandbox persistence lets you pause a sandbox and resume it later with the same filesystem and memory state. This is useful for long-running agent tasks, interactive apps, and workflows that need to survive idle periods without keeping compute resources active.

## Manual pause and resume

When a sandbox is paused:

* Files created in the sandbox are preserved.
* In-memory state is preserved, including running processes and variables.
* Network connections are interrupted until the sandbox resumes.
* The sandbox remains stored until you resume and kill it, or kill it while it is paused.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.commands.run('echo hello > /tmp/message.txt')
  const sandboxId = sandbox.sandboxId
  await sandbox.pause()

  console.log('Sandbox paused', sandboxId)

  const resumed = await novita.sandbox.connect(sandboxId)
  const result = await resumed.commands.run('cat /tmp/message.txt')
  console.log(result.stdout)

  await resumed.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.commands.run("echo hello > /tmp/message.txt")
  sandbox_id = sandbox.sandbox_id
  sandbox.pause()

  print('Sandbox paused', sandbox_id)

  resumed = novita.sandbox.connect(sandbox_id)
  result = resumed.commands.run("cat /tmp/message.txt")
  print(result.stdout)

  resumed.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Pause a running sandbox (alias: sandbox ps)
  novita sandbox pause <sandboxID>

  # Resume a paused sandbox (alias: sandbox rs)
  novita sandbox resume <sandboxID>
  ```
</CodeGroup>

## Auto pause and resume

Use lifecycle settings when you want the sandbox to pause automatically after timeout and optionally resume when activity is detected.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({
    timeoutMs: 10 * 60 * 1000,
    lifecycle: {
      onTimeout: 'pause',
      autoResume: true,
    },
  })

  await sandbox.files.write('/home/user/hello.txt', 'hello from a paused sandbox')
  await sandbox.pause()

  const content = await sandbox.files.read('/home/user/hello.txt')
  console.log(content)
  console.log(`State after read: ${(await sandbox.getInfo()).state}`)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(
      timeout=10 * 60,
      lifecycle={
          'on_timeout': 'pause',
          'auto_resume': True,
      },
  )

  sandbox.files.write('/home/user/hello.txt', 'hello from a paused sandbox')
  sandbox.pause()

  content = sandbox.files.read('/home/user/hello.txt')
  print(content)
  print(f'State after read: {sandbox.get_info().state}')
  ```
</CodeGroup>

## Web server example

Auto-resume works well for preview environments and web servers.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({
    timeoutMs: 5 * 60 * 1000,
    lifecycle: {
      onTimeout: 'pause',
      autoResume: true,
    },
  })

  await sandbox.commands.run('python3 -m http.server 3000', { background: true })
  const host = sandbox.getHost(3000)
  console.log(`Preview URL: https://${host}`)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(
      timeout=5 * 60,
      lifecycle={
          'on_timeout': 'pause',
          'auto_resume': True,
      },
  )

  sandbox.commands.run('python3 -m http.server 3000', background=True)
  host = sandbox.get_host(3000)
  print(f'Preview URL: https://{host}')
  ```
</CodeGroup>


# Pricing
Source: https://docs.novita.ai/guides/sandbox-pricing



Novita Sandbox is billed per second for CPU and RAM while a sandbox is **running**. Storage is billed separately for **paused** sandboxes when usage **exceeds the free allowance**.

## Billing Items

| Billing Item | Description |
| - | - |
| CPU | Billed based on the number of vCPU cores used and usage duration (accurate to the second). See pricing [details](https://novita.ai/pricing?sandbox=1). Billing stops when the sandbox stops. |
| RAM (memory) | Billed based on allocated memory capacity and usage duration (accurate to the second). See pricing [details](https://novita.ai/pricing?sandbox=1). Billing stops when the sandbox stops. |
| [Ephemeral Storage](/guides/sandbox-filesystem) | Each sandbox includes 20 GB of Ephemeral Storage while running. When paused, data is retained as Persistent Storage. |
| Persistent Storage | Templates and Snapshots are saved as persistent storage. Persistent storage is billed daily and includes a 60 GB free allowance. See the [pricing page](https://novita.ai/pricing?sandbox=1) for current unit prices. |

<Note>
  **Billing when a Sandbox is paused:** Once a Sandbox is paused, **CPU and RAM stop being billed**. You are only charged for **Persistent Storage** used to retain the sandbox data (60 GB free per account; usage beyond that is billed at \$0.00009/GB/h). See the Storage section below for details.
</Note>

## Free Sandbox Usage

New users can complete a quick account setup survey to receive \$100 in promotional credits for eligible Novita Sandbox usage. No credit card is required.

Promotional credits are applied to eligible Novita Sandbox usage first, including CPU, RAM, and billable storage usage where applicable. After credits are used up or expired, continued usage is billed pay-as-you-go per current pricing.

Credits may be subject to availability, validity period, usage scope, and quota limits. You can check your balance and voucher status on the [Payments](https://novita.ai/en/billing) page.

Promotional credits do not change the underlying unit prices listed below.

## Pricing

### CPU

| vCPUs (cores) | Unit Price |
| - | - |
| 1 | \$0.0000098/s |
| 2 | \$0.0000196/s |
| 3 | \$0.0000294/s |
| 4 | \$0.0000392/s |
| 5 | \$0.000049/s |
| 6 | \$0.0000588/s |
| 7 | \$0.0000686/s |
| 8 | \$0.0000784/s |

### RAM (memory)

| Memory (MiB) | Unit Price |
| - | - |
| Valid values: multiples of 512 MiB, from 512 MiB to 8192 MiB | \$0.0000032/GiB/s |
| 512 MiB | \$0.0000016/s |
| 1 GiB | \$0.0000032/s |
| 2 GiB | \$0.0000064/s |

### Storage

Each sandbox includes 20 GB of Ephemeral Storage while running. When paused, data is retained as Persistent Storage. Each account includes 60 GB of free Persistent Storage. Usage beyond the free amount is billed at the standard rate.

| Storage State | Unit Price |
| - | - |
| Ephemeral Storage for running sandboxes | Free (20 GB per sandbox) |
| Persistent Storage for paused sandboxes | 60 GB free per account, \$0.00009/GB/h for usage beyond 60 GB free storage |

## Get Bills

You can view and export Agent Sandbox bills in "Billing - Billing Details - Usage-based Billing".

* Go to the [billing details page](https://novita.ai/billing/details);
* Select the "Usage-based Billing" tab on the right, then click the "Agent Sandbox" secondary tab below;
* You can set the "Time Range" and "Group By" filters;
* You can click "Export" button after querying to export bill data as an .xlsx file.

## Overdue Payment Notice

If the account's available balance (including available balance and voucher balance) is less than the bill to be paid, the account is considered overdue. Overdue payments result in sandboxes being terminated and data loss. During usage, ensure sufficient account balance. After overdue, system sends notification emails; recharge promptly.

Balance and vouchers confirmable on the [payments](https://novita.ai/billing) page; recharge as needed.

To avoid overdue payments affecting service, setting up [low balance alerts](https://novita.ai/billing/balance-warning) is recommended. When balance is less than the alert threshold set, a notification email is sent.

The specific overdue payment impacts:

* Unable to start new sandboxes.
* Running sandboxes will be terminated.

[Source: Pricing — novita.ai](/guides/sandbox-pricing)


# Quota Limits
Source: https://docs.novita.ai/guides/sandbox-quota-limit



Novita Sandbox applies quota limits based on your account tier. Quotas define the resources and capabilities available to each tier.

If you need higher limits, please [contact our support team](https://meet.brevo.com/novita-ai/contact-sales).

## Account Tiers

| Account Tier | Description |
| - | - |
| Free | The default tier for new users. |
| Paid | Available after adding account balance or enabling a credit line. |
| Enterprise | Designed for enterprise customers that need custom limits and dedicated support. |

## Default Quotas

| Limit | Free | Paid | Enterprise |
| - | - | - | - |
| Concurrent sandboxes | 5 | 100 | 500 by default, adjustable |
| Maximum session duration | 1 hour | 3 hours | 4 hours by default, adjustable |
| Maximum vCPU | 2 vCPU | 8 vCPU | 8 vCPU by default, adjustable |
| Maximum memory | 4 GiB | 8 GiB | 8 GiB by default, adjustable |
| Local disk space | 20 GB | 20 GB | 20 GB by default, adjustable |
| Memory increment | 512 MiB | 512 MiB | 512 MiB |
| Memory per vCPU | 0.5-4 GiB | 0.5-4 GiB | 0.5-4 GiB |

## Configuration Rules

When configuring CPU and memory, your configuration must meet the following requirements:

* vCPU must not exceed the maximum vCPU limit for your account tier.
* Memory must not exceed the maximum memory limit for your account tier.
* vCPU must be configured as an integer value, such as 1, 2, 4, or 8 vCPU.
* Memory must be configured in 512 MiB increments.
* Memory must be within the allowed range of 0.5-4 GiB per vCPU.

For example:

* 1 vCPU supports 512 MiB to 4 GiB of memory.
* 2 vCPU supports 1 GiB to 8 GiB of memory.
* 4 vCPU can support up to 16 GiB of memory based on the memory-per-vCPU range, but the actual available memory is still limited by the maximum memory quota of your account tier.

The final available configuration is determined by both your account tier limits and the allowed memory-per-vCPU range.

## Check Current Quota Usage

Use the quota API to check the quota limits available to your account and the resources currently in use. This is useful before launching a large batch of sandboxes or when troubleshooting quota-related launch failures.

### Check quota usage using the SDKs

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox'

  const quota = await Sandbox.getQuota()

  console.log('Quota limit:', quota.limit)
  console.log('Current usage:', quota.usage)

  // Example shape:
  // {
  //   limit: {
  //     concurrentInstances: 100,
  //     concurrentVcpu: 200,
  //     concurrentRamMb: 409600,
  //     maxLengthHours: 4,
  //     diskMb: 20480,
  //     maxVcpu: 8,
  //     maxRamMb: 8192
  //   },
  //   usage: {
  //     concurrentInstances: 12,
  //     concurrentVcpu: 24,
  //     concurrentRamMb: 49152
  //   }
  // }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.core import Sandbox

  quota = Sandbox.get_quota()

  print('Quota limit:', quota.limit)
  print('Current usage:', quota.usage)

  # Example shape:
  # {
  #   "limit": {
  #     "concurrentInstances": 100,
  #     "concurrentVcpu": 200,
  #     "concurrentRamMb": 409600,
  #     "maxLengthHours": 4,
  #     "diskMb": 20480,
  #     "maxVcpu": 8,
  #     "maxRamMb": 8192,
  #   },
  #   "usage": {
  #     "concurrentInstances": 12,
  #     "concurrentVcpu": 24,
  #     "concurrentRamMb": 49152,
  #   },
  # }
  ```
</CodeGroup>

### Quota response fields

The quota response includes `limit` and `usage` objects.

| Field | Description |
| - | - |
| `limit.concurrentInstances` | Maximum number of sandboxes that can run concurrently. |
| `limit.concurrentVcpu` | Maximum total vCPU cores that can be used concurrently. |
| `limit.concurrentRamMb` | Maximum total RAM that can be used concurrently, in MiB. |
| `limit.maxLengthHours` | Maximum lifetime for a single sandbox, in hours. |
| `limit.diskMb` | Available runtime storage for a single sandbox, in MB. |
| `limit.maxVcpu` | Maximum vCPU cores for a single sandbox. Applies after the template is built. |
| `limit.maxRamMb` | Maximum memory for a single sandbox, in MB. Applies after the template is built. |
| `usage.concurrentInstances` | Current number of running sandboxes. |
| `usage.concurrentVcpu` | Current total vCPU cores in use. |
| `usage.concurrentRamMb` | Current total RAM in use, in MB. |

Quota usage is intended for capacity planning and troubleshooting. Usage data may have a short delay, depending on the backend resource reporting path.

## Notes

* If your resource configuration exceeds the quota for your account tier, the request will fail.
* If a region has no available capacity,  If a region has no available capacity, sandbox creation may fail even if your account-level quota is not exhausted.
* Choose CPU and memory based on your workload requirements to avoid out-of-memory errors or unnecessary resource usage.
* Enterprise quotas can be customized based on your requirements. To request a quota increase, please [contact our support team](https://meet.brevo.com/novita-ai/contact-sales).


# Run Command
Source: https://docs.novita.ai/guides/sandbox-run-command



You can use the `commands.run()` method to run terminal commands inside the sandbox.

## Basic usage

Call `commands.run()` with a command string. The returned result contains the command output.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const result = await sandbox.commands.run('ls -l')
  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  result = sandbox.commands.run('ls -l')
  print(result)

  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Execute a command in a running sandbox (alias: sandbox ex)
  novita sandbox exec <sandboxID> -- echo "hello"

  # Run in background
  novita sandbox exec <sandboxID> -b -- long-running-cmd

  # Set working directory / user / env
  novita sandbox exec <sandboxID> -c /home/user -u root -e KEY=VALUE -- ls -la
  ```
</CodeGroup>

## Read command output

Use `stdout` to read what the command printed.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const result = await sandbox.commands.run('echo hello')
  console.log(result.stdout) // hello

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  result = sandbox.commands.run("echo hello")
  print(result.stdout)   # hello

  sandbox.kill()
  ```
</CodeGroup>

## Run in the background

Pass the background option to start a long-running process (such as a server) without blocking on it.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.commands.run('python3 -m http.server 3000', { background: true })

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.commands.run("python3 -m http.server 3000", background=True)

  sandbox.kill()
  ```
</CodeGroup>

## Override environment variables per command

You can override or append environment variables for a single command with the `envs` option. It applies only to that command.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const result = await sandbox.commands.run('echo $LOG_LEVEL', {
    envs: { LOG_LEVEL: 'info' }, // applies only to this command
  })
  console.log(result.stdout) // info

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  result = sandbox.commands.run(
      "echo $LOG_LEVEL",
      envs={"LOG_LEVEL": "info"},   # applies only to this command
  )
  print(result.stdout)   # info

  sandbox.kill()
  ```
</CodeGroup>

## Stream output

For long-running commands, pass `on_stdout` / `on_stderr` (Python) or `onStdout` / `onStderr` (JavaScript) callbacks to receive output as it is produced, instead of waiting for the command to finish. The returned result still contains the final exit code.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sbx = novita.sandbox.connect(sandbox_id)

  result = sbx.commands.run(
      "for i in 1 2 3; do echo line $i; sleep 1; done",
      on_stdout=lambda data: print("OUT:", data, end=""),
      on_stderr=lambda data: print("ERR:", data, end=""),
  )
  print("exit code:", result.exit_code)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sbx = await novita.sandbox.connect(sandboxId)

  const result = await sbx.commands.run(
    'for i in 1 2 3; do echo line $i; sleep 1; done',
    {
      onStdout: (data) => process.stdout.write(`OUT: ${data}`),
      onStderr: (data) => process.stderr.write(`ERR: ${data}`),
    },
  )
  console.log('exit code:', result.exitCode)
  ```
</CodeGroup>


# Timeout Handle
Source: https://docs.novita.ai/guides/sandbox-timeout-handle



Every sandbox has a **maximum lifetime**, controlled by the `timeout` setting. When the timeout expires, the sandbox is stopped. By default, a new sandbox stays alive for **5 minutes**.

<Note>
  **Timeout vs. Idle timeout — don't confuse them:**

  * **Timeout** is the maximum lifetime of the sandbox. It starts counting down from creation, regardless of activity, and always takes effect when it expires.
  * **Idle timeout** only fires when no client is connected for the configured duration.
</Note>

## Create a sandbox with a timeout

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({ timeoutMs: 60_000 })

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(timeout=60)

  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Create a sandbox with a 60-second timeout (detached mode), prints the sandbox ID
  novita sandbox create base --timeout 60s -d
  ```
</CodeGroup>

## Update the timeout

You can use `setTimeout` / `set_timeout` to reset how much longer the sandbox should stay alive.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({ timeoutMs: 60_000 })

  await sandbox.setTimeout(300_000)
  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(timeout=60)

  sandbox.set_timeout(300)
  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Reset the sandbox timeout to 5 minutes
  novita sandbox set-timeout <sandbox_id> 5m
  ```
</CodeGroup>

## Lifecycle behavior

You can combine `timeout` with `lifecycle` to choose whether the sandbox is killed or paused when the timeout expires.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({
    timeoutMs: 300_000,
    lifecycle: {
      onTimeout: 'pause',
      autoResume: true,
    },
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(
      timeout=300,
      lifecycle={
          'on_timeout': 'pause',
          'auto_resume': True,
      },
  )
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Pause the sandbox instead of killing it when the timeout expires, and allow auto-resume
  novita sandbox create base --timeout 5m --on-timeout pause --auto-resume -d
  ```
</CodeGroup>

If you do not pass `lifecycle`, the sandbox is killed when the timeout expires.


# OpenVPN
Source: https://docs.novita.ai/guides/sandbox-vpn-openvpn



You can connect a Novita Sandbox to an OpenVPN network using a `.ovpn` client configuration file so the sandbox can reach private networks through the VPN tunnel. This is useful for accessing internal services, using a fixed egress path, or routing selected traffic through your own infrastructure.

The flow is: create a sandbox, install OpenVPN, upload the `.ovpn` config, start OpenVPN in the background, then verify the `tun0` tunnel interface is up.

***

## Prerequisites

* `pip install novita-sandbox`
* `export NOVITA_API_KEY=...`
* An OpenVPN client profile (`.ovpn`) available locally

***

## Connect a sandbox to OpenVPN

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  """novita-openvpn.py

  Connect a Novita sandbox to an OpenVPN network using a `.ovpn` client
  configuration file (non-interactive).
  """

  import os
  import time

  from novita_sandbox import Novita

  # Path to the local .ovpn client profile (in the project directory).
  OVPN_PATH = os.path.join(os.path.dirname(__file__), "..", "client1.ovpn")
  REMOTE_CONFIG = "/home/novita/client.ovpn"
  REMOTE_LOG = "/tmp/openvpn.log"


  def setup_openvpn(ovpn_config: str):
      """Connect a Novita sandbox to an OpenVPN network."""
      novita = Novita(api_key=os.environ["NOVITA_API_KEY"])

      # Create the sandbox
      print("Creating sandbox...")
      sandbox = novita.sandbox.create("base")
      print(f"Sandbox created: {sandbox.sandbox_id}")

      # Step 1: Install OpenVPN
      print("\\nInstalling OpenVPN...")
      response = sandbox.commands.run(
          "sudo apt update && sudo apt install -y openvpn",
          timeout=300,
      )
      if response.exit_code != 0:
          print(f"Error installing OpenVPN: {response.stderr}")
          return sandbox
      print("OpenVPN installed successfully.")

      # Step 2: Write the OpenVPN config file
      print("\\nWriting OpenVPN configuration...")
      sandbox.files.write(REMOTE_CONFIG, ovpn_config)
      print(f"Configuration written to {REMOTE_CONFIG}")

      # Step 3: Start OpenVPN in background.
      #   `--pull-filter ignore redirect-gateway` stops the server from taking
      #   over the sandbox's default route — otherwise the SDK's own control
      #   connection gets pulled into the tunnel and every later command times
      #   out. The VPN subnet routes are still installed.
      print("\\nStarting OpenVPN tunnel...")
      sandbox.commands.run(
          f'nohup sudo openvpn --config {REMOTE_CONFIG} '
          f'--pull-filter ignore "redirect-gateway" '
          f"> {REMOTE_LOG} 2>&1 &",
          background=True,
      )

      # Wait for connection to establish
      print("Waiting for VPN connection to establish...")
      time.sleep(10)

      # Step 4: Verify connection — check if the tun0 interface exists
      print("\\nVerifying OpenVPN connection...")
      response = sandbox.commands.run("ip addr show tun0 2>/dev/null || true")
      if "inet " in response.stdout:
          print("VPN tunnel interface (tun0) is up:")
          print(response.stdout)
      else:
          print("Warning: tun0 interface not found. Checking OpenVPN logs...")
          log = sandbox.commands.run(f"cat {REMOTE_LOG} 2>/dev/null || true")
          print(f"OpenVPN log:\\n{log.stdout}")
          return sandbox

      print("\\nOpenVPN connection established successfully.")
      return sandbox


  def main() -> None:
      with open(OVPN_PATH, "r") as f:
          ovpn_config = f.read().strip()

      sandbox = setup_openvpn(ovpn_config)
      try:
          print("\\nSandbox is connected. Press Ctrl+C to disconnect and kill it.")
          while True:
              time.sleep(3600)
      except KeyboardInterrupt:
          print("\\nInterrupted — shutting down.")
      finally:
          sandbox.kill()
          print("Sandbox killed")


  if __name__ == "__main__":
      main()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  // Connect a Novita sandbox to an OpenVPN network using a `.ovpn` client
  // configuration file (non-interactive).
  import fs from 'fs'
  import path from 'path'

  import { Novita } from 'novita-sandbox'

  // Path to the local .ovpn client profile (in the project directory).
  const OVPN_PATH = path.join(__dirname, '..', 'client1.ovpn')
  const REMOTE_CONFIG = '/home/novita/client.ovpn'
  const REMOTE_LOG = '/tmp/openvpn.log'

  async function setupOpenVPN(ovpnConfig) {
    const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })

    // Create the sandbox
    console.log('Creating sandbox...')
    const sandbox = await novita.sandbox.create('base')
    console.log(`Sandbox created: ${sandbox.sandboxId}`)

    // Step 1: Install OpenVPN
    console.log('\\nInstalling OpenVPN...')
    const install = await sandbox.commands.run(
      'sudo apt update && sudo apt install -y openvpn',
      { timeoutMs: 300_000 }
    )
    if (install.exitCode !== 0) {
      console.log(`Error installing OpenVPN: ${install.stderr}`)
      return sandbox
    }
    console.log('OpenVPN installed successfully.')

    // Step 2: Write the OpenVPN config file
    console.log('\\nWriting OpenVPN configuration...')
    await sandbox.files.write(REMOTE_CONFIG, ovpnConfig)
    console.log(`Configuration written to ${REMOTE_CONFIG}`)

    // Step 3: Start OpenVPN in background.
    //   `--pull-filter ignore redirect-gateway` stops the server from taking
    //   over the sandbox's default route — otherwise the SDK's own control
    //   connection gets pulled into the tunnel and every later command times
    //   out. The VPN subnet routes are still installed.
    console.log('\\nStarting OpenVPN tunnel...')
    await sandbox.commands.run(
      `nohup sudo openvpn --config ${REMOTE_CONFIG} ` +
        `--pull-filter ignore "redirect-gateway" ` +
        `> ${REMOTE_LOG} 2>&1 &`,
      { background: true }
    )

    // Wait for connection to establish
    console.log('Waiting for VPN connection to establish...')
    await new Promise((r) => setTimeout(r, 10_000))

    // Step 4: Verify connection — check if the tun0 interface exists
    console.log('\\nVerifying OpenVPN connection...')
    const res = await sandbox.commands.run('ip addr show tun0 2>/dev/null || true')
    if (res.stdout.includes('inet ')) {
      console.log('VPN tunnel interface (tun0) is up:')
      console.log(res.stdout)
    } else {
      console.log('Warning: tun0 interface not found. Checking OpenVPN logs...')
      const log = await sandbox.commands.run(`cat ${REMOTE_LOG} 2>/dev/null || true`)
      console.log(`OpenVPN log:\\n${log.stdout}`)
      return sandbox
    }

    console.log('\\nOpenVPN connection established successfully.')
    return sandbox
  }

  async function main() {
    const ovpnConfig = fs.readFileSync(OVPN_PATH, 'utf8').trim()
    const sandbox = await setupOpenVPN(ovpnConfig)
    try {
      console.log('\\nSandbox is connected. Press Ctrl+C to disconnect and kill it.')
      await new Promise(() => {}) // keep running until interrupted
    } finally {
      await sandbox.kill()
      console.log('Sandbox killed')
    }
  }

  main().catch(console.error)
  ```
</CodeGroup>

<Note>
  **Important:** The `--pull-filter ignore "redirect-gateway"` flag prevents the VPN server from taking over the sandbox's default route. Without it,   the SDK's control channel may be routed into the tunnel and subsequent commands can time out. VPN subnet routes are still installed, so you can reach the VPN network while the SDK keeps working.
</Note>

OpenVPN is started with `background=True` so it keeps running while you issue further commands. The tunnel is confirmed by checking that the `tun0` interface has an `inet` address; if it doesn't come up, inspect `/tmp/openvpn.log`.

***

## OpenVPN with remote shell

Instead of driving everything from the SDK, you can set up OpenVPN interactively from a remote shell using the CLI. This is handy for one-off connections, editing the `.ovpn` file by hand, or debugging the tunnel live.

### 1. Connect to the sandbox

Open an interactive remote shell into a running sandbox by its ID. This attaches your terminal to a shell inside the sandbox. See [Remote Shell](/guides/sandbox-cli-remote-shell) for details on the connect command.

```bash CLI icon="terminal" theme={"system"}
novita sandbox connect <sandbox_id>
```

### 2. Install OpenVPN and tools

Inside the remote shell, install OpenVPN along with `tmux` (to keep the tunnel running in a background session) and `vim` (to edit the config).

```bash CLI icon="terminal" theme={"system"}
sudo apt update && sudo apt install -y openvpn tmux vim
```

### 3. Create / edit the .ovpn config

Open the client profile in `vim` and paste in (or adjust) your OpenVPN client configuration, then save and exit. The file is created in the current directory as `client.ovpn`.

```bash CLI icon="terminal" theme={"system"}
sudo vim client.ovpn
```

### 4. Start OpenVPN in a background tmux session

Launch OpenVPN inside a detached `tmux` session so the tunnel keeps running independently of your shell. `-d` starts the session detached, `-s openvpn` names it, and the quoted command is what runs inside it.

```bash CLI icon="terminal" theme={"system"}
tmux new -d -s openvpn 'sudo openvpn client.ovpn'
```

The tunnel now runs in the background. You can keep using the current shell, disconnect, or manage the session as needed:

```bash CLI icon="terminal" theme={"system"}
# Attach to watch the OpenVPN output / logs
tmux attach -t openvpn
# Detach again without stopping it: press Ctrl+b then d

# Verify the tunnel interface is up
ip addr show tun0

# Stop the tunnel by killing the session
tmux kill-session -t openvpn
```

<Note>
  **Note:** Running `tmux attach -t openvpn` reattaches to the session to view live OpenVPN logs; press **Ctrl+b** then **d** to detach and leave it running.
</Note>


# Tailscale
Source: https://docs.novita.ai/guides/sandbox-vpn-tailscale



Tailscale lets you connect a Novita sandbox to your private tailnet, so the sandbox can securely reach — and be reached by — other devices on your network. Create the sandbox from the `tailscale` template, which comes with Tailscale pre-installed and ready to use.

This guide covers three ways to connect a sandbox to your tailnet:

* **Browser Login** — run `tailscale up`, open the printed login URL in your browser to authorize the sandbox, and it joins your tailnet. Best for interactive, one-off setups.
* **Tailscale auth key** — connect non-interactively with a pre-generated auth key. Best for automated scripts, CI/CD pipelines, or any scenario without manual browser interaction.
* **Manual installation** — install Tailscale and apply the required workaround yourself when starting from a template other than `tailscale` (e.g. `base`).

***

## Prerequisites

* `pip install novita-sandbox` (or `npm i novita-sandbox`)
* `export NOVITA_API_KEY=...`
* A Tailscale account to authorize the login

***

## Browser Login

Run `tailscale up` in the foreground with no timeout. It blocks and prints a login URL; open that URL in your browser to authorize, and the command returns once the sandbox is connected. Then read back the assigned Tailscale IP.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os
  import time

  from novita_sandbox import Novita


  novita = Novita(api_key=os.getenv("NOVITA_API_KEY", ""))
  sandbox = novita.sandbox.create(template="tailscale")
  print("Sandbox created:", sandbox.sandbox_id)
  try:
      # Run `tailscale up` in the foreground and stream its output.
      # It blocks until you authorize in the browser, printing a login
      # URL to stderr in the meantime, then returns once connected.
      print("\n=== Open the login URL below in your browser to authorize ===")
      sandbox.commands.run(
          'sudo tailscale up --hostname="${SANDBOX_ID:-no-name-sandbox-$(date +%s)}"',
          on_stdout=lambda data: print(data, end=""),
          on_stderr=lambda data: print(data, end=""),
          timeout=0,  # no limit — wait for the interactive browser login
      )
      print("=" * 60)

      # Show the assigned Tailscale IP.
      ip = sandbox.commands.run("sudo tailscale ip -4 || true")
      print("Connected! Tailscale IP:", ip.stdout.strip())

      # Keep the sandbox alive until the user interrupts.
      print("\nSandbox is connected. Press Ctrl+C to disconnect and kill it.")
      try:
          while True:
              time.sleep(3600)
      except KeyboardInterrupt:
          print("\nInterrupted — shutting down.")
  finally:
      sandbox.kill()
      print("Sandbox killed")
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from "novita-sandbox"

  async function main() {
    const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
    const sandbox = await novita.sandbox.create({
      template: "tailscale",
    })
    console.log("Sandbox created:", sandbox.sandboxId)

    try {
      // Run `tailscale up` in the foreground and stream its output.
      // It blocks until you authorize in the browser, printing a login
      // URL to stderr in the meantime, then returns once connected.
      console.log("\n=== Open the login URL below in your browser to authorize ===")
      await sandbox.commands.run('sudo tailscale up --hostname="${SANDBOX_ID:-no-name-sandbox-$(date +%s)}"', {
        onStdout: (data) => process.stdout.write(data),
        onStderr: (data) => process.stdout.write(data),
        timeoutMs: 0, // no limit — wait for the interactive browser login
      })
      console.log("=".repeat(60))

      // Show the assigned Tailscale IP.
      const ip = await sandbox.commands.run("sudo tailscale ip -4 || true")
      console.log("Connected! Tailscale IP:", ip.stdout.trim())

      // Keep the sandbox alive until the user interrupts.
      console.log("\nSandbox is connected. Press Ctrl+C to disconnect and kill it.")
      await new Promise(() => {})
    } finally {
      await sandbox.kill()
      console.log("Sandbox killed")
    }
  }

  main().catch(console.error)
  ```
</CodeGroup>

***

## Tailscale auth key

Using an auth key provides a non-interactive way to connect your Novita sandbox to Tailscale, making it suitable for automated scripts, CI/CD pipelines, or any scenario where manual browser interaction is not available.

1. Access your [Tailscale admin console](https://console.tailscale.com/admin/machines).
2. Click **Add device** and select **Linux server**.
3. Apply the configuration and click **Generate install script**.

This will generate a script that you can use to install Tailscale and connect to the Tailscale network:

```bash CLI icon="terminal" theme={"system"}
curl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up --auth-key=<AUTH_KEY> --hostname="${SANDBOX_ID:-no-name-sandbox-$(date +%s)}"
```

Run that command inside the sandbox from the SDK. Because the auth key logs in non-interactively, no browser step is required.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os

  from novita_sandbox import Novita

  AUTH_KEY = os.environ["TS_AUTH_KEY"]  # tskey-auth-...

  novita = Novita(api_key=os.getenv("NOVITA_API_KEY", ""))
  sandbox = novita.sandbox.create(template="tailscale")
  print("Sandbox created:", sandbox.sandbox_id)

  result = sandbox.commands.run(
      f"sudo tailscale up --auth-key={AUTH_KEY} --hostname=\"${{SANDBOX_ID:-no-name-sandbox-$(date +%s)}}\"",
      on_stdout=lambda data: print(data, end=""),
      on_stderr=lambda data: print(data, end=""),
      timeout=120,
  )
  if result.exit_code != 0:
      raise RuntimeError(f"tailscale up failed:\n{result.stderr}")

  ip = sandbox.commands.run("sudo tailscale ip -4 || true")
  print("Connected! Tailscale IP:", ip.stdout.strip())
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from "novita-sandbox"

  const AUTH_KEY = process.env.TS_AUTH_KEY // tskey-auth-...

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
  const sandbox = await novita.sandbox.create({
    template: "tailscale",
  })
  console.log("Sandbox created:", sandbox.sandboxId)

  const result = await sandbox.commands.run(
    `sudo tailscale up --auth-key=${AUTH_KEY} --hostname="${'${SANDBOX_ID:-no-name-sandbox-$(date +%s)}'}"`,
    {
      onStdout: (data) => process.stdout.write(data),
      onStderr: (data) => process.stdout.write(data),
      timeoutMs: 120_000,
    }
  )
  if (result.exitCode !== 0) {
    throw new Error(`tailscale up failed:\n${result.stderr}`)
  }

  const ip = await sandbox.commands.run("sudo tailscale ip -4 || true")
  console.log("Connected! Tailscale IP:", ip.stdout.trim())
  ```
</CodeGroup>

***

## Manually install

The `tailscale` template already includes Tailscale and the workaround described below, so Browser Login works out of the box. If you start from another template (e.g. `base`), you need to install Tailscale and apply the workaround yourself before `tailscale up` will work.

<Note>
  **Why the extra steps?** A sandbox's `eth0` uses a link-local address (`169.254.x.x`). Tailscale's `isUsableV4` check treats link-local addresses as "not usable for internet" — **except** in AWS Lambda / Azure App Service environments. As a result, `tailscaled` reports `network is down` and `tailscale up` hangs without ever printing the login URL. The fix is to make `tailscaled` believe it runs in AWS Lambda by injecting four `AWS_LAMBDA_*` environment variables, which makes `isUsableV4` accept the link-local address. See [tailscale/tailscale#20496](https://github.com/tailscale/tailscale/issues/20496) for the related upstream issue.
</Note>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from "novita-sandbox"

  const novita = new Novita({ apiKey: process.env.NOVITA_API_KEY })
  const sandbox = await novita.sandbox.create({
    template: "base",
  })
  console.log("Sandbox created:", sandbox.sandboxId)

  // 1. Install Tailscale.
  console.log("Installing Tailscale...")
  const install = await sandbox.commands.run(
    "curl -fsSL https://tailscale.com/install.sh | sh",
    { timeoutMs: 300_000 }
  )
  if (install.exitCode !== 0) {
    throw new Error(`Install failed:\n${install.stderr}`)
  }

  // 2. Add a systemd override so tailscaled starts with AWS Lambda env vars,
  //    then (re)start it via systemd. This is the workaround for the
  //    link-local address issue described above.
  console.log("Configuring tailscaled systemd override...")
  const override = `[Service]
  Environment="AWS_LAMBDA_FUNCTION_NAME=x"
  Environment="AWS_LAMBDA_FUNCTION_VERSION=1"
  Environment="AWS_LAMBDA_INITIALIZATION_TYPE=on-demand"
  Environment="AWS_LAMBDA_RUNTIME_API=127.0.0.1:9001"
  `
  await sandbox.commands.run("sudo mkdir -p /etc/systemd/system/tailscaled.service.d")
  await sandbox.files.write(
    "/etc/systemd/system/tailscaled.service.d/override.conf",
    override,
  )

  console.log("Reloading systemd and (re)starting tailscaled...")
  await sandbox.commands.run("sudo systemctl daemon-reload")
  await sandbox.commands.run("sudo systemctl restart tailscaled")
  await new Promise((r) => setTimeout(r, 3000))

  // 3. Now log in. `tailscale up` prints a login URL; open it in your browser.
  console.log("\n=== Open the login URL below in your browser to authorize ===")
  await sandbox.commands.run('sudo tailscale up --hostname="${SANDBOX_ID:-no-name-sandbox-$(date +%s)}"', {
    onStdout: (data) => process.stdout.write(data),
    onStderr: (data) => process.stdout.write(data),
    timeoutMs: 0,
  })

  const ip = await sandbox.commands.run("sudo tailscale ip -4 || true")
  console.log("Connected! Tailscale IP:", ip.stdout.trim())
  ```

  ```python Python icon="python" theme={"system"}
  import os
  import time

  from novita_sandbox import Novita

  novita = Novita(api_key=os.getenv("NOVITA_API_KEY", ""))
  sandbox = novita.sandbox.create(template="base")
  print("Sandbox created:", sandbox.sandbox_id)

  # 1. Install Tailscale.
  print("Installing Tailscale...")
  install = sandbox.commands.run(
      "curl -fsSL https://tailscale.com/install.sh | sh",
      timeout=300,
  )
  if install.exit_code != 0:
      raise RuntimeError(f"Install failed:\n{install.stderr}")

  # 2. Add a systemd override so tailscaled starts with AWS Lambda env vars,
  #    then (re)start it via systemd. This is the workaround for the
  #    link-local address issue described above.
  print("Configuring tailscaled systemd override...")
  override = """[Service]
  Environment="AWS_LAMBDA_FUNCTION_NAME=x"
  Environment="AWS_LAMBDA_FUNCTION_VERSION=1"
  Environment="AWS_LAMBDA_INITIALIZATION_TYPE=on-demand"
  Environment="AWS_LAMBDA_RUNTIME_API=127.0.0.1:9001"
  """
  sandbox.commands.run("sudo mkdir -p /etc/systemd/system/tailscaled.service.d")
  sandbox.files.write(
      "/etc/systemd/system/tailscaled.service.d/override.conf",
      override,
  )

  print("Reloading systemd and (re)starting tailscaled...")
  sandbox.commands.run("sudo systemctl daemon-reload")
  sandbox.commands.run("sudo systemctl restart tailscaled")
  time.sleep(3)

  # 3. Now log in. `tailscale up` prints a login URL; open it in your browser.
  print("\n=== Open the login URL below in your browser to authorize ===")
  sandbox.commands.run(
      'sudo tailscale up --hostname="${SANDBOX_ID:-no-name-sandbox-$(date +%s)}"',
      on_stdout=lambda data: print(data, end=""),
      on_stderr=lambda data: print(data, end=""),
      timeout=0,
  )

  ip = sandbox.commands.run("sudo tailscale ip -4 || true")
  print("Connected! Tailscale IP:", ip.stdout.strip())
  ```
</CodeGroup>


# Your First Sandbox
Source: https://docs.novita.ai/guides/sandbox-your-first-agent-sandbox



## Create an Account

If you don't have a Novita account, [sign up](https://novita.ai/user/register) first. For details, please refer to [Quickstart guide](/guides/quickstart).

New users can complete a quick account setup survey to receive \$100 in promotional  Sandbox credits. No credit card is required.

## Create API Key

Create an API key on the Novita [Key Management page](https://novita.ai/settings/key-management) and keep it for later use.

## Install SDK

You can install the Novita SDK by executing the following commands.

**JavaScript & TypeScript SDK**

```bash CLI icon="terminal" theme={"system"}
npm i novita-sandbox
```

**Python SDK**

```bash CLI icon="terminal" theme={"system"}
pip install novita-sandbox
```

## Configure Environment Variables

Create a `.env` file in your project folder if it does not exist, and configure your API key.

The SDK reads `NOVITA_API_KEY` from the environment. `dotenv/config` in JavaScript and `load_dotenv()` in Python only load the value into the process environment; you do not need to pass the API key into each example manually.

**.env**

```text Output icon="code" theme={"system"}
NOVITA_API_KEY=sk_*** # Novita API key obtained from previous steps
```

Or you can configure the API key by setting an environment variable in your terminal.

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=sk_*** # Novita API key obtained from previous steps
```

## Start a Sandbox with the SDK

Below is a simple example showing how to create a sandbox, execute code inside it, and list files in the sandbox filesystem.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  // index.ts
  import 'dotenv/config'
  import { Novita } from 'novita-sandbox'

  async function main() {
    const novita = new Novita()
    const sandbox = await novita.codeInterpreter.create()
    try {
      const execution = await sandbox.runCode('print("hello world")')
      console.log(execution.logs)

      const files = await sandbox.files.list('/tmp')
      console.log(files)
    } finally {
      // Close sandbox when no longer needed
      await sandbox.kill()
    }
  }

  main().catch((err) => {
    console.error(err)
    process.exit(1)
  })
  ```

  ```python Python icon="python" theme={"system"}
  # main.py
  from dotenv import load_dotenv
  from novita_sandbox import Novita

  # The .env file should be located in the project root directory
  # dotenv will automatically look for .env in the current working directory
  load_dotenv()

  novita = Novita()
  sandbox = novita.code_interpreter.create()
  try:
      execution = sandbox.run_code("print('hello world')")
      print(execution.logs)

      files = sandbox.files.list("/tmp")
      print(files)
  finally:
      # Close sandbox when no longer needed
      sandbox.kill()
  ```
</CodeGroup>

Execute the following commands to run the code.

**index.ts**

```bash CLI icon="terminal" theme={"system"}
npx tsx ./index.ts
```

**main.py**

```bash CLI icon="terminal" theme={"system"}
python main.py
```


# Skyvern
Source: https://docs.novita.ai/guides/skyvern

Learn how to seamlessly connect Novita AI's powerful GPU infrastructure with Skyvern's automation capabilities.

The collaboration between Skyvern and Novita AI marks a significant advancement in AI-powered automation and development capabilities. By combining Skyvern's expertise in browser-based workflow automation with Novita AI's robust GPU infrastructure and model deployment platform, developers and businesses can now access a comprehensive solution for building and scaling AI applications.

Our comprehensive guide will walk you through Novita AI implementation on Skyvern, including setting up Docker Compose and leveraging Novita AI in Skyvern.

## Setting Up Docker Compose

Step 1: Ensure [Docker Desktop](https://www.docker.com/products/docker-desktop/) is installed and running on your machine.

Step 2: Check for running PostgreSQL instances using `docker ps` the command.

Step 3: Navigate to the project directory after cloning the repository.

```bash theme={"system"}
git clone <repository-url> && cd <repository-name>
```

Step 4: Add your LLM provider API Key on the [docker-compose.yml](https://github.com/Skyvern-AI/skyvern/blob/main/docker-compose.yml). (*If you want to run Skyvern on a remote server, make sure you set the correct server ip for the UI container in* [*docker-compose.yml*](https://github.com/Skyvern-AI/skyvern/blob/main/docker-compose.yml)*.)*

Step 5: Enter the following command via the command line.

```
docker compose up -d
```

Step 6: Access the application interface at`http://localhost:8080` in your browser to start using the UI.

## Leveraging Novita AI in Skyvern

Step 1: Update environment variables in docker-compose.yml before building the docker image.

Step 2:  Start Novita AI LLM service by running`docker compose up -d`.

<Frame>
  <img alt="Leveraging Novita AI in Skyvern- edit environment variables" />
</Frame>


# Verba
Source: https://docs.novita.ai/guides/verba

Integrate Novita AI with Verba to simplify data management and unlock contextual answers instantly.

Novita AI has redefined LLM application development through its seamless integration with Verba. By merging Novita AI's innovative platform with Verba's advanced NLP framework, developers gain access to superior performance, enhanced customizability, and a user-friendly experience.

This guide will walk you through how to integrate Novita AI API with Verba.

## Integration Steps

### Step 1: **Visit the Verba Website**

* Open your browser and navigate to [https://verba.weaviate.io/](https://verba.weaviate.io/).

<Frame>
  <img alt="" />
</Frame>

### Step 2: **Choose Deployment Option**

* Select your preferred deployment method on the homepage to proceed.

<Frame>
  <img alt="" />
</Frame>

### Step 3: **Configure Settings**

* Click the "Start" button at the bottom-right corner.
* On the left-hand side, click **Config**.
* Under **Generator**, select **Novita**.
* Enter your [Novita API Key](https://novita.ai/settings/key-management) in the **API Key** field.
* Finally, click the **Save Config** button to apply your settings.

<Frame>
  <img alt="" />
</Frame>

## Step 4: **Import Data**

* Click the **Import Data** button in the top-right corner to open the file import page.
* Select the **Files** button at the bottom to open the file selection window.
* Choose the files you want to import and click the **Import Selected** button at the bottom-left corner.
* A successfully imported file will display on the right-hand side of the page.

<Frame>
  <img alt="" />
</Frame>

### Step 5: **Start Chatting**

* Click the **Chat** button at the top to return to the chat page.
* Enter your questions based on the imported file to receive accurate results instantly.

<Frame>
  <img alt="" />
</Frame>


# Zed
Source: https://docs.novita.ai/guides/zed

Connect Zed to Novita's OpenAI-compatible LLM API endpoint.

Zed is a high-performance, multiplayer code editor with built-in AI features. You can connect Zed to Novita's OpenAI-compatible LLM API endpoint as a custom OpenAI-compatible provider, and use Novita models in the Agent Panel, Inline Assistant, and other Zed AI features.

## Prerequisites

Before you begin, prepare the following values:

* **API key**: Create or copy an API key from [Key Management](https://novita.ai/settings/key-management).
* **Base URL**: `https://api.novita.ai/openai/v1`
* **Model ID**: Copy the model ID you want to use from the Model Hub, such as `deepseek/deepseek-v4.1-flash`.

<Tip>
  Keep your API key private. Enter it only in Zed's settings UI (it is stored in the system keychain) and never commit it to a Git repository.
</Tip>

## Add Novita as an OpenAI-compatible provider

Open Zed's command palette and run:

```text theme={"system"}
agent: open settings
```

Under **AI** settings, click **Configure** next to **LLM Providers**:

<Frame>
  <img />
</Frame>

Click **Add Provider**, choose **OpenAI** under **Compatible APIs**, and fill in the provider name, API URL, model ID, and context window:

<Frame>
  <img />
</Frame>

Alternatively, configure the provider directly in your `settings.json`:

```json theme={"system"}
{
  "language_models": {
    "openai_compatible": {
      "novita": {
        "api_url": "https://api.novita.ai/openai/v1",
        "available_models": [
          {
            "name": "deepseek/deepseek-v4.1-flash",
            "display_name": "Novita: DeepSeek V4.1 Flash",
            "max_tokens": 1048576
          }
        ]
      }
    }
  }
}
```

<Tip>
  Set `max_tokens` to the model's context window. Check the current value for your model in the [Model Hub](https://novita.ai/models), since it varies per model.
</Tip>

Enter your Novita API key in the provider settings UI, or set it as an environment variable. For a provider named `novita`, Zed reads `NOVITA_API_KEY` from its process environment (generated from the provider ID as upper snake case plus `_API_KEY`). Do not put the API key in `settings.json`.

## Start using Novita models in Zed

Open the Agent Panel and select your Novita model from the model picker. Send a test prompt:

```text theme={"system"}
In one sentence, tell me what programming tasks you can help with.
```

<Frame>
  <img />
</Frame>

If the request fails, check that:

* Your API key is correct and entered in the provider settings UI (or the generated environment variable is set).
* The API URL is `https://api.novita.ai/openai/v1`.
* The model ID exactly matches the Model ID in Novita's Model Hub.


# API Reference Overview
Source: https://docs.novita.ai/api-reference/api-reference-overview



## Welcome to Novita AI API Reference

This documentation provides comprehensive reference for all Novita AI APIs. Our APIs are organized into several categories:

## Base URLs

All API requests should be made to:

```
https://api.novita.ai
```

**OpenAI-Compatible Endpoints:**

```
https://api.novita.ai/openai
```

## API Groups

### Basic APIs

Fundamental APIs for authentication, billing, and account management.

| Endpoint | Description |
| - | - |
| [Authentication](/api-reference/basic-authentication) | API key authentication |
| [Error Codes](/api-reference/basic-error-code) | Unified error response format |
| [Get User Balance](/api-reference/basic-get-user-balance) | Query account balance |
| [Query Monthly Bill](/api-reference/basic-query-monthly-bill) | Query monthly billing |
| [Query Usage-Based Billing](/api-reference/basic-query-usage-based-billing) | Query usage-based billing |
| [Query Fixed-Term Billing](/api-reference/basic-query-fixed-term-billing) | Query fixed-term billing |

### Model APIs

#### LLM APIs

Large Language Model APIs with OpenAI compatibility.

| Endpoint | Description |
| - | - |
| [Create Chat Completion](/api-reference/model-apis-llm-create-chat-completion) | Create a chat completion |
| [Create Completion](/api-reference/model-apis-llm-create-completion) | Create a completion |
| [Create Embeddings](/api-reference/model-apis-llm-create-embeddings) | Create embeddings |
| [Create Rerank](/api-reference/model-apis-llm-create-rerank) | Rerank search results |
| [List Models](/api-reference/model-apis-llm-list-models) | List available models |
| [Retrieve Model](/api-reference/model-apis-llm-retrieve-model) | Get model details |

**Batch Operations:**

| Endpoint | Description |
| - | - |
| [Create Batch](/api-reference/model-apis-llm-create-batch) | Create a batch job |
| [Retrieve Batch](/api-reference/model-apis-llm-retrieve-batch) | Get batch job status |
| [Cancel Batch](/api-reference/model-apis-llm-cancel-batch) | Cancel a batch job |
| [List Batches](/api-reference/model-apis-llm-list-batches) | List all batch jobs |
| [Upload Batch Input File](/api-reference/model-apis-llm-upload-batch-input-file) | Upload file for batch |
| [List Files](/api-reference/model-apis-llm-list-files) | List uploaded files |
| [Query File](/api-reference/model-apis-llm-query-file) | Get file metadata |
| [Delete File](/api-reference/model-apis-llm-delete-file) | Delete a file |
| [Retrieve File Content](/api-reference/model-apis-llm-retrieve-file-content) | Get file content |

#### Image, Audio and Video APIs

Multimodal AI APIs for content generation and editing.

**Core Endpoints:**

| Endpoint | Description |
| - | - |
| [Webhook](/api-reference/model-apis-webhook) | Webhook configuration |
| [Get Model](/api-reference/model-apis-get-model) | Query available models |
| [Task Result](/api-reference/model-apis-task-result) | Get async task results |

**Image Generation:**

| Endpoint | Description |
| - | - |

**Image Editing:**

| Endpoint | Description |
| - | - |

**Video Generation:**

| Endpoint | Description |
| - | - |

**Audio:**

| Endpoint | Description |
| - | - |
| [Minimax Speech 2.8](/api-reference/model-apis-minimax-speech-2.8-hd) | Minimax 2.8 HD |

### GPU APIs

GPU instance management and serverless GPU endpoints.

#### GPU Instance

| Endpoint | Description |
| - | - |
| [Create Instance](/api-reference/gpu-instance-create-instance) | Create GPU instance |
| [List Instances](/api-reference/gpu-instance-list-instances) | List all instances |
| [Get Instance](/api-reference/gpu-instance-get-instance) | Get instance details |
| [Start Instance](/api-reference/gpu-instance-start-instance) | Start instance |
| [Stop Instance](/api-reference/gpu-instance-stop-instance) | Stop instance |
| [Delete Instance](/api-reference/gpu-instance-delete-instance) | Delete instance |

**Template Management:**

| Endpoint | Description |
| - | - |
| [Create Template](/api-reference/gpu-instance-create-template) | Create template |
| [List Templates](/api-reference/gpu-instance-list-templates) | List templates |
| [Get Template](/api-reference/gpu-instance-get-template) | Get template details |

**Product & Pricing:**

| Endpoint | Description |
| - | - |
| [List Products](/api-reference/gpu-instance-list-products) | List GPU products |

#### Serverless GPUs

| Endpoint | Description |
| - | - |
| [Create Endpoint](/api-reference/serverless-create-endpoint) | Create endpoint |
| [List Endpoint](/api-reference/serverless-list-endpoint) | List endpoints |
| [Get Endpoint](/api-reference/serverless-get-endpoint) | Get endpoint details |
| [Update Endpoint](/api-reference/serverless-update-endpoint) | Update endpoint |
| [Delete Endpoint](/api-reference/serverless-delete-endpoint) | Delete endpoint |

## Quick Start

### 1. Get Your API Key

Visit the [settings page](https://novita.ai/settings/key-management) to create and manage your API keys.

### 2. Make Your First Request

```bash theme={"system"}
curl --location 'https://api.novita.ai/openai/v1/models' \
  --header 'Authorization: Bearer {{API Key}}'
```

### 3. Explore SDKs

We provide official SDKs for popular languages:

* [Python SDK](/guides/model-apis-sdks#python)
* [JavaScript/TypeScript SDK](/guides/model-apis-sdks#javascripttypescript)

## Authentication

All API requests require authentication using Bearer tokens. Include your API key in the Authorization header:

```
Authorization: Bearer {{API Key}}
```

See [Authentication](/api-reference/basic-authentication) for details.

## Error Handling

Novita AI uses standard HTTP status codes and returns errors in a unified format. See [Error Codes](/api-reference/basic-error-code) for complete error reference.

## Common Use Cases

| User Query | Go To |
| - | - |
| "How to get started?" | [Quick Start](#quick-start) + [Authentication](/api-reference/basic-authentication) |
| "API not working, error 401" | [Error Codes](/api-reference/basic-error-code) |
| "Create chatbot with LLM" | [Chat Completion](/api-reference/model-apis-llm-create-chat-completion) |
| "Deploy GPU instance" | [Create Instance](/api-reference/gpu-instance-create-instance) |
| "Check my balance" | [Get User Balance](/api-reference/basic-get-user-balance) |
| "OpenAI compatible API" | [LLM API Guide](/guides/llm-api) |

## Need Help?

* [Discord Community](https://discord.gg/YyPFAzwp7P)
* [Email Support](mailto:support@novita.ai)
* [Console](https://novita.ai/console)


# Authentication
Source: https://docs.novita.ai/api-reference/basic-authentication



The `Novita AI` API uses API keys in the `request headers` to authenticate requests. You can view and manage your API keys in [settings page](https://novita.ai/settings/key-management?utm_source=getstarted).

```js theme={"system"}
{
    "Authorization": "Bearer {{API Key}}"
}
```


# API Error Codes
Source: https://docs.novita.ai/api-reference/basic-error-code



## Image / Video / Audio

<table>
  <thead>
    <tr>
      <th>Error Name</th>
      <th>Status Code</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>INVALID\_REQUEST\_BODY</td>
      <td>400</td>
      <td>Request parameter validation failed</td>
    </tr>

    <tr>
      <td>IMAGE\_FILE\_EXCEEDS\_MAX\_SIZE</td>
      <td>400</td>
      <td>Image size exceeds limit</td>
    </tr>

    <tr>
      <td>INVALID\_IMAGE\_FORMAT</td>
      <td>400</td>
      <td>Image format does not meet requirements</td>
    </tr>

    <tr>
      <td>IMAGE\_EXCEEDS\_MAX\_RESOLUTION</td>
      <td>400</td>
      <td>Image resolution exceeds limit</td>
    </tr>

    <tr>
      <td>INVALID\_IMAGE\_SIZE</td>
      <td>400</td>
      <td>Image width or height exceeds limit</td>
    </tr>

    <tr>
      <td>API\_NOT\_FOUND</td>
      <td>404</td>
      <td>API not found</td>
    </tr>

    <tr>
      <td>IMAGE\_NO\_FACE\_DETECTED</td>
      <td>400</td>
      <td>No face detected</td>
    </tr>

    <tr>
      <td>INVALID\_CUSTOM\_OUTPUT\_PATH</td>
      <td>400</td>
      <td>Invalid OSS path</td>
    </tr>

    <tr>
      <td>ILLEGAL\_PROMPT</td>
      <td>400</td>
      <td>Prompt contains inappropriate content</td>
    </tr>

    <tr>
      <td>ILLEGAL\_IMAGE\_CONTENT</td>
      <td>400</td>
      <td>Image contains inappropriate content</td>
    </tr>

    <tr>
      <td>INVALID\_AUDIO\_FILE</td>
      <td>400</td>
      <td>Invalid audio input</td>
    </tr>

    <tr>
      <td>BILLING\_FAILED</td>
      <td>500</td>
      <td>Billing service error</td>
    </tr>

    <tr>
      <td>BILLING\_AUTH\_FAILED</td>
      <td>403</td>
      <td>Billing service authentication failed</td>
    </tr>

    <tr>
      <td>BILLING\_BALANCE\_NOT\_ENOUGH</td>
      <td>400</td>
      <td>Insufficient balance</td>
    </tr>

    <tr>
      <td>MISSING\_API\_KEY</td>
      <td>400</td>
      <td>API Key not provided</td>
    </tr>

    <tr>
      <td>INVALID\_API\_KEY</td>
      <td>403</td>
      <td>API Key validation failed</td>
    </tr>

    <tr>
      <td>FEATURE\_NOT\_ALLOWED</td>
      <td>403</td>
      <td>No permission to upload model</td>
    </tr>

    <tr>
      <td>API\_NOT\_ALLOWED</td>
      <td>403</td>
      <td>No permission to use this API</td>
    </tr>

    <tr>
      <td>RATE\_LIMIT\_EXCEEDED</td>
      <td>429</td>
      <td>Rate limit exceeded</td>
    </tr>

    <tr>
      <td>NEED\_REAL\_NAME\_VERIFY</td>
      <td>403</td>
      <td>Enterprise verification not completed</td>
    </tr>

    <tr>
      <td>CREATE\_TASK\_FAILED</td>
      <td>500</td>
      <td>Failed to create task</td>
    </tr>

    <tr>
      <td>TASK\_NOT\_FOUND</td>
      <td>404</td>
      <td>Task not found</td>
    </tr>

    <tr>
      <td>GET\_RESULT\_FAILED</td>
      <td>500</td>
      <td>Failed to get task result</td>
    </tr>

    <tr>
      <td>TASK\_FAILED</td>
      <td>500</td>
      <td>Task execution failed</td>
    </tr>
  </tbody>
</table>

## LLM

<table>
  <thead>
    <tr>
      <th>Error Name</th>
      <th>Status Code</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>INVALID\_API\_KEY</td>
      <td>403</td>
      <td>API Key not provided</td>
    </tr>

    <tr>
      <td>MODEL\_NOT\_FOUND</td>
      <td>404</td>
      <td>Model not found</td>
    </tr>

    <tr>
      <td>FAILED\_TO\_AUTH</td>
      <td>401</td>
      <td>Authentication failed</td>
    </tr>

    <tr>
      <td>NOT\_ENOUGH\_BALANCE</td>
      <td>403</td>
      <td>Insufficient balance</td>
    </tr>

    <tr>
      <td>INVALID\_REQUEST\_BODY</td>
      <td>400</td>
      <td>Request body format error, see message for details</td>
    </tr>

    <tr>
      <td>RATE\_LIMIT\_EXCEEDED</td>
      <td>429</td>
      <td>Too many requests, please try again later</td>
    </tr>

    <tr>
      <td>TOKEN\_LIMIT\_EXCEEDED</td>
      <td>429</td>
      <td>Token limit exceeded, please try again later</td>
    </tr>

    <tr>
      <td>SERVICE\_NOT\_AVAILABLE</td>
      <td>503</td>
      <td>Service unavailable</td>
    </tr>

    <tr>
      <td>ACCESS\_DENY</td>
      <td>403</td>
      <td>Access denied</td>
    </tr>
  </tbody>
</table>

## GPU

<table>
  <thead>
    <tr>
      <th>Error Name</th>
      <th>Status Code</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>UNKNOWN</td>
      <td>500</td>
      <td>Unknown error</td>
    </tr>

    <tr>
      <td>GET\_TOKEN\_FAILED</td>
      <td>400</td>
      <td>Failed to obtain token</td>
    </tr>

    <tr>
      <td>FORBIDDEN</td>
      <td>403</td>
      <td>Access forbidden / No permission</td>
    </tr>

    <tr>
      <td>UNAUTHORIZED</td>
      <td>401</td>
      <td>Unauthorized</td>
    </tr>

    <tr>
      <td>USER\_ALREADY\_EXISTS</td>
      <td>400</td>
      <td>User already exists</td>
    </tr>

    <tr>
      <td>INVALID\_USER\_OR\_PASSWORD</td>
      <td>400</td>
      <td>Invalid username or password</td>
    </tr>

    <tr>
      <td>INVALID\_CODE</td>
      <td>400</td>
      <td>Invalid verification code</td>
    </tr>

    <tr>
      <td>USER\_NOT\_FOUND</td>
      <td>400</td>
      <td>User not found</td>
    </tr>

    <tr>
      <td>USER\_PHONE\_NOT\_CONSIST</td>
      <td>400</td>
      <td>User phone number mismatch</td>
    </tr>

    <tr>
      <td>SEND\_CODE\_TOO\_FAST</td>
      <td>429</td>
      <td>Verification code sent too frequently</td>
    </tr>

    <tr>
      <td>INVALID\_PUBLIC\_KEY</td>
      <td>400</td>
      <td>Invalid public key</td>
    </tr>

    <tr>
      <td>USER\_NOT\_ACTIVATED</td>
      <td>400</td>
      <td>User not activated</td>
    </tr>

    <tr>
      <td>USER\_ALREADY\_ACTIVATED</td>
      <td>400</td>
      <td>User already activated</td>
    </tr>

    <tr>
      <td>INVALID\_USER\_TOKEN</td>
      <td>400</td>
      <td>Invalid user token</td>
    </tr>

    <tr>
      <td>BANNED\_USER</td>
      <td>400</td>
      <td>Account has been banned</td>
    </tr>

    <tr>
      <td>RATE\_LIMIT\_EXCEEDED</td>
      <td>429</td>
      <td>Request rate limit exceeded</td>
    </tr>

    <tr>
      <td>RESOURCE\_NOT\_FOUND</td>
      <td>400</td>
      <td>Resource not found (e.g., container not found)</td>
    </tr>

    <tr>
      <td>CONFLICT</td>
      <td>400</td>
      <td>Conflict (e.g., container conflict)</td>
    </tr>

    <tr>
      <td>VALIDATOR\_PARAM</td>
      <td>400</td>
      <td>Parameter validation failed / Invalid parameter</td>
    </tr>

    <tr>
      <td>REQUEST</td>
      <td>400</td>
      <td>Request error</td>
    </tr>

    <tr>
      <td>OPERATION\_LIMIT</td>
      <td>400</td>
      <td>Operation limit reached</td>
    </tr>

    <tr>
      <td>INSUFFICIENT\_RESOURCE</td>
      <td>400</td>
      <td>Insufficient resources</td>
    </tr>

    <tr>
      <td>CLUSTER\_STATUS</td>
      <td>400</td>
      <td>Cluster status abnormal</td>
    </tr>

    <tr>
      <td>NODE\_STATUS</td>
      <td>400</td>
      <td>Node status abnormal</td>
    </tr>

    <tr>
      <td>DEPENDENT\_RESOURCE\_STATE</td>
      <td>400</td>
      <td>Dependent resource state abnormal</td>
    </tr>

    <tr>
      <td>PREPAID\_INSTANCE\_NOT\_SUPPORT\_RELEASE</td>
      <td>400</td>
      <td>Prepaid instance does not support release</td>
    </tr>

    <tr>
      <td>CREATING\_INSTANCE\_NOT\_SUPPORT\_RENEWAL</td>
      <td>400</td>
      <td>Instance in creation does not support renewal</td>
    </tr>

    <tr>
      <td>INSTANCE\_LOCAL\_STORAGE\_NOT\_FOUND</td>
      <td>400</td>
      <td>Instance local storage not found</td>
    </tr>

    <tr>
      <td>INVALID\_COMMAND\_PARAM</td>
      <td>400</td>
      <td>Invalid instance startup command parameter</td>
    </tr>

    <tr>
      <td>GPU\_SPEC\_USED</td>
      <td>400</td>
      <td>GPU specification already in use</td>
    </tr>

    <tr>
      <td>INCORRECT\_USER\_SYNCER\_REQUIRE\_PARAMS</td>
      <td>400</td>
      <td>Incorrect user syncer request parameters</td>
    </tr>

    <tr>
      <td>MIGRATE\_INSUFFICIENT\_RESOURCE</td>
      <td>400</td>
      <td>Insufficient resources for migration</td>
    </tr>

    <tr>
      <td>WALLET\_NOT\_FOUND</td>
      <td>500</td>
      <td>Wallet not found</td>
    </tr>

    <tr>
      <td>WALLET\_UNSUPPORT\_RECHARGE\_METHOD</td>
      <td>400</td>
      <td>Unsupported wallet recharge method</td>
    </tr>

    <tr>
      <td>BALANCE\_NOT\_ENOUGH</td>
      <td>400</td>
      <td>Insufficient balance</td>
    </tr>

    <tr>
      <td>UNSUPPORTED\_BILLING\_MODE</td>
      <td>400</td>
      <td>Unsupported billing mode</td>
    </tr>

    <tr>
      <td>EXPIRED\_OR\_BALANCE\_NOT\_ENOUGH</td>
      <td>400</td>
      <td>Expired or insufficient balance</td>
    </tr>

    <tr>
      <td>ORDER\_NOT\_FOUND</td>
      <td>400</td>
      <td>Order not found</td>
    </tr>

    <tr>
      <td>SAVING\_PLAN\_ALREADY\_EXISTS</td>
      <td>400</td>
      <td>Saving plan already exists</td>
    </tr>

    <tr>
      <td>CREATE\_INSTANCE\_LIMIT</td>
      <td>400</td>
      <td>Instance creation limit reached, please recharge or delete other instances</td>
    </tr>

    <tr>
      <td>NETWORK\_STORAGE\_TOO\_LARGE</td>
      <td>400</td>
      <td>Network storage size exceeds limit</td>
    </tr>

    <tr>
      <td>CUR\_CLUSTER\_NETWORK\_STORAGE\_NOT\_SUPPORT</td>
      <td>400</td>
      <td>Network storage not supported in current region</td>
    </tr>

    <tr>
      <td>NETWORK\_STORAGE\_IN\_USE</td>
      <td>400</td>
      <td>Network storage is in use</td>
    </tr>

    <tr>
      <td>NETWORK\_STORAGE\_UNAVAILABLE</td>
      <td>400</td>
      <td>Network storage unavailable</td>
    </tr>

    <tr>
      <td>NETWORK\_STORAGE\_NOT\_FOUND</td>
      <td>400</td>
      <td>Network storage not found</td>
    </tr>

    <tr>
      <td>IMAGE\_NOT\_FOUND</td>
      <td>400</td>
      <td>Image not found</td>
    </tr>

    <tr>
      <td>IMAGE\_AUTH\_IN\_USE</td>
      <td>400</td>
      <td>Image authentication in use</td>
    </tr>

    <tr>
      <td>NETWORK\_NOT\_FOUND</td>
      <td>400</td>
      <td>Instance network not found</td>
    </tr>

    <tr>
      <td>NETWORK\_IN\_USE</td>
      <td>400</td>
      <td>Instance network in use</td>
    </tr>

    <tr>
      <td>NETWORK\_MAX\_LIMIT</td>
      <td>400</td>
      <td>Instance network creation limit exceeded</td>
    </tr>

    <tr>
      <td>SEND\_MSG\_ERROR</td>
      <td>400</td>
      <td>Message sending error</td>
    </tr>

    <tr>
      <td>JOB\_NOT\_FOUND</td>
      <td>400</td>
      <td>Instance job not found</td>
    </tr>

    <tr>
      <td>SERVERLESS\_ENDPOINT\_NOT\_FOUND</td>
      <td>400</td>
      <td>Serverless endpoint not found</td>
    </tr>

    <tr>
      <td>SERVERLESS\_WORKER\_NOT\_FOUND</td>
      <td>400</td>
      <td>Serverless worker not found</td>
    </tr>

    <tr>
      <td>SERVERLESS\_PRODUCT\_NOT\_FOUND</td>
      <td>400</td>
      <td>Serverless product not found</td>
    </tr>

    <tr>
      <td>SERVERLESS\_APP\_NAME\_IS\_EXIST</td>
      <td>400</td>
      <td>Serverless application name already exists</td>
    </tr>

    <tr>
      <td>TEMPLATE\_IS\_PRIVATE</td>
      <td>400</td>
      <td>Template is private</td>
    </tr>

    <tr>
      <td>TEMPLATE\_NOT\_FOUND</td>
      <td>400</td>
      <td>Template not found</td>
    </tr>
  </tbody>
</table>

## Billing

<table>
  <thead>
    <tr>
      <th>Error Name</th>
      <th>Status Code</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>UNKNOWN</td>
      <td>500</td>
      <td>Unknown error, please contact us</td>
    </tr>

    <tr>
      <td>LIST\_BILL\_TOO\_FAST</td>
      <td>429</td>
      <td>Requests are too frequent, please try again later</td>
    </tr>

    <tr>
      <td>INVALID\_PRODUCT\_CATEGORY</td>
      <td>400</td>
      <td>Invalid productCategory parameter</td>
    </tr>

    <tr>
      <td>INVALID\_BILL\_CYCLE</td>
      <td>400</td>
      <td>Invalid cycle parameter</td>
    </tr>

    <tr>
      <td>LIST\_BILL\_ERROR</td>
      <td>500</td>
      <td>Query error, please contact us</td>
    </tr>
  </tbody>
</table>


# User Balance Info
Source: https://docs.novita.ai/api-reference/basic-get-user-balance

GET https://api.novita.ai/openapi/v1/billing/balance/detail

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response Parameters

<ResponseField name="availableBalance" type="string">
  The user's credit balance, unit is 1/10000 USD (e.g., `10000` = \$1.00).
</ResponseField>

<ResponseField name="cashBalance" type="string">
  The remaining balance of your top-up, unit is 1/10000 USD (e.g., `10000` = \$1.00).
</ResponseField>

<ResponseField name="creditLimit" type="string">
  Your credit limit (i.e., the maximum amount you can owe), unit is 1/10000 USD (e.g., `10000` = \$1.00).
</ResponseField>

<ResponseField name="pendingCharges" type="string">
  Pending charges, unit is 1/10000 USD. Currently defaults to `0`.
</ResponseField>

<ResponseField name="outstandingInvoices" type="string">
  The amount you currently owe, unit is 1/10000 USD (e.g., `10000` = \$1.00).
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url https://api.novita.ai/openapi/v1/billing/balance/detail \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "availableBalance": "1000000",
    "cashBalance": "800000",
    "creditLimit": "200000",
    "pendingCharges": "0",
    "outstandingInvoices": "0"
  }
  ```
</ResponseExample>


# Query Fixed-term Billing
Source: https://docs.novita.ai/api-reference/basic-query-fixed-term-billing

GET https://api.novita.ai/openapi/v1/billing/bill/monthly/list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Product type. Common values:

  * `summary` (Summary bill)
  * `gpu` (GPU instance)
  * `serverless` (Serverless Endpoint)
  * `cloud_storage` (Cloud storage)
  * `local_storage` (Local storage)
  * `image` (Image)
  * `bare_metal` (Bare metal)
</ParamField>

<ParamField type="string">
  Product name. Supports fuzzy matching.
</ParamField>

<ParamField type="integer">
  Start time of the billing period to query, Unix timestamp in seconds (int64).
</ParamField>

<ParamField type="integer">
  End time of the billing period to query, Unix timestamp in seconds (int64).
</ParamField>

<ParamField type="string">
  Resource instance ID.
</ParamField>

<Info>
  This endpoint does not support pagination. Keep each query range within 31 days. For longer ranges, split requests by calendar month or calendar week.
</Info>

## Response Parameters

<ResponseField name="bills" type="object[]">
  Subscription / fixed-term bill list.

  <Expandable title="properties">
    <ResponseField name="userId" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="memberId" type="string">
      Member (sub-user) account ID.
    </ResponseField>

    <ResponseField name="startTime" type="string">
      Bill start time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="endTime" type="string">
      Bill end time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="productName" type="string">
      Product name.
    </ResponseField>

    <ResponseField name="productCategory" type="string">
      Product type.
    </ResponseField>

    <ResponseField name="productId" type="string">
      Product ID.
    </ResponseField>

    <ResponseField name="ownerID" type="string">
      Resource instance ID.
    </ResponseField>

    <ResponseField name="tradeMode" type="string">
      Billing mode. `monthly` indicates subscription.
    </ResponseField>

    <ResponseField name="tradeType" type="string">
      Subscription transaction type. Common values:

      * `monthly_new_buy` (new purchase)
      * `monthly_re_buy` (renewal)
      * `monthly_re_config` (configuration change or scale-up)
    </ResponseField>

    <ResponseField name="billingMethod" type="int32">
      Billing method:

      * `1`: pay-as-you-go
      * `2`: subscription
      * `3`: refund
      * `4`: Spot billing
      * `5`: Batch API
      * `6`: Saving Plan
      * `7`: LLM multimodal billing
      * `8`: bare metal test deduction
    </ResponseField>

    <ResponseField name="basePrice" type="string">
      Unit price.
    </ResponseField>

    <ResponseField name="billNum" type="string">
      Usage. For `gpu`, this usually means GPU card count × subscription duration. For storage products, this usually means storage capacity.
    </ResponseField>

    <ResponseField name="amount" type="string">
      Total amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="voucherAmount" type="string">
      Amount deducted by vouchers, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="payAmount" type="string">
      Cash payment amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="payAmountDisplay" type="float">
      Display value of cash payment amount, in USD.
    </ResponseField>

    <ResponseField name="pricePrecision" type="int32">
      Price precision. Unit price (USD) = unit price / 10000 / `pricePrecision`.
    </ResponseField>

    <ResponseField name="createTime" type="string">
      Bill creation time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="cycle" type="string">
      Billing cycle, in `YYYY-MM` format.
    </ResponseField>

    <ResponseField name="storageDays" type="int32">
      Storage usage duration, in days.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/billing/bill/monthly/list?category=gpu&startTime=1764547200&endTime=1767225599' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "bills": [
      {
        "userId": "<string>",
        "memberId": "<string>",
        "startTime": "1764547200",
        "endTime": "1767225599",
        "productName": "<string>",
        "productCategory": "gpu",
        "ownerID": "<string>",
        "tradeMode": "monthly",
        "tradeType": "monthly_new_buy",
        "basePrice": "1000000",
        "billNum": "1",
        "amount": "1000000",
        "voucherAmount": "0",
        "payAmount": "1000000",
        "payAmountDisplay": 100,
        "pricePrecision": 1,
        "createTime": "1764547200",
        "cycle": "2025-12",
        "productId": "<string>",
        "billingMethod": 2
      }
    ]
  }
  ```
</ResponseExample>


# Query Monthly Bill
Source: https://docs.novita.ai/api-reference/basic-query-monthly-bill

GET https://api.novita.ai/openapi/v1/billing/monthly/bill

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Start month, in `YYYY-MM` format, for example `2025-10`. Monthly bill data is available starting from `2025-10`.
</ParamField>

<ParamField type="int32">
  Page number, starting from `1`.
</ParamField>

<ParamField type="int32">
  Number of items per page.
</ParamField>

<Info>
  The current production environment fills months starting from `startMonth`, so the returned item count may not strictly equal `pageSize`.
</Info>

## Response Parameters

<ResponseField name="data" type="object[]">
  Monthly bill list.

  <Expandable title="properties">
    <ResponseField name="billId" type="string">
      Monthly bill ID.
    </ResponseField>

    <ResponseField name="userId" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="billingMonth" type="string">
      Billing month, in `YYYY-MM` format.
    </ResponseField>

    <ResponseField name="totalAmount" type="string">
      Total amount payable, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="originTotalAmount" type="string">
      Original total amount before discounts or deductions, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="voucherPayAmount" type="string">
      Amount paid by vouchers, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="cashPayAmount" type="string">
      Amount paid in cash, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="debtAmount" type="string">
      Overdue amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="repaidAmount" type="string">
      Repaid amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="status" type="string">
      Monthly bill status. Possible values:

      * `pending`
      * `outed`
      * `paid`
      * `overdue`
      * `voided`
    </ResponseField>

    <ResponseField name="invoiceUrl" type="string">
      Bill or invoice download URL. Empty string means no downloadable file is available.
    </ResponseField>

    <ResponseField name="startTime" type="string">
      Billing period start time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="endTime" type="string">
      Billing period end time, Unix timestamp in seconds.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/billing/monthly/bill?startMonth=2025-10&page=1&pageSize=20' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "data": [
      {
        "billId": "<string>",
        "userId": "<string>",
        "billingMonth": "2025-12",
        "totalAmount": "1000000",
        "originTotalAmount": "1200000",
        "voucherPayAmount": "200000",
        "cashPayAmount": "800000",
        "debtAmount": "0",
        "status": "paid",
        "invoiceUrl": "",
        "startTime": "1764547200",
        "endTime": "1767225599",
        "repaidAmount": "0"
      }
    ]
  }
  ```
</ResponseExample>


# Query Usage-based Billing
Source: https://docs.novita.ai/api-reference/basic-query-usage-based-billing

GET https://api.novita.ai/openapi/v1/billing/bill/list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Billing cycle granularity. Options: `Hour`, `Day`, `Week`, `Month`.
</ParamField>

<ParamField type="string">
  Product type. Common values:

  * `summary` (Summary bill)
  * `gpu` (GPU instance)
  * `llm` (LLM API)
  * `serverless` (Serverless Endpoint)
  * `cloud_storage` (Storage resources)
  * `gen_api` (Image/Video/Audio generation)
  * `cloud_sandbox` (Agent Sandbox)
  * `llm_dedicated_endpoint` (LLM Dedicated Endpoint)
  * `web_search` (Web Search)
  * `bare_metal` (Bare Metal)
</ParamField>

<ParamField type="string">
  Product name. Supports fuzzy matching.
</ParamField>

<ParamField type="string">
  Product subcategory.
</ParamField>

<ParamField type="integer">
  Start time of the billing period to query, Unix timestamp in seconds (int64).
</ParamField>

<ParamField type="integer">
  End time of the billing period to query, Unix timestamp in seconds (int64).
</ParamField>

<ParamField type="string">
  Resource instance ID.
</ParamField>

<Info>
  This endpoint does not support pagination. Keep each query range within 31 days. For longer ranges, split requests by calendar month or calendar week.
</Info>

## Response Parameters

<ResponseField name="bills" type="object[]">
  Pay-as-you-go bill list.

  <Expandable title="properties">
    <ResponseField name="userId" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="startTime" type="string">
      Bill start time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="endTime" type="string">
      Bill end time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="billingMethod" type="int32">
      Billing method: `1` (pay-as-you-go), `2` (subscription).
    </ResponseField>

    <ResponseField name="productName" type="string">
      Product name.
    </ResponseField>

    <ResponseField name="category" type="string">
      Product type or subcategory.
    </ResponseField>

    <ResponseField name="ownerID" type="string">
      Resource instance ID.
    </ResponseField>

    <ResponseField name="productId" type="string">
      Product ID.
    </ResponseField>

    <ResponseField name="billNum0" type="string">
      Usage field 0. Meaning varies by product type:

      * `llm`: input tokens.
      * `gpu`: number of cards × billing duration.
      * `cloud_storage`: storage capacity × billing duration.
      * `serverless`: sum of worker count × duration.
    </ResponseField>

    <ResponseField name="billNum1" type="string">
      Usage field 1. For `llm`, this usually means output tokens.
    </ResponseField>

    <ResponseField name="billNum2" type="string">
      Usage field 2. For `llm`, this usually means read cache tokens.
    </ResponseField>

    <ResponseField name="billNum3" type="string">
      Usage field 3. For `llm`, this usually means write 5-minute cache tokens.
    </ResponseField>

    <ResponseField name="billNum4" type="string">
      Usage field 4. For `llm`, this usually means output reasoning tokens. Currently not supported.
    </ResponseField>

    <ResponseField name="billNum5" type="string">
      Usage field 5. For `llm`, this usually means write 1-hour cache tokens.
    </ResponseField>

    <ResponseField name="billNum6" type="string">
      Usage field 6.
    </ResponseField>

    <ResponseField name="billNum7" type="string">
      Usage field 7.
    </ResponseField>

    <ResponseField name="billNum8" type="string">
      Usage field 8.
    </ResponseField>

    <ResponseField name="billNum9" type="string">
      Usage field 9.
    </ResponseField>

    <ResponseField name="billNum10" type="string">
      Usage field 10.
    </ResponseField>

    <ResponseField name="billNum11" type="string">
      Usage field 11.
    </ResponseField>

    <ResponseField name="billNum12" type="string">
      Usage field 12.
    </ResponseField>

    <ResponseField name="billNum13" type="string">
      Usage field 13.
    </ResponseField>

    <ResponseField name="billNum14" type="string">
      Usage field 14.
    </ResponseField>

    <ResponseField name="billNum15" type="string">
      Usage field 15.
    </ResponseField>

    <ResponseField name="basePrice0" type="string">
      Original price field 0.

      * `llm`: input price per 1M tokens.
      * `gpu`: price per card per hour.
      * `cloud_storage`: price per GB per day.
    </ResponseField>

    <ResponseField name="basePrice1" type="string">
      Original price field 1. For `llm`, this usually means the output price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice2" type="string">
      Original price field 2. For `llm`, this usually means the read cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice3" type="string">
      Original price field 3. For `llm`, this usually means the write 5-minute cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice4" type="string">
      Original price field 4. For `llm`, this usually means the reasoning token price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice5" type="string">
      Original price field 5. For `llm`, this usually means the write 1-hour cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice0" type="string">
      Discounted price field 0.

      * `llm`: discounted input price per 1M tokens.
      * `gpu`: discounted price per card per hour.
      * `cloud_storage`: discounted price per GB per day.
    </ResponseField>

    <ResponseField name="discountPrice1" type="string">
      Discounted price field 1. For `llm`, this usually means the discounted output price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice2" type="string">
      Discounted price field 2. For `llm`, this usually means the discounted read cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice3" type="string">
      Discounted price field 3. For `llm`, this usually means the discounted write 5-minute cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice4" type="string">
      Discounted price field 4. For `llm`, this usually means the discounted reasoning token price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice5" type="string">
      Discounted price field 5. For `llm`, this usually means the discounted write 1-hour cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="amount" type="string">
      Total amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="voucherAmount" type="string">
      Amount deducted by vouchers, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="payAmount" type="string">
      Cash payment amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="payAmountDisplay" type="float">
      Display value of cash payment amount, in USD.
    </ResponseField>

    <ResponseField name="pricePrecision" type="int32">
      Price precision. Unit price (USD) = unit price / 10000 / `pricePrecision`.
    </ResponseField>

    <ResponseField name="llmSeries" type="string">
      LLM series.
    </ResponseField>

    <ResponseField name="requestCount" type="string">
      Request count.
    </ResponseField>

    <ResponseField name="multimodalPricing" type="object">
      Multimodal model billing details.
    </ResponseField>

    <ResponseField name="tieredConfig" type="object">
      Tiered billing configuration.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/billing/bill/list?cycleType=Day&productCategory=llm&startTime=1764547200&endTime=1767225599' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "bills": [
      {
        "userId": "<string>",
        "startTime": "1764547200",
        "endTime": "1764633599",
        "billingMethod": 1,
        "productName": "<string>",
        "category": "llm",
        "ownerID": "<string>",
        "billNum0": "1000",
        "billNum1": "500",
        "basePrice0": "0",
        "basePrice1": "0",
        "discountPrice0": "0",
        "discountPrice1": "0",
        "amount": "10000",
        "voucherAmount": "0",
        "payAmount": "10000",
        "payAmountDisplay": 1,
        "pricePrecision": 1,
        "productId": "<string>"
      }
    ]
  }
  ```
</ResponseExample>


# Automatic Top-Up
Source: https://docs.novita.ai/guides/auto-top-up



When you enable **"Automatic Top-Up"**, your account will automatically add credit if the balance falls below a specified threshold. Please follow the steps below to set up this feature:

### 1. Enable Automatic Top-Up

Go to <a href="https://novita.ai/billing/payment-methods">Payment Methods</a> in the console.

<Frame>
  <img />
</Frame>

If you don't have a payment method, click **"Add payment method"**. You'll be redirected to Stripe's secure page to add one, and we do not store any payment information.

### 2. Automatic Top-Up Settings

After you turn on the **"Automatic Payments"** switch, a configuration popup will appear automatically.

Set valid values for **"When credit goes below"** and **"Bring credit back up to"**, then click **"Save settings"** to complete the process.

<Frame>
  <img />
</Frame>


# Run Axolotl on Novita AI
Source: https://docs.novita.ai/guides/axolotl

Discover how to fine-tune large language models (LLMs) effortlessly with Axolotl on Novita AI.

Axolotl offers a robust, flexible framework for training LLMs using advanced techniques, supporting various model architectures and training strategies. Ideal for researchers and developers, Axolotl combined with Novita AI’s powerful, hardware-free infrastructure streamlines workflows, removing local hardware constraints.

This guide provides a step-by-step process to deploy and run Axolotl on Novita AI, unlocking the full potential of your AI model training projects.

## How to Use Axolotl:main-latest on Novita AI

Step 1: Access [**the GPU Instance Console**](https://novita.ai/gpus)

* Click `Get Started` to access the GPU Instance console.

<Frame>
  <img alt="Step1 Accessthe GPU Instance Console Pn" />
</Frame>

Step 2: Choose a Template and GPU Type

* Browse various official templates and GPU card options.
* Select [**the Axolotl:main-latest template**](https://novita.ai/gpus-console?templateId=311).
* Click `Deploy` under the 4090 GPU card to proceed to the instance creation page.

<Frame>
  <img alt="Step2 Choosea Templateand GPU Type Pn" />
</Frame>

Step 3: Adjust Disk and Configuration Parameters

* In the `Disk` section, adjust the size of the system disk and local disk.
* In the `Configuration` section, modify settings such as the image, startup commands, ports, and environment variables.
* Check the box for Start Jupyter Notebook to launch Jupyter.

<Frame>
  <img alt="Step3 Adjust Diskand Configuration Parameters Pn" />
</Frame>

Step 4: Confirm Configuration and Deploy

* Review the instance configuration and costs on the confirmation page.
* Click `Deploy` to start the deployment process.

<Frame>
  <img alt="Step4 Confirm Configurationand Deploy Pn" />
</Frame>

Step 5: Wait for Deployment to Complete

* Wait for the instance to finish deploying.

<Frame>
  <img alt="Step5 Waitfor Deploymentto Complete Pn" />
</Frame>

Step 6: Manage and Monitor Instances

* Once deployment is complete, the system will redirect you to the `Instance Management` page.
* Locate your newly created instance, which will initially show a Pulling status (indicating the image is being downloaded).
* Click the small arrow on the right side of the instance to view details.
* Monitor the image pull progress. Once complete, the instance will transition to Running status.
* Click `Logs` to view deployment logs.

<Frame>
  ### &#x20; <img alt="Step6 Manageand Monitor Instances Pn" />
</Frame>

Step 7: Check Instance Logs

* Go to the `Instance Logs` tab to check if the service is starting.
* Wait for the service to finish initializing.

<Frame>
  ### &#x20; <img alt="Step7 Check Instance Logs Pn" />
</Frame>

Step 8: Connect to Jupyter Lab

* Close the logs page.
* Click `Connect` to open the connection information page.
* Locate the `Connection Options` section and click `Connect to Jupyter Lab` to access the Jupyter interface.

<Frame>
  ### &#x20; <img alt="Step8 Connectto Jupyter Lab Pn" />
</Frame>

Step 9: Access Jupyter Lab

* Wait for the Jupyter Lab web interface to load.
* Open `Terminal` to run an official example and verify the service is working correctly.

<Frame>
  ### &#x20; <img alt="Step9 Access Jupyter Lab Pn" />
</Frame>

Step 10: Run a Fine-Tuning Example

* Execute the official example code to perform a fine-tuning task.

```bash theme={"system"}
# Fetch axolotl examples
axolotl fetch examples

# Or, specify a custom path
axolotl fetch examples --dest path/to/folder

# Train a model using LoRA
axolotl train examples/llama-3/lora-1b.yml
```

**Note:** You can't change the default mount path for the network volume in the console. It can only be set when creating an instance or via OpenAPI. Set your desired mount path during instance creation when attaching a volume.


# Budgets
Source: https://docs.novita.ai/guides/budgets



The **Budgets** page allows you to set and manage spending limits for team members and individual API Keys, giving you fine-grained control over costs across all service tiers.

<Note>
  This feature is available for team accounts only. Personal accounts or accounts not part of a team can see the menu but cannot access budget management. You can convert your personal account to a team account or join an existing team via the [**Team Settings**](https://novita.ai/settings/team) page.
</Note>

## Permissions

* **Team Owner**, **Admin**, and **Billing** roles can view and modify budgets for all team members and their API Keys.
* **Developer** and **Basic** roles can only view their own budget type and limits (including their API Keys). They cannot make changes.

## Member Budgets and API Key Budgets

You can control spending at two levels:

* **Member budget**: A ceiling on everything a member spends across all services (LLM API, GPU instances, sandbox, etc.)
* **API Key budget**: A separate spending cap for that Key's LLM API usage only. GPU instances, sandbox, and other services are not counted against Key budgets, but do count toward the member budget.

**Both limits are enforced simultaneously — whichever is hit first takes effect:**

* A Key's budget is exhausted → that Key is paused; the member's other Keys and GPU services are unaffected.
* The member's budget is exhausted → all services under that member are paused. For GPU instances and other time-billed products, no new resources can be created; existing resources continue until the end of their billing cycle.

## Budget Types

Both member budgets and API Key budgets support the same three types and can be configured independently:

| Type | Description | Reset Behavior |
| - | - | - |
| Unlimited | No spending cap. Usage is still recorded for reporting. | None |
| Fixed Budget | A one-time total limit. Services pause when exhausted. | Does not reset. Admin must manually increase the limit to resume. |
| Monthly Budget | A per-month limit. Services pause when exhausted. | Resets automatically at 00:00 UTC on the 1st of each month. Unused balance does not carry over. |

**Type combination examples:**

* Member: Monthly \$500 + Key: Monthly \$100 → Standard two-tier control
* Member: Unlimited + Key: Monthly \$50 → Trust the member overall, but cap a public-facing Key
* Member: Monthly \$500 + Key: Fixed Budget \$200 → Key is for a project with a defined total budget

### "Not Set" vs. "Unlimited"

* **Not Set**: No budget has been configured for this member / Key yet. Admins should set this to avoid a management blind spot.
* **Unlimited**: The admin has intentionally chosen no limit. Review periodically.

## Switching Budget Types

When changing a budget type, the system applies the following rules to existing usage and reset cycles:

| Switch Direction | Used Amount | Reset Behavior |
| - | - | - |
| Unlimited → Fixed Budget | Starts from \$0. No prior usage carried over. | No reset. |
| Fixed Budget → Monthly Budget | Historical usage is retained and displayed. New monthly tracking begins from the moment of the switch. | First monthly reset on the 1st of next month. |
| Monthly Budget → Fixed Budget | Current used amount is retained. Monthly reset stops. | No further resets. |
| Any → Unlimited | Budget configuration is cleared. | Unrestricted immediately. |

## Monthly Budget Details

### Automatic Reset

Monthly budgets reset at **00:00 UTC on the 1st of each month**: used amount is zeroed out and the full limit is restored. **Unused balance does not roll over to the next month.**

### Adjusting Monthly Limits

Admins can update the monthly limit at any time. Changes take effect immediately. Remaining balance = new limit − amount already used this month.

Common scenarios:

* **Increase limit**: Available balance increases immediately. If services were paused, they resume right away.
* **Decrease limit**: Available balance decreases. If the new limit is below the amount already used, services pause immediately and resume once the limit is raised again.
* **Fixed Budget — adding more quota**: Increase the budget limit directly. Manual reset of used amount is not supported.

## Setting Member Budgets

1. Go to the [**Budgets**](https://novita.ai/billing/budgets) page. Find the target member in the list, or use the search box.

   <img alt="Budges01 Pn" />

2. Click **Edit**, select a budget type, and enter the limit amount.

   * New members default to **Unlimited**. This can be changed at any time.
   * **Fixed Budget** and **Monthly Budget** types require a specific amount.

   <img alt="Budges02 Pn" />

3. Click **Refresh** to load the latest budget and usage data.

   <img alt="Budges03 Pn" />

<Warning>
  Budget changes take effect immediately. Once a member's quota is exhausted, they cannot initiate new service calls. Confirm the limit before saving.
</Warning>

## Setting API Key Budgets

**Primary entry:** Go to the [**Budgets**](https://novita.ai/billing/budgets) page and click **View API Keys** on a member row. A panel opens showing all Keys under that member, where you can view and edit their budgets.

**Secondary entry:** Go to **LLM API → API Keys**. Budget status labels are shown in the Key list. Click a Key's details to be redirected to the Budgets page for configuration.

API Key budgets support the same three types as member budgets (Unlimited / Fixed Budget / Monthly Budget) and are configured in the same way.

<Note>
  API Key budgets only apply to LLM API usage. GPU instances, sandbox, and other services are not subject to Key budget limits, but their costs still count toward the member's total budget.
</Note>

<Note>
  When an API Key is deleted, its budget configuration is automatically archived. Historical usage records are retained and remain accessible.
</Note>

## Budget Limit Reached

When a budget is exhausted, both the dashboard and API responses indicate which budget level was reached:

* **Member budget exhausted**: "Your member budget has reached its limit (\$200 / \$200). All services are paused. Please contact your admin to adjust."
* **API Key budget exhausted**: "API Key your-key-name has reached its budget limit (\$50 / \$50). Other Keys are not affected."
* **Both limits reached at the same time**: The Key-level message is shown first, with the member budget status included as additional context.

Admins can increase the limit or switch the budget type directly from the Budgets page. Changes take effect immediately.


# FAQ
Source: https://docs.novita.ai/guides/faq



Here are some frequently asked questions about Novita AI. Before contacting our support team, please check the FAQs below to help you quickly find solutions.

## <Icon icon="credit-card" /> Payments

### 1. How can I avoid top-up failures?

Top-up failures are generally caused by two main reasons:

* **Rejection from the card issuer.** This may occur for the following reasons. Please check or contact your card issuer for details:
  * The corresponding payment channel is not activated.
  * The credit card has expired or been frozen.
  * The credit card balance is insufficient.
  * The card number is incorrect.
  * The security code is incorrect.

* **Risk control measures from the payment channel.** Please check and make any necessary adjustments:
  * The device ID is associated with a high number of cards.
  * The number of cards declined using this email address is very high.
  * The time since this card was first seen on the Stripe network with this device ID is very short.
  * The authorization rate associated with this email address is very low.
  * The name on the email address does not match the name on the card.

### 2. What payment methods are supported?

All payments are processed by Stripe. You can pay with a credit or debit card, and depending on your region Stripe may also offer local wallets such as Apple Pay or Link at checkout. Cryptocurrency payments are not supported. See [Payment Methods](/guides/payment-methods) for details.

### 3. How do I access my invoices?

Stripe emails you a receipt after each successful payment. You can also view and download your past invoices under **Billing** in the [Console](https://novita.ai/console). If you cannot find an invoice, <a href="mailto:support@novita.ai">contact support</a> with your order number.

### 4. Can I get a refund after topping up my account?

Refunds are generally not available. In exceptional circumstances (e.g., complete service unavailability), refund requests may be submitted to our support team and are typically processed between the 10th and 15th of each month.

## <Icon icon="user" /> Account

### 1. How do I delete my account?

Please provide your Novita account ID or registered email address to our support team. We will process your account deletion request within 14 business days.

### 2. Can I use the Coding Plan with a negative account balance?

No. Your account balance must remain positive to access the Coding Plan, even if you have an active subscription.

### 3. Why did my sign-up credit voucher disappear?

Our system automatically revokes vouchers when suspicious registration activity is detected (e.g., malicious sign-ups or multiple accounts from the same device). If you believe this was done in error, please <a href="mailto:support@novita.ai">contact our support team</a> for a reissuance review.

### 4. How do I change my account email address?

Contact our support team with your current registered email address and the new email address you wish to use. Our team will assist you with the update.

## <Icon icon="building-2" /> Enterprise

### 1. How do I inquire about enterprise requirements (e.g., higher rate limits, dedicated model deployments, or custom contracts)?

Please provide your company name, business use case, expected usage volume, and contact information. Our account management team will reach out to you directly. You can also <a href="https://meet.brevo.com/novita-ai/contact-sales">book a call with our sales team</a>.


# Image Prewarm
Source: https://docs.novita.ai/guides/gpu-image-prewarm



The image prewarm feature supports synchronizing specified images from the source image repository to the cluster's local cache in advance. When instances start or elastic scaling is triggered in Serverless scenarios, images can be loaded directly from the local cache, avoiding remote repository pulls, improving startup performance and optimizing scaling response time.

<Info>
  By default: The system will select cache nodes within the cluster for image prewarm, and other nodes will pull images from these nodes via the internal network when starting instances or scaling.
</Info>

## Operation Steps

1. **Navigate to the Image Prewarm page**.

2. **Create a prewarm task**:

   Click the **"Create Image Prewarm Task"** button to enter the task creation page and complete the following configurations:

   * You can **manually enter the image address**, or **directly select a template** to load the images configured in the template.
   * If the image is located in a **non-public repository**, you need to select **pre-configured image repository authentication information**.
   * Select the target **prewarm region**.
   * Select the **card type** (GPU/accelerator card type) to be prewarmed; if not selected, the system will prewarm **all card types** by default.
   * Click **"Confirm Create"**, and the system will generate the image prewarm task.

3. **View task status**:

   You can view task progress and status in real-time in the **prewarm task list**.

4. **Create a template based on prewarmed images**:

   After prewarm is complete, you can directly **create an image template** based on the prewarmed images for subsequent instance or service deployment.

<Warning>
  Notes:

  During the image prewarm process, images will be pulled in parallel across multiple nodes in the cluster. Please confirm in advance whether the source image repository has **access rate limiting policies**.

  If rate limiting or bandwidth restrictions exist, the prewarm task may remain in an executing state for an extended period or fail.
</Warning>

## Quota Limits

Currently, the image prewarm feature supports **up to 5 concurrent prewarm tasks** by default, and the **image pull quota limit per task is 100 GB**.

If you need to adjust the number of concurrent tasks or the pull quota per task, please contact technical support for assistance with configuration.


# How to Choose a GPU?
Source: https://docs.novita.ai/guides/gpu-instance-choose-a-gpu



## Introduction

Novita AI offers a variety of GPU container cloud computing specifications to meet different virtualization capability needs, supporting a wide range of business applications and service scenarios.

Each computing specification is divided into several families based on hardware capability differences. Each family utilizes different Intel processors, CPU/memory ratios, GPU cards, cloud disk types, and network card virtualization methods to achieve differentiated computing, storage, and network performance. Each family is further subdivided into various instance specifications, with higher specifications offering stronger performance.

## How to Choose a GPU

| Model | Memory | Single Precision (FP32) | Half Precision (FP16) | Description |
| - | - | - | - | - |
| Tesla P40 | 24 GB | 11.76 T | 11.76 T | Based on the earlier Pascal architecture, suitable for tasks requiring large memory and using versions of cuda before 11.x. |
| TITAN XP | 12 GB | 12.15 T | 12.15 T | An older model under the Pascal architecture, a suitable entry-level choice for beginners. |
| 1080 Ti | 11 GB | 11.34 T | 11.34 T | A product of the same generation as TITAN XP, suitable for entry-level users, though its 11 GB memory may be limiting in some cases. |
| 2080Ti | 11 GB | 13.45 T | 53.8 T | Turing architecture GPU, offers good performance, especially suitable for mixed precision computing scenarios, with a high cost-performance ratio. |
| V100 | 16/32 GB | 15.7 T | 125 T | A high-end product designed for professional computing scenarios, especially suitable for high half-precision computing tasks, leading the previous generation of compute cards. |
| 3060 | 12 GB | 12.74 T | About 24 T | If the memory of 1080 Ti does not meet the requirements, 3060 provides a good alternative, especially suitable for beginners and requires the use of cuda 11.x. |
| A4000 | 16 GB | 19.17 T | About 76 T | Balanced memory and computing power, suitable for intermediate users. Requires the use of cuda 11.x environment. |
| 3080Ti | 12 GB | 34.10 T | About 70 T | Excellent performance output makes it an ideal choice for scenarios not requiring extreme memory. Requires the use of cuda 11.x. |
| A5000 | 24 GB | 27.77 T | About 117 T | High-performance GPU, suitable for scenarios requiring large memory and high half-precision computing power. Requires the use of cuda 11.x. |
| 3090 | 24 GB | 35.58 T | About 71 T | Provides excellent performance and memory quota, suitable for a wide range of application scenarios, the first choice for cost-effectiveness. Requires the use of cuda 11.x. |
| A40 | 48 GB | 37.42 T | 149.7 T | Huge memory capacity, computing power close to 3090, suitable for computing tasks with extremely high memory requirements. Requires the use of cuda 11.x. |
| A100 SXM4 | 40/80 GB | 19.5 T | 312 T | Top professional computing GPU, with huge memory and half-precision computing capability, suitable for the most complex computing tasks. Supports NVLink, optimized for multi-card parallel computing. Requires the use of cuda 11.x. |
| 4090 | 24 GB | 82.58 T | 165.2 T | A new generation high-performance GPU, provides excellent single precision and half precision computing capability, suitable for scenarios with high cost-performance ratio. Apart from relatively small memory, it has almost no obvious shortcomings. |

## Notes

* GPU instance prices and configuration options (system disk, data disk, public IP, etc.) can be viewed on the console page. These prices are for reference only, and the actual order on the Novita AI console shall prevail.
* Instance specifications vary by region and availability zone; please refer to the actual display on the console.
* GPU instances can only be changed within the same specification family. For detailed information, please refer to the document on modifying instance specifications.
* For purchasing instances, please refer to the sections on purchasing GPU container cloud computing instances and purchasing high-performance computing GPU instances.

**Actual supply is subject to the [GPU Market](https://novita.ai/gpu-instance/console/explore).**


# Edit Instance
Source: https://docs.novita.ai/guides/gpu-instance-edit-instance



When you need to modify the "Volume Disk Size", "Expose HTTP Ports" or "Expose TCP Ports" of a GPU instance, you can use <a href="https://novita.ai">Novita AI</a> **Edit Instance** feature. Here's how to use this feature.

1. Go to the <a href="https://novita.ai/gpus-console/instances">Instances</a> page and locate the instance you want to modify. Click "More Operations - Edit".

2. In the pop-up edit window, modify the instance configuration and click the "Save" button.

<Tip>
  * You can modify the instance configuration while the instance is running, and the instance will not restart after modification;
  * Changes are expected to take effect within 1 minute.
</Tip>

<Warning>
  * The "Volume Disk Size" configuration can only be increased, not decreased;
  * New "Expose HTTP Ports" and "Expose TCP Ports" cannot conflict with ports that are already in use.
</Warning>


# GPU Instance FAQ
Source: https://docs.novita.ai/guides/gpu-instance-faq

Frequently asked questions about GPU instances on Novita AI.

## Instance Management

### How do I create a multi-GPU instance (e.g., 5× A100)?

Select your desired GPU quantity directly on the instance creation page when configuring your instance.

### What should I do if my instance fails to start?

Attempt to migrate the instance first using the **Migrate** option in the console. If no migration option is available, or the issue persists, contact customer support for further assistance.

### How long is data retained after an instance stops?

Data from stopped instances is retained for **7 days**, during which storage fees of **\$0.005/GB/day** apply. After 7 days, data is automatically and permanently deleted with no recovery option.

<Warning>
  We recommend using [Network Volume](/guides/gpu-instance-quickstart-manage-network-volume) for any data with high persistence requirements, as network volume data persists independently of the instance lifecycle.
</Warning>

### Can I change the IP address or endpoint URL for a monthly subscription instance?

Direct IP/URL changes are not supported for active subscriptions. To obtain a new instance with a different address, you must first cancel your current monthly subscription and create a new instance.

### Is ComfyUI supported on GPU instances?

Yes. ComfyUI is supported. Relevant pre-built images are available in the image repository on the instance creation page. You may also upload and use a custom image.

## Connectivity

### My SSH connection fails but the instance status shows as running. What should I do?

This is often a local SSH client or VSCode configuration issue. Try connecting directly via terminal SSH (instead of through VSCode Remote SSH) to isolate the problem. If the issue persists, check the instance logs in the console or <a href="https://discord.gg/YyPRAzwp7P">contact us</a>.

### Can I start or stop Web Terminal via API?

No. Web Terminal operations are only available through the manual controls in the Novita console. API-based control of Web Terminal is not currently supported.

## Storage

### How do I expand local storage capacity?

Contact customer support with your instance IP address and the required capacity (e.g., 30 TB or 60 TB). Our team will provide a custom quote and assist with the expansion.


# Jupyter Lab
Source: https://docs.novita.ai/guides/gpu-instance-jupyterlab



Jupyter Lab is an interactive notebook that supports over 40 programming languages. Jupyter Lab is an open-source web application that facilitates the creation and sharing of literate programming documents, supporting real-time code execution, mathematical equations, visualizations, and markdown editing.

The official templates of GPU instance come with a pre-installed Jupyter Lab environment. To enable this feature, please checkout the **"Start Jupyter Notebook"** option when deploying the instance.

<Warning>
  Some official templates may not support the Jupyter Lab environment. If you have specific requirements, please reach out to us through the <a href="https://discord.gg/YyPRAzwp7P">Discord channel</a>.
</Warning>

## Accessing Jupyter Lab

While the GPU instance is running, you can click the **"Connect"** button in the bottom right corner of the instance, then click **"Connect to Jupyter Lab"** in the pop-up window to access the Jupyter Lab webpage deployed on that instance.


# Overview
Source: https://docs.novita.ai/guides/gpu-instance-overview



<u>**GPU Instance**</u> provides cost-effective, accessible high-performance computing with easy GPU access, making it ideal for complex AI tasks. Users benefit from on-demand rental, optimizing both efficiency and cost-effectiveness.

## Features

* Rich **built-in templates** help you quickly deploy GPU instances for AI tasks out of the box;
* Start a GPU instance **in seconds**, helping you quickly handle traffic peaks;
* Provides nodes in **multiple global regions** to deploy GPUs closer to your users for minimal latency;
* Provides **free local storage** and allows you to attach network volume, also provides free network transfer;
* Billing is **accurate to the second**, charging only for the actual running time of the GPU container instances;
* **Affordable pricing** save costs by up to 50%.

## Terminology

* **Container Image**: Docker-compatible OCI image reference, supporting both public registries and private repositories with authentication.
* **Environment Variables**: Runtime configuration key-value pairs injected into containers for dynamic configuration and secrets management.
* **HTTP/TCP Ports**: Network endpoints exposed by containers for communication. HTTP ports (e.g., 80, 443) for web services, TCP ports for general network protocols.
* **Container Disk**: Root filesystem partition containing OS and system binaries. Mounted as read-write at container startup.
* **Volume Disk**: High-performance ephemeral storage directly attached to the GPU instance. Optimized for I/O intensive workloads but non-persistent across instance restarts.
* **Network Volume**: Network-attached storage service that allows users to access and manage data through a network. Data can be stored on remote servers and accessed from any device.

If you have any questions, you can first check the [FAQs for GPU Instance](/guides/gpu-instance-faq).


# GPU Instance Pricing
Source: https://docs.novita.ai/guides/gpu-instance-pricing



## On-Demand Pricing

### Introduction

On-Demand Pricing is designed to offer users the flexibility to access computational resources without any long-term commitments or upfront costs. This pricing model is ideal for users who require compute power on an as-needed basis, whether for short-term projects, variable workloads, or unpredictable processing demands.

### Billing Structure of On-Demand Pricing

Includes "computing resources" and "storage resources", you can view the billing details on the <a href="https://novita.ai/gpu-instance/console/billing">Billing Explore</a>.

#### 1. Computing Resources

**<u>Cost = Instance Unit Price × Billing Duration × Number of Cards</u>**

<Tip>
  * Billing duration is accurate to the second and settled hourly;

  * You can view the unit prices of various instance specifications on the <a href="https://novita.ai/gpu-instance/console">GPU Instance Explore</a>;

  * The billing start time is when the instance is successfully created and enters the **pulling** state, and the billing stop time is when the instance is stopped.
</Tip>

#### 2. Storage Resources

| **Billing Item** | **Billing Method** | **Explanation** | **Billing Logic** |
| - | - | - | - |
| Container Disk | Pay-as-you-go | Supports **60GB** free quota, charges for the excess based on capacity and usage duration. | Unit price for the excess capacity: **\$0.005/GB/day** |
| Volume Disk | Pay-as-you-go | Charges based on capacity and usage duration. | Unit price: **\$0.005/GB/day** |
| Network Volume | Pay-as-you-go | Charges based on capacity and usage duration. | Unit price: **\$0.002/GB/day** |

### Advantages of On-Demand Pricing for Different Scenarios

* **Short-Term Projects**: For projects with a limited duration, On-Demand Pricing allows you to align your compute costs with the project timeline.

* **Variable Workloads**: If your workloads fluctuate, On-Demand Pricing provides the flexibility to scale up or down as needed without incurring fixed costs.

* **Testing and Development**: For testing new applications or developing code, On-Demand Pricing offers a cost-effective way to access necessary compute resources without long-term financial commitments.


# Connect to Instance
Source: https://docs.novita.ai/guides/gpu-instance-quickstart-connect-to-instance



## Connect via SSH with an SSH Key (Recommended)

### Linux/MacOS

1. On Ubuntu or Mac, use the following command to generate an RSA SSH public/private key pair:

```bash theme={"system"}
ssh-keygen -t rsa
```

2. Force the agent to load the new private key and verify that it's loaded:

```bash theme={"system"}
ssh-add; ssh-add -l
```

3. Retrieve the content of your SSH public key:

```bash theme={"system"}
cat ~/.ssh/id_rsa.pub
```

1. Paste the content into the "SSH Public Keys" section in the <a href="https://novita.ai/gpu-instance/console/settings">Console - Settings</a>.

Here is an example of an SSH public key:

```txt theme={"system"}
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDopiaR1Q6Na6dW1QTK7hMuHVi+gXqutY5pUvrWCgeyjcBtBLHejxDQjdNt+qWQRORK/fZzrmyA8/ghani0rZlGPN3bYw1RXFHitt+bD1jVgtJzOmjMQdpMVALnZASQMoQQcCqVJ8PG5Xc08TciACS7S/Ltz3VdZfIq1tih969GCzCe3sctSFBuTwqUDnyrQ6iqSBhPSfQS7nCC/CwzMi3rDg0JzW2NHzIBsbvy0gdXy1ZtC4kkCr7/lUWAgZLk52v3s2CTXCg6QXnhWx+Q/dHOJVAEEucY4o8bo4rF4Bj+ifgEHFtUEfoK9+yj3OLbWfpIxyzhHK+nNXz3UaOMSC8D ultra@novita.ai
```

### Windows

Generating SSH keys on Windows requires additional command-line tools like Cmder, with operations similar to those on Linux.

Since most machine learning and AI programming is performed on Linux, we strongly recommend installing a Linux subsystem on your Windows machine, such as WSL.

### Common Issues

If you encounter a "Permission denied (publickey)" error, it might be due to:

* Attempting to run SSH/SCP from within an instance;
* Incorrect key setup or using the wrong/unmatched key;
* The SSH key format used is incorrect or unsupported;
* The private key wasn't correctly loaded with `ssh-add`; you may need to use the `-i` parameter;
* New or updated keys not taking effect for already created resources;
* Incorrect SSH key permission settings.

Use the `ssh -vv` command for detailed information.

## Connect via SSH with a Password

Go to <a href="https://novita.ai/gpu-instance/console/instances">Console - Instances</a>, select the instance you want to connect to, click "Connect", and copy the "Basic SSH Terminal" command to your terminal. Log in using the password provided in the pop-up window.


# Create Instances
Source: https://docs.novita.ai/guides/gpu-instance-quickstart-create-instances



## Step 1: Prepare Docker Image

You need to package your runtime environment into a Docker image and upload it to an image repository in advance. Currently, <a href="https://novita.ai">Novita AI</a> supports specifying both "public image repository" and "private image repository" (including access credentials).

Additionally, you can use the <a href="https://novita.ai/gpus-console/explore">public image templates</a> provided by the platform to quickly create instances.

## Step 2: Create GPU Instance

After entering the <a href="https://novita.ai/gpus-console/explore">Explore</a> page, select an image template and choose your desired specifications, then click the "Deploy" button.

<Tip>
  You can use the "Filter" feature in the bottom right corner to find the instance specifications you need.
</Tip>

## Configuration Explanation

| Configuration | Description |
| - | - |
| CPU | The number of vCPU cores you select for your instance. The vCPU count will affect the instance's processing capability and performance, with more vCPUs being beneficial for compute-intensive applications. |
| Memory | The size of RAM in GB specified for each instance. Memory is crucial for running applications and overall instance speed. |
| Supported CUDA Version | CUDA is a parallel computing platform and API model developed by NVIDIA. Choosing the correct CUDA version is important if you plan to run applications optimized for NVIDIA GPUs, such as machine learning and 3D rendering applications. |
| Ports | You can expose your ports according to your service needs. The platform will provide port mapping services for your service. Multiple ports can be entered, separated by commas. |
| Environment Variables | Used to specify parameters for the operating system's runtime environment. You can set environment variables here directly, and the platform will automatically initialize them for you. |
| GPU Instance Pricing | More details can be found at [GPU Instance Pricing](/guides/gpu-instance-pricing). |
| Base Image | The Docker image provided by the platform. |
| Custom Image | The Docker image name you specify, which the platform will default to pulling from DockerHub. Ensure that the image name is entered correctly. |

After the instance is successfully created, you can refer to the [Manage Instances](/guides/gpu-instance-quickstart-manage-instances) guide to access the instance.


# Manage Instances
Source: https://docs.novita.ai/guides/gpu-instance-quickstart-manage-instances



In this guide, we will show you how to stop or terminate your instances.

## Stop an Instance

* Go to <a href="https://novita.ai/gpu-instance/console/instances">Console - Instances</a>;
* In the instance list, select the instance you need to stop;
* Click the **More Operations - Stop** button;
* Confirm the **Stop** action in the pop-up window.

<Warning>
  After stopping your instance, you can start it later, but it is not guaranteed to be available because the resources may have been preempted, and all data will be saved for 7 days at most.

  Please note that you will be **charged for volume storage** while your instance is stopped. If you won't start this instance later, please terminate it to avoid disk charges.
</Warning>

## Terminate an Instance

* In the instance list, find the instance and click the **More Operations** button;
* In the action menu, select the **Terminate** option;
* Confirm the **Terminate** action in the pop-up window.

<Warning>
  Please note that terminating an instance will permanently delete the data on the instance.
</Warning>


# Manage Network Volume
Source: https://docs.novita.ai/guides/gpu-instance-quickstart-manage-network-volume



"Network Volume" can provide multi-instance shared storage space for GPU container instances, with the characteristics of large capacity and multi-instance sharing. Network volumes are billed on a per-second basis. Please note that if your account is in arrears and no instances are running, your Network Volume will be **released 3 days later**. Network Volume is provided in the pursuit of running tasks using its GPUs and is **not meant to be a long-term backup solution**. It is highly advisable to continually back up anything you want to save offsite locally or to a cloud provider.

## Create Network Volume

1. Go to the <a href="https://novita.ai/gpu-instance/console/storage">Console - Storage</a> page and click on "+ New Network Volume".
2. Select the "Data Center" where the Network Volume will reside. The "Data Center" is tightly bound to your instance and currently does not support cross-Data Center mounting of Network Volumes to instances.
3. Enter the "Volume Name" and capacity in GB for the Network Volume partition, and click "Save" after verifying that the details are correct.

## Edit Network Volume

You can edit your Network Volume through the console as follows:

1. Go to the <a href="https://novita.ai/gpu-instance/console/storage">Console - Storage</a> page, locate the Network Volume you plan to expand, and click the "..." button in the upper right corner.
2. Click the "Edit" button to modify the Network Volume configuration.

<Warning>
  Please note that we currently only support the expansion of the Network Volume capacity, and the storage capacity cannot be less than the size before modification.
</Warning>

## Delete Network Volume

You can delete your Network Volume through the console as follows:

1. Go to the <a href="https://novita.ai/gpu-instance/console/storage">Console - Storage</a> page, locate the Network Volume you plan to delete, and click the "..." button in the upper right corner.
2. Click the "Delete" button to delete the Network Volume.

<Warning>
  Please note that deletion of a Network Volume is irreversible. Once deleted, all data stored on it **will be permanently lost**, please proceed with caution.
</Warning>


# Preparations
Source: https://docs.novita.ai/guides/gpu-instance-quickstart-preparations



## Preliminary Preparations

Before deploying a new GPU instance, please be aware of the following points:

* First, create your Novita AI account and ensure you have enough credits in your account. Refer to our [Quickstart](/guides/quickstart) for more information.
* Confirm that you have read and understood the product introductions, pricing, features, usage restrictions, and terms of service for [Novita AI](https://novita.ai).
* Understand the billing methods for GPU instances. Here's some guidance: [GPU Instance Pricing](/guides/gpu-instance-pricing).


# Save Image
Source: https://docs.novita.ai/guides/gpu-instance-save-image



<a href="https://novita.ai">Novita AI</a> GPU instance supports the "Save Image" feature, which allows you to export the current instance as a Docker image.

For **instance migration scenarios**, you can first save the current instance as an image, and then specify the exported Docker image address when creating an instance in another region.

1. To ensure the integrity of the exported Docker image, please make sure the instance has been exited. You can also stop the instance by clicking "More Operations - Stop".

2. For instances in the "exited" state, you can click the "More Operations - Save Image" button at the bottom right corner of the instance to save the image.

3. In the pop-up form, input the "Image Path", for private repositories, include the "Container Registry Credentials", and then click the "Save" button.

4. You can view the progress of the "Save Image" job on the <a href="https://novita.ai/gpus-console/jobs">Jobs</a> page.


# Upgrade Instance
Source: https://docs.novita.ai/guides/gpu-instance-upgrade-instance



When you need to modify the "Docker Image", "Container Start Parameters" or "Container Environment Variables" of a GPU instance, you can use <a href="https://novita.ai">Novita AI</a> **Upgrade Instance** feature. Here's how to use this feature.

<Tip>
  * While upgrading the instance, the instance will be reset, but the resources originally occupied by the instance **will be retained**.
</Tip>

<Warning>
  * When upgrading the instance, the platform will save your system disk data, but if the data is incompatible with the upgraded instance system, the data cannot be successfully restored to the new instance.
</Warning>

1. Go to the <a href="https://novita.ai/gpus-console/instances">Instances</a> page and locate the instance you want to upgrade. Click "More Operations - Upgrade".

2. In the pop-up edit dialog, modify the image and other configurations, and click the "Upgrade" button after confirming everything is correct.


# Overview
Source: https://docs.novita.ai/guides/gpus-overview



Novita AI **GPUs** provides:

* **[GPU Instance](/guides/gpu-instance-overview):** A dedicated virtual machine with full control over GPU hardware, ideal for long-running, predictable workloads.
* **[Serverless GPUs](/guides/serverless-gpus-overview):** A fully managed, on-demand GPU service that dynamically scales and charges based on actual usage, perfect for burstable and sporadic tasks.

<CardGroup>
  <Card title="Create your first GPU instance" icon="box" href="/guides/gpu-instance-quickstart-preparations">
    Here's a guide on how to create, manage and use your first GPU instance.
  </Card>

  <Card title="Set up a Serverless Endpoint from Scratch" icon="box" href="/guides/serverless-gpus-quickstart-preparations">
    To learn about the Serverless GPUs, follow this guide.
  </Card>
</CardGroup>


# Run Kohya_SS GUI on Novita AI
Source: https://docs.novita.ai/guides/kohya_ss-gui

Transform Your Stable Diffusion Experience with Kohya_SS GUI on Novita AI. 

Kohya\_SS GUI offers a user-friendly Gradio interface for Kohya's Stable Diffusion trainers. It has strong Linux support through community installations, with limited but possible macOS compatibility. Combining Novita AI with Kohya\_SS GUI streamlines AI model training workflows, removing hardware limitations and creating an optimized environment for diffusion model fine-tuning.

This guide will show you how to deploy and run Kohya\_SS GUI on Novita AI's platform.

## How to Run Kohya\_ss: GUI on Novita AI

Step 1: Access GPU Instance Control Panel

* Navigate to the `GPU menu` in the top navigation bar.
* Click `Get Started` to enter the GPU Instance control interface.

<Frame>
  ### &#x20; <img alt="images/Step1AccessGPUInstanceControlPanel.jpg" />
</Frame>

Step 2: Select Kohya\_ss:GUI Template

* Locate and select the `Kohya_ss:GUI` official template.
* Click `Deploy` button under the 4090 GPU card option to enter the instance creation page.

<Frame>
  ### &#x20; <img alt="images/Step2SelectKohya_ssGUITemplate.png" />
</Frame>

Step 3: Configure Disk Parameters and Review Configuration Settings

* On the left panel, adjust the disk settings as needed:
  * Set appropriate system disk size;
  * Configure local disk capacity based on your storage needs.
* Check the right panel for configuration options:
  * Verify image settings are correct;
  * Confirm startup commands are properly configured;
  * Ensure ports and environment variables meet your requirements.
* Confirm all settings are correct and then click the `Next` button to advance to the final confirmation page.

<Frame>
  ### &#x20; <img alt="images/Step3ConfigureDiskParametersandReviewConfigurationSettings.png" />
</Frame>

Step 4: Proceed to Confirmation and Deploy Your Instance

* Review the complete instance configuration summary.
* Verify the cost details displayed on this page.
* Click `Deploy` to initiate the deployment process.

<Frame>
  ### &#x20; <img alt="images/Step4ProceedtoConfirmationandDeployYourInstance.png" />
</Frame>

Step 5: Wait as the System Creates Your Instance

<Frame>
  ### &#x20; <img alt="images/Step5WaitastheSystemCreatesYourInstance.jpg" />
</Frame>

Step 6: Monitor Deployment Progress and Track Image Download

* After deployment, the system will automatically redirect you to the instance management page.
* Your new instance will display `Pulling` status while downloading the image.
* After clicking the arrow icon next to your instance name, the instance details panel will be expanded with the image download progress in real-time.
* Once image downloading completes, instance will change status from `Pulling` to `Running`.

<Frame>
  ### &#x20; <img alt="images/Step6MonitorDeploymentProgressandTrackImageDownload.png" />
</Frame>

Step 7: Check Instance Logs

* Click the `Logs` button on your instance and select `Instance Logs` from the available options.
* Observe the Kohya\_ss service startup process in the logs and wait for confirmation that all services have loaded successfully.

<Frame>
  ### &#x20; <img alt="images/Step7CheckInstanceLogs.png" />
</Frame>

Step 8: Connect to Your Instance

* Close the logs view when ready and click the `Connect` button to view connection options.
* View various connection methods: SSH, TCP, and HTTP of your instance.
* For Kohya\_SS GUI access, focus on the HTTP connection details. Therefore, in the Connection Options section, click `Connect to HTTP Service` and access to a new browser tab or window.

<Frame>
  ### &#x20; <img alt="images/Step8-1ConnecttoYourInstance.png" />
</Frame>

<Frame>
  <img alt="images/Step8-2ConnecttoYourInstance.png" />
</Frame>

Step 9: Begin Using Your Instance

* Allow a few moments for the web interface to fully load and get ready to run Kohya\_ss:GUI on Novita AI.

<Frame>
  <img alt="images/Step9BeginUsingYourInstance.png" />
</Frame>


# Low Balance Alert
Source: https://docs.novita.ai/guides/low-balance-alert



To prevent service interruption due to insufficient balance, you can enable a low balance alert.

Once set, if your account balance drops below your designated threshold, the system will notify you via **Email** or **Inbox** to ensure continuous service.

<Note>
  The alert will be sent at most once per day (from 00:00 to 24:00 UTC+0), and notifications will stop once your balance rises above the set threshold.
</Note>

## How to Set Up

1. Go to the [Low Balance Alert page](https://novita.ai/billing/balance-warning) and click "Add Alerts".

2. In the pop-up dialog, enter your desired warning amount, enable the "Active" status, and click "Confirm".

* The warning amount must be between \$1 and \$1,000.
* The interval between amounts with an active warning status must be at least \$5.


# LLM API Metrics
Source: https://docs.novita.ai/guides/observability-llm-api-metrics



Novita AI provides comprehensive monitoring metrics for your LLM API usage. These metrics give you insights into the availability and performance of your LLM API requests.

You can access these metrics through the [LLM Metrics Console](https://novita.ai/model-api/console/llm-metrics).

## Available Metrics

<Tip>
  All the following metrics are **dimensioned by model**, and sampled on a **per-minute basis**, but depending on the interval you select, the sampling points may not be displayed per minute. In such cases, the sampling points within that time interval will be averaged before being displayed.
</Tip>

**Request Per Minute (RPM)**

Shows the number of API requests made per minute, helping you understand usage patterns and API concurrency levels.

**Request Success Rate**

Shows the percentage of successful API responses (non-5xx status codes) made per minute, indicating API availability.

**Average Token Count Per Request**

Shows the average number of input and output tokens per request made per minute, useful for understanding token consumption patterns.

**End-to-end (E2E) Latency**

Shows the overall time it takes for the model to generate the full response for requests made per minute. Includes 99th percentile, 95th percentile, and average latency metrics.

**Time to First Token (TTFT)**

<Tip>
  This metric is only tracked for streaming requests with the `stream=true` parameter is enabled.
</Tip>

Shows the time required to process the prompt and generate the first output token for requests made per minute. Includes 99th percentile, 95th percentile, and average latency metrics.

**Time Per Output Token (TPOT)**

<Tip>
  This metric is only tracked for streaming requests when the `stream=true` parameter is enabled.
</Tip>

Shows the average time between consecutive output tokens for requests made per minute. Includes 99th percentile, 95th percentile, and average latency metrics.


# Payment Methods
Source: https://docs.novita.ai/guides/payment-methods



Novita AI uses **Stripe** for all payment processing.

<Tip>All your payment information is securely stored by Stripe, and we <u>do not store</u> any payment information.</Tip>

You can manually top up a specified amount of credit, which must be greater than \$10. Additionally, you can enable the [Automatic Top-Up](/guides/auto-top-up), which will automatically add credit if your balance falls below a specified threshold.

<Frame>
  <img />
</Frame>


# Advanced Features
Source: https://docs.novita.ai/guides/sandbox-agent-runtime-advanced



This document covers advanced features and best practices for Novita Agent Runtime.

***

## Table of Contents

* [Configuration File Reference](#configuration-file-reference)
* [Environment Variables Management](#environment-variables-management)
* [Streaming Responses](#streaming-responses)
* [Version Management](#version-management)
* [Health Checks](#health-checks)
* [Multi-turn Conversations](#multi-turn-conversations)
* [Example Projects](#example-projects)

***

## Configuration File Reference

### .novita-agent.yaml Structure

The `.novita-agent.yaml` configuration file uses Kubernetes-style YAML format:

```yaml theme={"system"}
apiVersion: v1
kind: Agent
metadata:
  name: my-agent              # Agent name (must consist of lowercase letters, numbers, and hyphens only)
  version: 1.0.0              # Agent version (semantic versioning)
  author: dev@example.com     # Author email (required)
  description: My AI Agent    # Agent description (optional)
  created: '2025-10-23T10:30:00Z'  # Creation time (auto-generated)

spec:
  entrypoint: app.py          # Python entry file (must be .py file)
  
  # Environment variables configuration (optional)
  envVars:
    MODEL_NAME: deepseek/deepseek-v4.1-flash
    TEMPERATURE: '0.7'
  
  # Runtime configuration (optional, applied to the built sandbox template)
  runtime:
    timeout: 300              # Startup timeout in seconds (1-3600, default 300)
    memory_limit: 1Gi         # Memory limit (supports "512Mi", "1Gi", etc.)
    cpu_limit: '1'            # CPU limit (supports "1", "1000m", etc.)

# Status field (maintained by the system, should not be modified manually by users)
status:
  phase: deployed             # Current phase: pending/building/deployed/failed
  agent_id: agent-xxxxx       # Agent ID (auto-generated after deployment)
  last_deployed: '2025-10-23T10:35:00Z'  # Last deployment time
  build_id: build_xyz789      # Build ID (auto-generated after deployment)
```

### Modifying Configuration

#### Modifying CPU and Memory Settings

Modify resource configuration under `spec.runtime` in `.novita-agent.yaml`:

```yaml theme={"system"}
spec:
  runtime:
    # CPU configuration
    cpu_limit: '2'        # 2 CPU cores
    # Memory configuration
    memory_limit: 2Gi     # 2 GB memory
```

#### Modifying Environment Variables

The `spec.envVars` in `.novita-agent.yaml` is only used for the CLI's `agent invoke` command and will not be passed to the deployed sandbox template.

Modify environment variables under `spec.envVars` in `.novita-agent.yaml`:

```yaml theme={"system"}
spec:
  envVars:
    # LLM configuration
    MODEL_NAME: deepseek/deepseek-v4.1-flash
    TEMPERATURE: '0.7'
```

**Note**:

* ⚠️ **Do not store sensitive information** (such as API Keys) in `.novita-agent.yaml`
* You can also pass environment variables via the `--env` parameter when running the `agent invoke` command

#### Redeploy to Apply Configuration Changes

After modifying resource specifications in `.novita-agent.yaml`, redeploy is required:

```bash theme={"system"}
# Redeploy (creates a new version)
npx novita agent launch
```

***

## Environment Variables Management

There are several ways to pass environment variables to Agents running in sandbox instances:

### Method 1: Define in Configuration File (CLI invocation only)

Define environment variables under `spec.envVars` in `.novita-agent.yaml`:

```yaml theme={"system"}
spec:
  envVars:
    MODEL_NAME: deepseek/deepseek-v4.1-flash
    TEMPERATURE: '0.7'
```

### Method 2: Pass Dynamically via SDK

When invoking an Agent using the SDK's `invoke_agent_runtime` method, pass them dynamically via the `envVars` parameter:

```python theme={"system"}
import os
from novita_sandbox.agent_runtime import AgentRuntimeClient

client = AgentRuntimeClient(api_key=os.getenv("NOVITA_API_KEY"))

response = await client.invoke_agent_runtime(
    agentId="agent-xxxxx",
    payload=payload,
    envVars={
        # Read sensitive information from environment variables
        "NOVITA_API_KEY": os.getenv("NOVITA_API_KEY"),
        "DATABASE_PASSWORD": os.getenv("DATABASE_PASSWORD"),
        
        # Or pass directly
        "MODEL_NAME": "deepseek/deepseek-v4.1-flash",
        "TEMPERATURE": "0.7"
    }
)
```

***

## Streaming Responses

### Implementing Streaming with Synchronous Generators

Use Python generators to implement streaming responses:

```python theme={"system"}
from novita_sandbox.agent_runtime import AgentRuntimeApp

app = AgentRuntimeApp()

@app.entrypoint
def streaming_agent(request: dict):
    """Synchronous streaming response"""
    prompt = request.get("prompt", "")

    # Use generator to return chunks
    for i, chunk in enumerate(generate_response(prompt)):
        yield {
            "chunk": chunk,
            "type": "content",
            "index": i
        }
    # Send end marker
    yield {"chunk": "", "type": "end"}

```

### Implementing Streaming with Async Generators

Use Python async generators:

```python theme={"system"}
import asyncio

@app.entrypoint
async def async_streaming_agent(request: dict):
    """Async streaming response"""
    prompt = request.get("prompt", "")

    async for chunk in async_generate_response(prompt):
        yield {
            "chunk": chunk,
            "type": "content"
        }
    yield {"chunk": "", "type": "end"}

```

### LangChain Streaming Response Example

Complete example using LangChain for streaming responses:

```python theme={"system"}
import os
from langchain_openai import ChatOpenAI
from langchain.callbacks.base import BaseCallbackHandler
from novita_sandbox.agent_runtime import AgentRuntimeApp

app = AgentRuntimeApp()

class StreamingHandler(BaseCallbackHandler):
    """Streaming callback handler"""
    def __init__(self):
        self.tokens = []
    
    def on_llm_new_token(self, token: str, **kwargs):
        self.tokens.append(token)

@app.entrypoint
def langchain_streaming_agent(request: dict):
    """LangChain streaming response"""
    prompt = request.get("prompt", "")

    # Create streaming-enabled LLM
    llm = ChatOpenAI(
        api_key=os.getenv("NOVITA_API_KEY"),
        streaming=True
    )
    
    # Stream invocation
    for chunk in llm.stream(prompt):
        if chunk.content:
            yield {
                "chunk": chunk.content,
                "type": "content"
            }
    yield {"chunk": "", "type": "end"}
```

### Invoking a Streaming Agent

Invoke a streaming Agent using the SDK:

```python theme={"system"}
import asyncio
import json
import os
from novita_sandbox.agent_runtime import AgentRuntimeClient

async def call_streaming_agent():
    client = AgentRuntimeClient(api_key=os.getenv("NOVITA_API_KEY"))
    
    payload = json.dumps({
        "prompt": "Tell me a story"
    }).encode()
    
    response = await client.invoke_agent_runtime(
        agentId="agent-xxxxx",
        payload=payload
    )
    
    # Process streaming response
    print("Streaming response:")
    print(response)
```

***

## Version Management

### Deploying a New Agent Version

Modify the version number and deploy a new version:

```bash theme={"system"}
# Modify version number
npx novita agent configure --agent-version 1.1.0

# Deploy new version
npx novita agent launch
```

After successful deployment, a new `agent_id` is generated. Each deployment generates a unique `agent_id` that corresponds to a specific version.

***

## Health Checks

### Default Health Check Endpoint

AgentRuntimeApp automatically provides a `/ping` health check endpoint:

```python theme={"system"}
from novita_sandbox.agent_runtime import AgentRuntimeApp

app = AgentRuntimeApp()

# Default health check automatically responds with {"status": "Healthy"}
```

### Custom Health Checks

Use the `@app.ping` decorator to customize health check logic:

```python theme={"system"}
@app.ping
def custom_health_check():
    """Custom health check"""
    # Check dependent services
    db_ok = check_database_connection()
    llm_ok = check_llm_service()
    
    if db_ok and llm_ok:
        return {"status": "Healthy"}
    elif db_ok or llm_ok:
        return {"status": "HealthyBusy"}  # Partially available
    else:
        return {"status": "Unhealthy"}  # Unavailable

def check_database_connection():
    """Check database connection"""
    try:
        # Simulate database check
        return True
    except:
        return False

def check_llm_service():
    """Check LLM service"""
    try:
        # Simulate LLM service check
        return True
    except:
        return False
```

### Supported Health Check Statuses

Agents can return the following health statuses:

| Status | Description | HTTP Status Code |
| - | - | - |
| `Healthy` | Agent is fully available | 200 |
| `HealthyBusy` | Agent is partially available (e.g., processing heavy load) | 200 |
| `Unhealthy` | Agent is unavailable | 503 |

***

## Multi-turn Conversations

### Using Session ID for Multi-turn Conversations

Use the `runtimeSessionId` parameter to route multiple requests to the same sandbox instance:

```python theme={"system"}
import uuid
import json
import os
from novita_sandbox.agent_runtime import AgentRuntimeClient

async def multi_turn_conversation():
    runtime_session_id = str(uuid.uuid4())
    client = AgentRuntimeClient(api_key=os.getenv("NOVITA_API_KEY"))
    agent_id = "agent-xxxxx"
    
    # First turn
    response1 = await client.invoke_agent_runtime(
        agentId=agent_id,
        payload=json.dumps({"prompt": "Hello, my name is John"}).encode(),
        runtimeSessionId=runtime_session_id,
    )
    print(f"AI: {response1}")
    
    # Second turn (sent to the same sandbox instance, Agent remembers the context)
    response2 = await client.invoke_agent_runtime(
        agentId=agent_id,
        payload=json.dumps({"prompt": "What's my name?"}).encode(),
        runtimeSessionId=runtime_session_id,
    )
    print(f"AI: {response2}")  # Should answer "John"
```

***

## Example Projects

We provide a complete example project based on LangGraph, demonstrating how to build real AI applications with Novita Agent Runtime.

### Project Repository

🔗 [https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/agent-runtime/agentic-frameworks/langgraph](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/agent-runtime/agentic-frameworks/langgraph)


# Agent Framework Integration Guide
Source: https://docs.novita.ai/guides/sandbox-agent-runtime-frameworks



This document shows you how to integrate agents built with various AI frameworks into Novita Agent Runtime.

We provide integration examples for popular AI Agent frameworks:

* [LangGraph](#langgraph) - Build complex multi-step Agent workflows
* [OpenAI Agents SDK](#openai-agents-sdk) - OpenAI's official Agent framework
* [Microsoft AutoGen](#microsoft-autogen) - Microsoft's multi-Agent conversation framework
* [Google ADK](#google-adk) - Google Agent Development Kit

***

## Core Integration Pattern

All framework integrations follow this core pattern:

```python theme={"system"}
from novita_sandbox.agent_runtime import AgentRuntimeApp

# 1. Create Agent Runtime application instance
app = AgentRuntimeApp()

# 2. Initialize your Agent framework

# 3. Define entry point with decorator
@app.entrypoint
def agent_invocation(request: dict) -> dict:
    """
    Args:
        request: Request data, typically contains fields like prompt
    Returns:
        Response data dictionary
    """
    prompt = request.get("prompt", "")
    
    # Call your Agent framework
    result = your_agent.run(prompt)
    
    return {"result": result}

# 4. Run the application
if __name__ == "__main__":
    app.run()
```

***

## LangGraph

LangGraph is the official framework from LangChain for building stateful, multi-step Agent applications.

### Example Code

For the complete example project, see [here](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/agent-runtime/agentic-frameworks/langgraph)

```python theme={"system"}
from langchain_community.chat_models import ChatOpenAI
from typing_extensions import TypedDict
from typing import Annotated
from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition

# Import Novita Agent Runtime
from novita_sandbox.agent_runtime import AgentRuntimeApp

app = AgentRuntimeApp()

# Define state
class State(TypedDict):
    messages: Annotated[list, add_messages]

# Initialize LLM
llm = ChatOpenAI(model="gpt-4")

# Define tools
def get_weather(location: str) -> str:
    """Get weather information for a specified location"""
    return f"The weather in {location} is sunny, 23°C"

tools = [get_weather]
llm_with_tools = llm.bind_tools(tools)

# Define node function
def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

# Build graph
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", ToolNode(tools=tools))
graph_builder.add_conditional_edges(
    "chatbot",
    tools_condition,
)
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")

graph = graph_builder.compile()

# Define entry point
@app.entrypoint
def agent_invocation(request: dict) -> dict:
    """LangGraph Agent entry point"""
    prompt = request.get("prompt", "Hello!")
    
    # Invoke LangGraph
    result = graph.invoke({
        "messages": [{"role": "user", "content": prompt}]
    })
    
    # Extract the last message
    final_message = result['messages'][-1].content
    
    return {"result": final_message}

if __name__ == "__main__":
    app.run()
```

***

## OpenAI Agents SDK

OpenAI Agents SDK is the official toolkit from OpenAI for building AI Agents.

### Example Code

For the complete example project, see [here](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/agent-runtime/agentic-frameworks/openai-agents-sdk)

```python theme={"system"}
import os
from novita_sandbox.agent_runtime import AgentRuntimeApp
from openai import AsyncOpenAI

# Create application
app = AgentRuntimeApp()

# Define tool function
def get_weather(city: str) -> str:
    """Get city weather (simulated)"""
    weather_data = {
        "Beijing": "Sunny, 15°C",
        "Shanghai": "Cloudy, 20°C",
        "Shenzhen": "Light rain, 25°C",
    }
    return weather_data.get(city, f"{city}: Sunny, 23°C")

# Tool definition
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather information for a specified city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name, e.g. 'Beijing', 'Shanghai'"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

# Agent core logic
async def run_agent(query: str) -> str:
    """Run OpenAI Agent (supports function calling)"""
    client = AsyncOpenAI(
        base_url=os.getenv("OPENAI_API_BASE"),
        api_key=os.getenv("NOVITA_API_KEY"),
    )
    
    messages = [
        {"role": "system", "content": "You are a helpful AI assistant that can query weather."},
        {"role": "user", "content": query}
    ]
    
    # First call
    response = await client.chat.completions.create(
        model=os.getenv("MODEL_NAME", "deepseek/deepseek-v4.1-flash"),
        messages=messages,
        tools=TOOLS,
        tool_choice="auto"
    )
    
    response_message = response.choices[0].message
    
    # If tool call is needed
    if response_message.tool_calls:
        messages.append(response_message)
        
        for tool_call in response_message.tool_calls:
            function_args = eval(tool_call.function.arguments)
            function_response = get_weather(**function_args)
            
            messages.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": "get_weather",
                "content": function_response
            })
        
        # Second call to get final response
        final_response = await client.chat.completions.create(
            model=os.getenv("MODEL_NAME", "deepseek/deepseek-v4.1-flash"),
            messages=messages
        )
        return final_response.choices[0].message.content
    
    return response_message.content

# Novita Agent Runtime entry point
@app.entrypoint
async def agent_invocation(request: dict) -> dict:
    """Entry point function"""
    prompt = request.get("prompt", "Hello!")
    result = await run_agent(prompt)
    return {"result": result}

# Start application
if __name__ == "__main__":
    app.run()
```

***

## Microsoft AutoGen

AutoGen is a multi-Agent conversation framework developed by Microsoft that supports multi-Agent collaboration and conversations, capable of working independently or with humans.

### Example Code

For the complete example project, see [here](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/agent-runtime/agentic-frameworks/autogen)

```python theme={"system"}
import os
from novita_sandbox.agent_runtime import AgentRuntimeApp
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_core.models import ModelFamily, ModelInfo

# Create application
app = AgentRuntimeApp()

# Define tool function
async def get_weather(city: str) -> str:
    """Get weather for a specified city"""
    weather_data = {
        "Beijing": "Sunny, 15°C",
        "Shanghai": "Cloudy, 20°C",
        "Shenzhen": "Light rain, 25°C",
    }
    return weather_data.get(city, f"{city}: Sunny, 23°C")

# Create AutoGen Agent
def create_agent():
    """Create AutoGen Agent"""
    model_client = OpenAIChatCompletionClient(
        base_url=os.getenv("OPENAI_BASE_URL", "https://api.novita.ai/v3/openai"),
        model=os.getenv("MODEL_NAME", "deepseek/deepseek-v4.1-flash"),
        api_key=os.getenv("OPENAI_API_KEY"),
        model_info=ModelInfo(
            vision=False,
            function_calling=True,
            json_output=True,
            family=ModelFamily.UNKNOWN,
        ),
    )
    
    agent = AssistantAgent(
        name="assistant",
        model_client=model_client,
        tools=[get_weather],
        system_message="You are a helpful AI assistant that can query weather information.",
        reflect_on_tool_use=True,
    )
    
    return agent

# Run Agent
async def run_agent(prompt: str) -> str:
    """Run AutoGen Agent"""
    agent = create_agent()
    
    # Create user message
    message = TextMessage(content=prompt, source="user")
    
    # Run Agent
    response_message = await agent.on_messages([message])
    
    # Extract response content
    if response_message and hasattr(response_message, 'chat_message'):
        return response_message.chat_message.content
    
    return str(response_message)

# Novita Agent Runtime entry point
@app.entrypoint
async def agent_invocation(request: dict):
    """Entry point function"""
    prompt = request.get("prompt", "Hello!")
    result = await run_agent(prompt)
    return {"result": result}

# Start application
if __name__ == "__main__":
    app.run()
```

***

## Google ADK

Google Agent Development Kit (ADK) is Google's toolkit for Agent development.

### Complete Example

For the complete example project, see [here](https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/agent-runtime/agentic-frameworks/google-adk)

```python theme={"system"}
import os
import uuid
import asyncio
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import google_search
from google.genai import types
from novita_sandbox.agent_runtime import AgentRuntimeApp

# Create application
app = AgentRuntimeApp()

APP_NAME = "google_search_agent"

# Create Google ADK Agent
root_agent = LlmAgent(
    model=os.getenv("GEMINI_MODEL", "gemini-2.0-flash"), 
    name=APP_NAME,
    instruction="I can answer your questions by searching the internet. Ask me anything!",
    tools=[google_search]
)

# Create Session service and Runner
session_service = InMemorySessionService()
runner = Runner(
    agent=root_agent, 
    app_name=APP_NAME, 
    session_service=session_service
)

# Run Agent
async def run_agent(query: str) -> str:
    """Run Google ADK Agent"""
    user_id = "user_default"
    session_id = str(uuid.uuid4())
    
    # Create Session
    await session_service.create_session(
        app_name=APP_NAME, 
        user_id=user_id, 
        session_id=session_id
    )
    
    # Create user message
    user_content = types.Content(
        role='user', 
        parts=[types.Part(text=query)]
    )
    
    # Run Agent
    result = ""
    async for event in runner.run_async(
        user_id=user_id, 
        session_id=session_id, 
        new_message=user_content
    ):
        if event.is_final_response() and event.content and event.content.parts:
            result = event.content.parts[0].text
    
    return result

# Novita Agent Runtime entry point
@app.entrypoint
def agent_invocation(request: dict):
    """Entry point function"""
    prompt = request.get("prompt", "Hello!")
    result = asyncio.run(run_agent(prompt))
    return {"result": result}

# Start application
if __name__ == "__main__":
    app.run()
```


# Installation
Source: https://docs.novita.ai/guides/sandbox-agent-runtime-installation



This feature is currently in **beta**. You need to install the beta versions of both the Novita Sandbox Python SDK and the CLI tool:

***

## Install Python SDK with pip

Use the `--pre` flag to install the latest beta version:

```bash theme={"system"}
pip install novita-sandbox==1.2.5b1
```

***

## Install Node.js CLI

### Option 1: Run with npx (Recommended)

Install the beta CLI locally in your project directory using `npm`, then run it with `npx`. This avoids polluting your global environment with the beta version.

```bash theme={"system"}
# Install the latest beta version CLI
npm install novita-sandbox-cli@beta

# First, complete authentication
npx novita auth login

# Run agent configure command to configure your Agent
npx novita agent configure
```

### Option 2: Global Installation

If you use the CLI frequently, you can install it globally:

```bash theme={"system"}
# Install the latest beta version
npm install -g novita-sandbox-cli@beta
```

After installation, you can use the command directly:

```bash theme={"system"}
novita agent configure
```


# Overview
Source: https://docs.novita.ai/guides/sandbox-agent-runtime-introduction



## What is Novita Agent Runtime?

Novita Agent Runtime is a lightweight framework for deploying AI Agents that enables you to **deploy AI Agents quickly and cost-effectively**.

No need to worry about infrastructure configuration, container orchestration, service exposure, or other complex details—just focus on developing your Agent's business logic.

***

## Core Components

Novita Agent Runtime is included in the Novita Sandbox SDK and CLI tools:

| Component | Description |
| - | - |
| **Novita Sandbox SDK** | Provides decorator-based APIs to expose your Agent as a standard HTTP service, and methods to invoke Agents |
| **Novita Sandbox CLI** | One-click configuration and deployment of Agents to the Novita Agent Sandbox ecosystem |

***

## Deployment Workflow

The complete deployment and usage workflow consists of three steps:

### Step 1: Develop and Integrate Your Agent

Integrate the SDK into your Agent code by adding decorators:

```python theme={"system"}
from novita_sandbox.agent_runtime import AgentRuntimeApp

app = AgentRuntimeApp()

@app.entrypoint
def my_agent(request: dict):
    # Agent business logic
    return {"result": "..."}
```

### Step 2: Configure and Deploy

Use the CLI tool to configure and deploy to the cloud:

```bash theme={"system"}
# Configure Agent
novita agent configure

# Deploy to cloud
novita agent launch
```

* Generates `Dockerfile` and `.novita-agent.yaml` configuration files
* Builds sandbox template and uploads it
* Generates Agent ID (format: `agent_<name>_v<version>`)

### Step 3: Invoke Agent

After successful deployment, you can invoke your Agent via CLI or SDK:

**Option 1: Quick test with CLI**

```bash theme={"system"}
novita agent invoke "Hello, Agent!"
```

**Option 2: Invoke via SDK in your application**

```python theme={"system"}
import json
from novita_sandbox.agent_runtime import AgentRuntimeClient

client = AgentRuntimeClient(api_key="your-api-key")

# Prepare request data (converted to a JSON string and encoded as bytes)
payload = json.dumps({"prompt": "Hello, Agent!"}).encode()

response = await client.invoke_agent_runtime(
    agentId="agent-xxxxx",
    payload=payload
)
```

**Agent invocation execution flow**:

* Creates an isolated sandbox instance from the sandbox template
* Executes the Agent in an isolated environment
* Returns the processed result

***

## Key Benefits

SDK:

* ✅ **Minimal Changes**: Just modify a few lines of code with decorators
* ✅ **Framework Agnostic**: Supports LangChain, LangGraph, CrewAI, and other popular AI frameworks
* ✅ **Streaming Support**: Native streaming responses for real-time LLM generation scenarios
* ✅ **Health Checks**: Built-in health check endpoint with customizable health status

CLI:

* ✅ **Smart Detection**: Auto-detects project structure, entry files, and dependency management files
* ✅ **Auto Build**: Automatically generates Dockerfile and project configuration
* ✅ **Version Management**: Supports multiple versions coexisting with independent deployments
* ✅ **Quick Testing**: Built-in invoke command for rapid deployment verification

Runtime:

* ✅ **Environment Isolation**: Each sandbox instance runs independently without interference
* ✅ **Session Persistence**: Multiple invocations to the same sandbox instance, supporting multi-turn interactive Agents
* ✅ **Secure Sandbox**: Securely isolated sandbox environment with restricted filesystem access


# Quick Start
Source: https://docs.novita.ai/guides/sandbox-agent-runtime-quick-start



This guide will help you **develop, deploy, and invoke your first Agent in 5 minutes**.

***

## Table of Contents

* [Overview](#overview)
* [Prerequisites](#prerequisites)
* [Step 1: Integrate SDK into Your Agent](#step-1-integrate-sdk-into-your-agent)
* [Step 2: Generate Configuration Files with CLI](#step-2-generate-configuration-files-with-cli)
* [Step 3: Deploy to Novita with One-Click](#step-3-deploy-to-novita-with-one-click)
* [Step 4: Invoke Agent via CLI or SDK](#step-4-invoke-agent-via-cli-or-sdk)
* [FAQ](#faq)

***

## Overview

The complete workflow consists of 4 steps:

1. ✅ **Integrate SDK** - Add decorators to your Agent code
2. ✅ **Configure Agent** - Generate configuration files using CLI
3. ✅ **Deploy Agent** - One-click deployment to the cloud
4. ✅ **Invoke Agent** - Call your Agent using SDK or CLI

***

## Prerequisites

Ensure you have the following prerequisites:

* ✅ Python 3.9+ and Node.js 20+ installed
* ✅ Beta version Python SDK and Node.js CLI installed (see [Installation Guide](/guides/sandbox-agent-runtime-installation))
* ✅ Novita AI API Key obtained (from the [Console](https://novita.ai/settings/key-management))
* ✅ [Docker](https://www.docker.com/products/docker-desktop/) installed

***

## Step 1: Integrate SDK into Your Agent

### 1.1 Create Agent Code

Create `app.py` in your project directory:

```python theme={"system"}
from novita_sandbox.agent_runtime import AgentRuntimeApp

# Create Agent Runtime application instance
app = AgentRuntimeApp()

# Define Agent entry point with decorator
@app.entrypoint
def my_agent(request: dict) -> dict:
    """
    Agent entry function
    
    Args:
        request: Request data, which typically contains fields like prompt
        
    Returns:
        Response data dictionary
    """
    prompt = request.get("prompt", "")
    
    # Agent business logic
    # You can call LLMs, use Agent frameworks, or implement any custom logic here
    result = f"Received message: {prompt}"
    
    return {"result": result}

# Local run entry point
if __name__ == "__main__":
    app.run()
```

### 1.2 Prepare Dependencies File

Ensure your project root has a `requirements.txt` file with the required dependencies:

```txt theme={"system"}
novita-sandbox>=1.1.0b1
# Your other dependencies...
```

### 1.3 Local Testing

Test locally before deployment:

```bash theme={"system"}
# Start the Agent service
python app.py
```

In another terminal, test the endpoints:

```bash theme={"system"}
# Test health check
curl http://localhost:8080/ping

# Test Agent invocation
curl -X POST http://localhost:8080/invocations \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello, Agent!"}'
```

**Expected output**:

Health check should return:

```json theme={"system"}
{"status": "Healthy"}
```

Agent invocation should return:

```json theme={"system"}
{"result": "Received message: Hello, Agent!"}
```

***

## Step 2: Generate Configuration Files with CLI

Use CLI to interactively configure your Agent:

```bash theme={"system"}
# Make sure environment variables are configured
export NOVITA_API_KEY=your-novita-api-key

# Run configuration command
npx novita agent configure
```

Follow the CLI prompts to configure the required information. When complete, the CLI will generate the following files in your project directory:

* `novita.Dockerfile` - Docker build file
* `.dockerignore` - Docker ignore file
* `.novita-agent.yaml` - Agent configuration file

***

## Step 3: Deploy to Novita AI with One-click

> ⚠️ **Important**: After successful deployment, an `agent_id` will be generated. This is the unique identifier for invoking your Agent—make sure to save it.

### 3.1 Deploy Command

Deploy with one-click using CLI:

```bash theme={"system"}
npx novita agent launch
```

### 3.2 View Deployment Results

After successful deployment, the `.novita-agent.yaml` file will update the `status` field:

```yaml theme={"system"}
status:
  phase: deployed
  agent_id: agent-xxxx  # ⭐ This is your Agent's unique identifier
  last_deployed: '2025-10-23T10:35:00Z'
  build_id: build_xyz789
```

**Record the `agent_id`—you'll need it for subsequent invocations.**

***

## Step 4: Invoke Agent via CLI or SDK

### Option 1: Quick Test with CLI

Quickly test your Agent using CLI:

```bash theme={"system"}
npx novita agent invoke "Hello, Agent!"
```

**Note**: The CLI automatically reads the Agent ID from the `status.agent_id` field in `.novita-agent.yaml`.

**Expected output**:

```json theme={"system"}
{"result": "Received message: Hello, Agent!"}
```

### Option 2: SDK Invocation (Recommended for Production)

Use the SDK to invoke your Agent in backend services:

#### Example Code

```python theme={"system"}
import asyncio
import json
import os
from novita_sandbox.agent_runtime import AgentRuntimeClient
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Create Agent Runtime client
client = AgentRuntimeClient(
    api_key=os.getenv("NOVITA_API_KEY")
)

async def main():
    # Get from status.agent_id in .novita-agent.yaml after deployment
    agent_id = "agent-xxxx"
    
    # Prepare request data
    payload = json.dumps({
        "prompt": "Hello, Agent! Tell me something about AI."
    }).encode()
    
    # Invoke Agent
    print(f"🚀 Invoking agent: {agent_id}")
    response = await client.invoke_agent_runtime(
        agentId=agent_id,
        payload=payload,
        timeout=300
    )
    
    print(f"✅ Response: {response}")

if __name__ == "__main__":
    asyncio.run(main())
```

***

## FAQ

### Q1: How do I get the Agent ID?

After successful deployment, the Agent ID is saved in the `status.agent_id` field of the `.novita-agent.yaml` file:

```yaml theme={"system"}
status:
  agent_id: agent-xxxxx  # Here
```

### Q2: What if deployment fails?

1. **Check dependencies file**: Ensure `requirements.txt` contains all dependencies
2. **View detailed logs**: Use the `--verbose` flag
3. **Check network connection**: Ensure you can access the Novita AI Sandbox domain (e.g., `sandbox.novita.ai`)
4. **Verify API Key**: Confirm `NOVITA_API_KEY` is correct

```bash theme={"system"}
# Show detailed logs
npx novita agent launch --verbose
```

### Q3: How do I update a deployed Agent?

Modify your code and redeploy:

```bash theme={"system"}
# Option 1: Create new version (Recommended)
npx novita agent configure --agent-version 1.1.0
npx novita agent launch

# Option 2: Update existing version
npx novita agent launch --update-existing
```

### Q4: Works locally but fails after deployment?

**Possible causes**:

1. Environment variables not passed to sandbox instance
2. Dependency package version inconsistencies
3. File path issues

**Solution**:

Pass environment variables to the sandbox instance via the `envVars` parameter:

```python theme={"system"}
response = await client.invoke_agent_runtime(
    agentId=agent_id,
    payload=payload,
    envVars={
        "NOVITA_API_KEY": os.getenv("NOVITA_API_KEY"),
        "MODEL_NAME": "deepseek/deepseek-v4.1-flash"
    }
)
```


# Configure
Source: https://docs.novita.ai/guides/sandbox-cli-agent-configure



The `agent configure` command sets up a Novita Agent Runtime project. It analyzes your project directory, collects agent metadata, and generates the configuration and build files needed to deploy your agent. Run it from your project root.

## Usage

```bash CLI icon="terminal" theme={"system"}
novita agent configure [options]
```

By default it runs interactively, auto-detecting sensible defaults from your project (agent name, entry point, dependency file) and prompting you to confirm or adjust them.

```bash CLI icon="terminal" theme={"system"}
# Interactive configuration using auto-detected defaults
novita agent configure

# Non-interactive with explicit values
novita agent configure \
  --name my-agent \
  --entrypoint app.py \
  --author me@example.com \
  --no-interactive
```

## Options

| Option | Description |
| - | - |
| `-n, --name <name>` | Agent name. Default: auto-detected from the project. |
| `-e, --entrypoint <file>` | Entry point file. Default: auto-detected, or `app.py`. |
| `--agent-version <version>` | Agent version. Default: `1.0.0`. |
| `-a, --author <email>` | Author email. Default: from environment or interactive prompt. |
| `-rf, --requirements-file <file>` | Dependency file path (`requirements.txt`, `pyproject.toml`, etc.). |
| `--no-interactive` | Skip interactive prompts and use only defaults / provided options. |
| `--force` | Force overwrite of an existing configuration. |
| `--verbose` | Verbose output. |

## What it generates

On success, the command writes three files into your project and prints their paths:

* The agent **configuration file** — a Kubernetes-style manifest describing the agent.
* A **Dockerfile** — used to build the agent runtime image.
* A **.dockerignore** — controls which files are excluded from the build context.

```text Output icon="code" theme={"system"}
Configuration completed successfully!
Configuration file: <project>/agent.yaml
Dockerfile: <project>/Dockerfile
Docker ignore: <project>/.dockerignore
```

## Configuration structure

The generated configuration uses a Kubernetes-style manifest. The key fields are:

| Field | Description |
| - | - |
| `apiVersion` / `kind` | Manifest identifiers, fixed as `v1` and `Agent`. |
| `metadata.name` | Agent name (lowercase letters, numbers, and hyphens). Used as the base for the generated template name. |
| `metadata.version` | Agent version. |
| `metadata.author` | Author email (required). |
| `metadata.description` | Optional agent description. |
| `spec.entrypoint` | Python entry file, e.g. `agent.py` (must be a `.py` file). |
| `spec.envVars` | Optional environment variables for the agent runtime. |
| `spec.runtime.timeout` | Startup timeout in seconds (1–3600). |
| `spec.runtime.memory_limit` | Memory limit, e.g. `512Mi` or `1Gi`. |
| `spec.runtime.cpu_limit` | CPU limit, e.g. `1` or `1000m`. |

<Note>
  The `status` section of the manifest (deployment phase, agent ID, build ID, last deployed time) is maintained by the system during deployment. Do not edit it manually.
</Note>

## Overwriting an existing configuration

If a configuration file already exists, the behavior depends on the flags:

* With `--force`, the existing configuration is overwritten without prompting.
* In interactive mode, you are prompted to type `Y` to confirm the overwrite; any other input cancels the operation.
* With `--no-interactive` (and no `--force`), the configuration is automatically overwritten.


# Invoke
Source: https://docs.novita.ai/guides/sandbox-cli-agent-invoke



The CLI's `agent invoke` command sends a payload to a deployed Novita Agent and returns its response. It supports both plain-text prompts and structured JSON input, and can stream the response as it is produced.

<Note>
  **Note:** Invoke runs from a deployed agent's project directory. It requires a `novita.toml` in the current directory (created by `agent launch`) and the `NOVITA_API_KEY` environment variable.
</Note>

***

## Prerequisites

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<your API key>

# The agent must already be deployed (run from its project directory)
# novita agent launch
```

***

## Usage

```bash CLI icon="terminal" theme={"system"}
novita agent invoke <payload> [options]
```

| Argument / Option | Description |
| - | - |
| `<payload>` | JSON payload or prompt text (required) |
| `--agentId <id>` | Agent ID to invoke, in the form `agent_name-template_id` |
| `--stream` | Stream the response as it is generated |
| `--timeout <seconds>` | Request timeout in seconds (default: 60) |
| `--env <key=value>` | Environment variable for this invocation. Repeatable |
| `--verbose` | Verbose output |

***

## Payload

The `<payload>` is parsed as JSON if possible; otherwise it is treated as plain prompt text.

* A **string** is wrapped as `{ "query": "...", "prompt": "..." }`.
* A **JSON object** is passed through; if it has `query` but no `prompt`, `prompt` is filled in from `query`.

***

## Agent ID & environment variables

The command resolves which agent to call and what env vars to pass as follows:

| Item | Resolution |
| - | - |
| Agent ID | `--agentId` if given; otherwise `status.agent_id` from `.novita-agent.yaml` |
| Env vars | `spec.envVars` in `.novita-agent.yaml` merged with `--env` — CLI values override the config file |

***

## Streaming vs non-streaming

Without `--stream`, the command waits for the full result and prints the agent ID, session ID, execution time, and the response body. With `--stream`, output chunks are written to the terminal as they arrive.

***

## Examples

```bash CLI icon="terminal" theme={"system"}
novita agent invoke "Summarize today's news"
```

```bash CLI icon="terminal" theme={"system"}
novita agent invoke '{"query": "list files", "path": "/tmp"}'
```

```bash CLI icon="terminal" theme={"system"}
novita agent invoke "Analyze this repo" \
  --agentId my-agent-tpl_123 \
  --stream \
  --env LOG_LEVEL=debug \
  --timeout 120
```


# Launch
Source: https://docs.novita.ai/guides/sandbox-cli-agent-launch



The `agent launch` command deploys your agent to Novita Sandbox. It reads the project configuration produced by `agent configure`, builds the agent template, and deploys it, reporting the resulting agent, template, and build identifiers. Run it from your project root.

## Prerequisites

* Set the `NOVITA_API_KEY` environment variable.
* A configured project — run `agent configure` first so the configuration file and Dockerfile exist.

## Usage

```bash CLI icon="terminal" theme={"system"}
novita agent launch [options]
```

```bash CLI icon="terminal" theme={"system"}
# Deploy using the project configuration
novita agent launch

# Validate everything without deploying
novita agent launch --dry-run

# Update the existing template instead of creating a new one
novita agent launch --update-existing
```

## Options

| Option | Description |
| - | - |
| `--timeout <seconds>` | Deployment timeout in seconds. Default: `300`. |
| `--no-cache` | Disable the build cache and rebuild from scratch. |
| `--dry-run` | Perform a dry run — validate the project and configuration without actually deploying. |
| `--update-existing` | Update the existing template instead of creating a new one. |
| `--verbose` | Verbose output. |

## What it does

When you run `agent launch`, the CLI performs the deployment in sequence:

* Checks prerequisites — the configuration file and Dockerfile must exist.
* Loads and validates the agent configuration.
* Verifies the `NOVITA_API_KEY` and checks service health.
* Builds the agent template and deploys it (unless `--dry-run` is set).

## Output

On success, the CLI reports the deployment identifiers and status:

```text Output icon="code" theme={"system"}
🎉 Agent deployment completed successfully! 🎉
Agent ID: <agent-id>
Template ID: <template-id>
Build ID: <build-id>
Status: <status>
```

<Note>
  With `--dry-run`, no template is built or deployed. The command validates the project and configuration and reports a dry-run result, so you can confirm everything is in order before a real deployment.
</Note>


# Authentication
Source: https://docs.novita.ai/guides/sandbox-cli-auth



Before using the Novita Sandbox CLI, you need to authenticate. The `novita auth` command group manages your login session, active team, and local credentials. Credentials are stored locally at `~/.novita/config.json`.

## Login

Use `auth login` to sign in to your Novita account. The CLI opens a browser-based authorization page; once you approve, it captures your access token, selects your default team, and writes the credentials to `~/.novita/config.json`.

```bash CLI icon="terminal" theme={"system"}
novita auth login
```

On success you will see the logged-in email and the selected team:

```text Output icon="code" theme={"system"}
Logged in as you@example.com with selected team my-team
```

If you are already logged in, the command reports the current session instead of signing in again. To sign in as a different user, run `auth logout` first; to switch teams, run `auth configure`.

## Logout

Use `auth logout` to sign out. This deletes the local credential file at `~/.novita/config.json`.

```bash CLI icon="terminal" theme={"system"}
novita auth logout
```

If a session exists, the CLI prints `Logged out.`; if you are not logged in, it prints `Not logged in, nothing to do`.

## Info

Use `auth info` to show information about the current user, including the logged-in email and the selected team.

```bash CLI icon="terminal" theme={"system"}
novita auth info
```

Example output:

```text Output icon="code" theme={"system"}
You are logged in as you@example.com,
Selected team: my-team (team-id)
```

If no credentials are found, it prints `Not logged in`.

## Configure

Use `auth configure` to change the active team for an existing session. The CLI fetches the teams you belong to and prompts you to select one; the chosen team's name, ID, and API key are saved back to `~/.novita/config.json`.

```bash CLI icon="terminal" theme={"system"}
novita auth configure
```

You must be logged in first. If no user config is found, the CLI asks you to run `novita auth login` before configuring a team. After selecting, it confirms the chosen team:

```text Output icon="code" theme={"system"}
Team my-team (team-id) selected.
```


# Execute
Source: https://docs.novita.ai/guides/sandbox-cli-execute-commands



The `sandbox exec` command runs a single command inside an already-running sandbox and streams its output back to your terminal. Use it for one-off commands, scripts, and automation; for an interactive shell session, use the remote shell (`connect`) command instead.

## Prerequisites

Set the `NOVITA_API_KEY` environment variable before running the command. You also need the ID of a running sandbox.

## Usage

```bash CLI icon="terminal" theme={"system"}
novita sandbox exec <sandboxID> <command...>
```

The command has the alias `ex`. Everything after the sandbox ID is treated as the command to execute.

```bash CLI icon="terminal" theme={"system"}
# Run a simple command
novita sandbox exec sbx-123 ls -l /tmp

# Using the alias
novita sandbox exec sbx-123 python3 --version
```

## Arguments

| Argument | Description |
| - | - |
| `<sandboxID>` | ID of the running sandbox to execute the command in. |
| `<command...>` | The command to execute. Multiple parts are quoted and joined into a single shell command. |

## Options

| Option | Description |
| - | - |
| `-b, --background` | Run the command in the background and return immediately. The process ID (PID) is printed, and the CLI exits with code `0`. |
| `-c, --cwd <dir>` | Working directory to run the command in. |
| `-u, --user <user>` | Run the command as the specified user. |
| `-e, --env <KEY=VALUE>` | Set an environment variable for the command. Repeatable to set multiple variables. |

## Foreground execution

By default, the command runs in the foreground. Its `stdout` and `stderr` stream to your terminal in real time, and the CLI exits with the same exit code as the remote command.

```bash CLI icon="terminal" theme={"system"}
# Run in a specific directory, as a specific user, with env vars
novita sandbox exec sbx-123 \
  --cwd /home/user/app \
  --user root \
  --env LOG_LEVEL=debug \
  --env REGION=us-east \
  ./run.sh
```

## Background execution

Pass `-b` / `--background` to start the command and return immediately without waiting for it to finish. The CLI prints the process ID and exits with code `0`.

```bash CLI icon="terminal" theme={"system"}
novita sandbox exec sbx-123 --background \
  "python3 -m http.server 3000"
```

## Piping stdin

You can pipe data into the command through standard input; the CLI streams it to the remote process and signals end-of-file when the input is exhausted, so tools like `cat`, `wc`, and `grep` terminate correctly.

```bash CLI icon="terminal" theme={"system"}
# Pipe local data into a command running in the sandbox
cat local-file.txt | novita sandbox exec sbx-123 "wc -l"

# Redirect a file into the command
novita sandbox exec sbx-123 "grep error" < app.log
```

<Warning>
  Piped stdin requires a recent sandbox version. If the sandbox does not support it, the CLI prints a warning and ignores the piped input — rebuild your template to pick up the latest sandbox version.
</Warning>

## Exit codes and interruption

* In the foreground, the CLI exits with the remote command's exit code, so it composes naturally with shell scripts and CI.
* If the command cannot be started or another error occurs, the CLI prints the error and exits with code `1`.
* Pressing `Ctrl+C` (or sending a termination signal) kills the remote process before the CLI exits.


# Memory
Source: https://docs.novita.ai/guides/sandbox-cli-memory



The `sandbox hotplug-memory` command dynamically adjusts the memory of a running sandbox without restarting it. This is useful when a workload needs more memory than initially provisioned, or when you want to release extra memory once it is no longer needed.

<Warning>
  Memory hotplug requires a **hotplug-enabled template**. Sandboxes created from templates without hotplug support cannot adjust memory at runtime.
</Warning>

## Prerequisites

* Set the `NOVITA_API_KEY` environment variable.
* A running sandbox created from a hotplug-enabled template.

## Usage

```bash CLI icon="terminal" theme={"system"}
novita sandbox hotplug-memory <sandboxID> <size-mib>
```

The command has the alias `hp`.

```bash CLI icon="terminal" theme={"system"}
# Set the hotplugged memory of the sandbox to 2048 MiB
novita sandbox hotplug-memory sbx-123 2048

# Using the alias
novita sandbox hp sbx-123 2048
```

## Arguments

| Argument | Description |
| - | - |
| `<sandboxID>` | ID of the running sandbox to adjust. |
| `<size-mib>` | New **total** hotplugged memory size in MiB — not a delta. Use `0` to unplug all hotplugged memory. |

<Warning>
  The size represents the new total plugged memory, not an increment on top of the current value. It must be a multiple of the virtio-mem block size (2 MiB).
</Warning>

## Unplug all hotplugged memory

Pass `0` to remove all previously hotplugged memory, returning the sandbox to its base allocation.

```bash CLI icon="terminal" theme={"system"}
novita sandbox hotplug-memory sbx-123 0
```

## Output and errors

* On success, the CLI prints a confirmation such as `Memory hotplug complete: sandbox sbx-123 set to 2048 MiB`.
* If the sandbox ID is not found, the CLI prints `Sandbox sbx-123 wasn't found` and exits with code `1`.
* Any other error is printed and the CLI exits with code `1`.


# Network
Source: https://docs.novita.ai/guides/sandbox-cli-network



The CLI's `network` command updates the **egress (outbound) rules** of a running sandbox, letting you control which external destinations the sandbox is allowed or denied to reach. Rules are applied to a sandbox by its ID and take effect on the running sandbox.

<Note>
  **Note:** If no `allowOut` is specified, all outbound traffic is allowed. Omitting both `--allow-out` and `--deny-out` clears all egress rules.
</Note>

***

## Prerequisites

Set your API key in the environment before running the command:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<your API key>
```

***

## Usage

```bash CLI icon="terminal" theme={"system"}
novita sandbox network <sandboxID> [--allow-out <destination>] [--deny-out <destination>]
```

| Option / Argument | Value | Description |
| - | - | - |
| `<sandboxID>` | string | ID of the running sandbox to update (required) |
| `--allow-out` | destination | Allowed outbound destination. Repeatable — pass multiple times to allow several destinations |
| `--deny-out` | destination | Denied outbound destination. Repeatable — pass multiple times to deny several destinations |

A `destination` is an IP address or CIDR range, for example `1.1.1.1` or `8.8.8.0/24`.

***

## Examples

```bash CLI icon="terminal" theme={"system"}
# Only allow egress to these two destinations
novita sandbox network <sandboxID> \
  --allow-out 1.1.1.1 \
  --allow-out 8.8.8.0/24
```

```bash CLI icon="terminal" theme={"system"}
novita sandbox network <sandboxID> \
  --deny-out 1.1.1.1 \
  --deny-out 8.8.8.0/24
```

```bash CLI icon="terminal" theme={"system"}
# Omit both options to remove all egress rules
novita sandbox network <sandboxID>
```

On success the CLI prints:

```text Output icon="code" theme={"system"}
Sandbox <sandboxID> network has been updated
```

<Note>
  **Note:** If the sandbox ID does not exist, the command reports `Sandbox <sandboxID> wasn't found` and exits with a non-zero status.
</Note>


# Remote Shell
Source: https://docs.novita.ai/guides/sandbox-cli-remote-shell



The CLI's `connect` command attaches an interactive terminal — a **Remote Shell** — to an already-running sandbox. It opens a real shell inside the sandbox over a PTY, streaming your keystrokes in and the shell's output back, so you can run commands, inspect state, and debug as if you were sitting at the sandbox's terminal.

<Warning>
  **Note:** The sandbox must already be running. `connect` attaches to an existing sandbox by its ID; it does not create one. Disconnecting leaves the sandbox and any other users' sessions untouched.
</Warning>

***

## Prerequisites

Set your API key in the environment before connecting:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<your API key>
```

***

## Usage

```bash CLI icon="terminal" theme={"system"}
novita sandbox connect <sandboxID>

# Using the short aliases (sandbox → sbx, connect → cn)
novita sbx cn <sandboxID>
```

| Item | Value | Description |
| - | - | - |
| Command | `sandbox connect` | Connect a terminal to an already-running sandbox |
| Alias | `sbx cn` | Short form of `sandbox connect` |
| Argument | `<sandboxID>` | ID of the running sandbox to connect to (required) |

On connect, the CLI prints the sandbox's dashboard inspect URL and a confirmation line, then hands your terminal over to the sandbox shell:

```text Output icon="code" theme={"system"}
Terminal connecting to sandbox <sandboxID>
user@sandbox:~$ 
```

***

## Ending the session

Exit the shell as you normally would — type `exit` or press **Ctrl+C**. The CLI cleans up the terminal, closes the connection, and prints:

```text Output icon="code" theme={"system"}
Closing terminal connection to sandbox <sandboxID>
```

<Note>
  **Note:** Disconnecting only ends your terminal session. It does not stop or kill the sandbox, and it does not disconnect other users connected to the same sandbox.
</Note>


# Build Template
Source: https://docs.novita.ai/guides/sandbox-cli-template-create



`novita template create` builds a Dockerfile directly into a reusable sandbox template. It reads the Dockerfile, builds the image, applies sandbox settings (start / ready command, CPU, memory), and registers the result under the template name — which you can then pass to `sandbox.create(...)`.

***

## Usage

```bash CLI icon="terminal" theme={"system"}
novita template create <template-name> [options]
```

## Argument

| Argument | Required | Description |
| - | - | - |
| `<template-name>` | Yes | Template name to create or rebuild. Must be lowercase and contain only letters, numbers, dashes and underscores (`^[a-z0-9-_]+$`). |

## Options

| Option | Default | Description |
| - | - | - |
| `-p, --path <path>` | current directory | Change the root directory where the command is executed. Dockerfile lookup and the build file context are resolved relative to it. |
| `-d, --dockerfile <file>` | `novita.Dockerfile`, then `Dockerfile` | Path to the Dockerfile, relative to the root directory. Without this option the CLI looks for `novita.Dockerfile` first and falls back to `Dockerfile`. |
| `-c, --cmd <start-command>` | — | Command executed when the sandbox is started. Must be used together with `--ready-cmd`. |
| `--ready-cmd <ready-command>` | — | Command that must exit 0 for the template to be considered ready. Can be used on its own. |
| `--cpu-count <cpu-count>` | `2` | Number of CPUs used to run the sandbox. |
| `--memory-mb <memory-mb>` | `2048` | Memory in megabytes used to run the sandbox. Must be an even number. |
| `--no-cache` | off | Skip the build cache when building the template. |
| `--patch-cmd <cmd>` | — | Command to patch an incompatible base image at the end of the provision phase. |

## Behavior and validation

* Authentication is required: log in with `novita auth login` or set `NOVITA_API_KEY` in the environment.
* Providing `--cmd` without `--ready-cmd` fails with `Both start and ready commands must be provided.`; `--ready-cmd` alone is allowed.
* An invalid template name or an odd `--memory-mb` value aborts the build before anything is sent to the server.
* Build logs are streamed to the terminal. On success the CLI prints Python / JS snippets showing how to create a sandbox from the new template.

## Examples

An example Dockerfile used by the commands below:

```docker Dockerfile icon="docker" theme={"system"}
FROM python:3.12

WORKDIR /home/user/app

RUN pip install --no-cache-dir flask requests

COPY app.py .

ENV PORT=8080

CMD ["python", "app.py"]
```

### Minimal build

Reads `./novita.Dockerfile` (or `./Dockerfile`) in the current directory:

```bash CLI icon="terminal" theme={"system"}
novita template create my-python-template
```

### Specify a Dockerfile explicitly

Point the build at a specific Dockerfile relative to the root directory:

```bash CLI icon="terminal" theme={"system"}
novita template create my-python-template --dockerfile ./Dockerfile
```

### Full build

Custom root directory, resources, start/ready commands, and no cache:

```bash CLI icon="terminal" theme={"system"}
novita template create my-python-template \
  --path ./app \
  --dockerfile ./docker/novita.Dockerfile \
  --cpu-count 4 \
  --memory-mb 2048 \
  --cmd "python app.py" \
  --ready-cmd "curl -sf http://localhost:8080/health" \
  --no-cache
```

Once the build finishes, create sandboxes from the template by name:

```typescript JavaScript & TypeScript icon="js" theme={"system"}
const sandbox = await novita.sandbox.create('my-python-template')
```


# Migrate
Source: https://docs.novita.ai/guides/sandbox-cli-template-migrate



The CLI's `migrate` command converts a legacy template — a `novita.Dockerfile` plus a `novita.toml` config — into the new **Template SDK** format, generating template and build scripts in the language you choose (TypeScript, sync Python, or async Python).

<Note>
  **Note:** After a successful migration, the original `novita.Dockerfile` and `novita.toml` are renamed with a `.old` suffix — they are no longer needed by the new SDK-based build.
</Note>

***

## Usage

```bash CLI icon="terminal" theme={"system"}
novita template migrate [options]
```

If you don't pass `--language`, the command prompts you to pick a target language interactively (default: TypeScript).

### Options

| Option | Description |
| - | - |
| `-d, --dockerfile <file>` | Path to the Dockerfile. Defaults to `novita.Dockerfile` |
| `--config <novita-toml>` | Path to the config toml. Defaults to `./novita.toml` |
| `-l, --language <language>` | Target language: `typescript`, `python-sync`, or `python-async` |
| `-p, --path <path>` | Change the root directory where the command runs |

***

## What it does

* Reads the Dockerfile and `novita.toml`, then parses the Dockerfile with the SDK (`fromDockerfile`) to build the equivalent template definition.
* Carries over config values — start / ready commands, CPU count, and memory — into the generated template.
* Writes the template and build scripts for the chosen language.
* Renames the old `novita.Dockerfile` and `novita.toml` to `.old`.

<Warning>
  **Note:** If the Dockerfile cannot be fully converted, the command falls back to a placeholder image (`my-custom-image`) and prints instructions to build and push the Docker image manually, then update the generated files with the real image tag.
</Warning>

***

## Examples

```bash CLI icon="terminal" theme={"system"}
novita template migrate --language typescript
```

```bash CLI icon="terminal" theme={"system"}
novita template migrate \
  --dockerfile custom.Dockerfile \
  --language python-async
```

After migrating, get started with the generated files:

```bash CLI icon="terminal" theme={"system"}
npm install novita-sandbox
npx tsx build.dev.ts   # development build
npx tsx build.prod.ts  # production build
```

```bash CLI icon="terminal" theme={"system"}
pip install novita-sandbox
python build_dev.py    # development build
python build_prod.py   # production build
```


# Publish & Unpublish
Source: https://docs.novita.ai/guides/sandbox-cli-template-publish



The CLI's `publish` and `unpublish` commands control the visibility of a sandbox template. **Publishing** makes a template public to everyone outside your team; **unpublishing** makes it private to your team again. Under the hood both set the template's `public` flag.

<Warning>
  **Note:** By default the command asks for confirmation before changing visibility, warning that publishing makes the template public to everyone outside your team. Pass `-y` / `--yes` to skip the prompt.
</Warning>

***

## Prerequisites

Set your API key in the environment:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<your API key>
```

***

## Usage

```bash CLI icon="terminal" theme={"system"}
# Publish (alias: tpl pb)
novita template publish [template]

# Unpublish (alias: tpl upb)
novita template unpublish [template]
```

The `[template]` argument is optional. There are three ways to choose which template(s) to act on:

| Way to select | Behavior |
| - | - |
| Pass `[template]` | Act on the template with the given template ID |
| Omit `[template]` | Use the template defined by `novita.toml` in the root directory |
| `-s` / `--select` | Pick from an interactive list (only shows templates eligible for the action) |

### Options

| Option | Description |
| - | - |
| `-p, --path <path>` | Change the root directory where the command runs |
| `--config <novita-toml>` | Path to the sandbox config toml (defaults to `./novita.toml`) |
| `-s, --select` | Select template(s) from an interactive list |
| `-t, --team <team-id>` | Team ID the operation is associated with (used with `--select`) |
| `-y, --yes` | Skip the manual confirmation prompt |

***

## Examples

```bash CLI icon="terminal" theme={"system"}
novita template publish <templateID>
```

```bash CLI icon="terminal" theme={"system"}
novita template publish -y
```

```bash CLI icon="terminal" theme={"system"}
novita template unpublish --select --team <teamID>
```

On success, publishing prints the public names the template was published as:

```text Output icon="code" theme={"system"}
- Publishing sandbox template <template>
  Published as: <name>
```


# Computer Use
Source: https://docs.novita.ai/guides/sandbox-computer-use



***Novita Desktop*** is a secure virtual desktop framework specifically engineered for developing Computer Use Agents. This solution provides a comprehensive, isolated desktop environment that enables AI agents to securely interact with desktop applications through controlled virtualization. This document will provide detailed instructions on how to run this on Novita Agent sandbox service.

***

### 1. Get Novita API Key

### 2. Environment Configuration

Before you start using the service, you need to configure the necessary environment variables:

```bash CLI icon="terminal" theme={"system"}
export NOVITA_API_KEY=<Your API key obtained in the previous step>
```

### 3. SDK Installation

Select the appropriate installation method corresponding to your development environment and programming language:

```bash CLI icon="terminal" theme={"system"}
npm i novita-sandbox
```

```bash CLI icon="terminal" theme={"system"}
pip install novita-sandbox
```

### 4. Implementation Examples

#### Get Virtual Desktop Stream VNC URL

The following implementation demonstrates the instantiation of a virtual desktop environment and the retrieval of VNC access endpoints for remote desktop interaction:

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  // demo.ts implementation
  import { Novita } from 'novita-sandbox'

  async function main() {
    const novita = new Novita()

    // Initialize virtual desktop sandbox instance
    const desktop = await novita.desktop.create()

    // Activate desktop streaming service
    await desktop.stream.start()

    // Retrieve interactive VNC endpoint URL
    const url = desktop.stream.getUrl()
    console.log(url)

    // Expected output format:
    // Browser-accessible URL for interactive virtual desktop session. Suitable for application integration.
    // https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&resize=scale

    // Retrieve read-only VNC endpoint URL (interaction disabled)
    const urlDisabledInteraction = desktop.stream.getUrl({ viewOnly: true })
    console.log(urlDisabledInteraction)
    // Expected output format:
    // Browser-accessible URL for view-only virtual desktop session (non-interactive mode). Suitable for monitoring applications.
    // https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&view_only=true&resize=scale

    // Keep the program running
    console.log('Desktop stream started, press Ctrl+C to stop the program...')
    const interval = setInterval(() => {}, 1000)

    // Register interrupt signal handlers for resource cleanup
    let isCleaning = false
    const cleanup = async () => {
      if (isCleaning) return
      isCleaning = true

      console.log('\nProgram interrupted, cleaning up resources...')
      clearInterval(interval)
      try {
        await desktop.stream.stop() // Terminate streaming service
        await desktop.kill() // Deallocate sandbox instance
      } catch (err) {
        console.error('Error cleaning up resources:', err)
      }
      process.exit(0)
    }

    process.on('SIGINT', cleanup)
    process.on('SIGTERM', cleanup)
  }

  main().catch(console.error)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # Initialize virtual desktop sandbox instance
  desktop = novita.desktop.create()

  # Activate desktop streaming service
  desktop.stream.start()

  # Retrieve interactive VNC endpoint URL
  url = desktop.stream.get_url()
  print(url)
  # Expected output format:
  # Browser-accessible URL for interactive virtual desktop session. Suitable for application integration.
  # https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&resize=scale

  # Retrieve read-only VNC endpoint URL (interaction disabled)
  url_readonly = desktop.stream.get_url(view_only=True)
  print(url_readonly)
  # Expected output format:
  # Browser-accessible URL for view-only virtual desktop session (non-interactive mode). Suitable for monitoring applications.
  # https://6080-ik0n7lc3j0dvqd4jxy6g7.us-phx-1.sandbox.novita.ai/vnc.html?autoconnect=true&view_only=true&resize=scale

  # Implement keyboard interrupt handling for graceful shutdown
  try:
      print('Desktop stream started, press Ctrl+C to stop the program...')
      import time
      while True:
          time.sleep(0.1)
  except KeyboardInterrupt:
      print('\nProgram interrupted, cleaning up resources...')

  # Execute resource deallocation procedures
  desktop.stream.stop()  # Terminate streaming service
  desktop.kill()  # Deallocate sandbox instance
  ```
</CodeGroup>

Running example:

<img alt="Computer Use" />

Access virtual desktop stream VNC URL:

<img alt="Computer Use 2" />


# Filesystem Overview
Source: https://docs.novita.ai/guides/sandbox-filesystem



Each sandbox has a completely isolated filesystem. Each sandbox currently includes 20 GB of storage.

This section covers how to operate the sandbox filesystem using the SDK:

* [Read & write files](/guides/sandbox-filesystem-read-write)
* [File & directory metadata](/guides/sandbox-filesystem-metadata)
* [Watch sandbox directory for changes](/guides/sandbox-filesystem-watch)
* [Upload data to sandbox](/guides/sandbox-filesystem-upload)
* [Download data from sandbox](/guides/sandbox-filesystem-download)
* [Mount object storage bucket](/guides/sandbox-mount-cloudstorage)


# Download
Source: https://docs.novita.ai/guides/sandbox-filesystem-download



You can use the `files.read()` method to download data from the sandbox.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import fs from 'fs'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  // Create a file in the sandbox for testing
  const filePathInSandbox = '/tmp/test-file'
  await sandbox.files.write(filePathInSandbox, 'test-file-content')

  // Read file from sandbox
  const content = await sandbox.files.read(filePathInSandbox)

  // Write file to local filesystem
  const localFilePath = './local-test-file'
  fs.writeFileSync(localFilePath, content)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  # Create a file in the sandbox for testing
  file_path_in_sandbox = '/tmp/test-file'
  sandbox.files.write(file_path_in_sandbox, 'test-file-content')

  # Read file from sandbox
  content = sandbox.files.read(file_path_in_sandbox)

  # Write file to local filesystem
  local_file_path = './local-test-file'
  with open(local_file_path, 'w') as file:
      file.write(content)

  sandbox.kill()
  ```
</CodeGroup>

## Download with pre-signed URL

Pre-signed download URLs allow users to download files securely from environments that do not hold Novita SDK credentials, such as web browsers.

Create the sandbox with the `secure: true` option, then use the SDK on your trusted backend to generate the download URL. Return it only to users authorized by your application, and set a short expiration time. The URL acts as a temporary bearer credential until it expires.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import fs from 'fs'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create({ secure: true })

  // Create a test file in the sandbox (requires SDK authentication)
  const filePathInSandbox = '/tmp/test-file'
  await sandbox.files.write(filePathInSandbox, 'test-file-content')

  // Generate pre signed download URL (valid for 120 seconds, optional)
  const publicDownloadUrl = await sandbox.downloadUrl(filePathInSandbox, {
    useSignatureExpiration: 120, // optional, in seconds
  })

  // Simulate "browser/unauthorized environment": without API key, directly GET download
  const res = await fetch(publicDownloadUrl)
  if (!res.ok) {
    throw new Error(`Download failed: ${res.status} ${await res.text()}`)
  }
  const content = await res.text()

  // Write to local file
  const localFilePath = './local-test-file'
  fs.writeFileSync(localFilePath, content)
  console.log(content)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from pathlib import Path

  import requests
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create(secure=True)

  # Create a test file in the sandbox (requires SDK authentication)
  file_path_in_sandbox = '/tmp/test-file'
  sandbox.files.write(file_path_in_sandbox, 'test-file-content')

  # Generate pre signed download URL (valid for 120 seconds, optional)
  signed_url = sandbox.download_url(
      path=file_path_in_sandbox,
      use_signature_expiration=120,  # optional, in seconds
  )

  # Simulate "browser/unauthorized environment": without API key, directly GET download
  response = requests.get(signed_url, timeout=60)
  response.raise_for_status()
  content = response.text

  # Write to local file
  local_file_path = Path('./local-test-file')
  local_file_path.write_text(content)
  print(content)

  sandbox.kill()
  ```
</CodeGroup>


# File & Directory
Source: https://docs.novita.ai/guides/sandbox-filesystem-metadata



You can use the `files.getInfo()` / `files.get_info()` method to retrieve metadata about a file or directory. It returns details such as the filename, type, and path.

***

## Getting metadata about a file

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  // Create test file
  const filePathInSandbox = '/tmp/test-file'
  await sandbox.files.write(filePathInSandbox, 'test-file-content')

  // Get file metadata
  const info = await sandbox.files.getInfo(filePathInSandbox)
  console.log(info)
  // {
  //   name: 'test-file',
  //   type: 'file',
  //   path: '/tmp/test-file',
  //   size: 17,
  //   mode: 420,
  //   permissions: '-rw-r--r--',
  //   owner: 'user',
  //   group: 'user',
  //   modifiedTime: 2026-06-03T12:12:23.964Z,
  //   symlinkTarget: undefined
  // }

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  # Create test file
  file_path_in_sandbox = '/tmp/test-file'
  sandbox.files.write(file_path_in_sandbox, 'test-file-content')

  # Get file metadata
  info = sandbox.files.get_info(file_path_in_sandbox)
  print(info)
  # EntryInfo(
  #     name='test-file',
  #     type=<FileType.FILE: 'file'>,
  #     path='/tmp/test-file',
  #     size=17,
  #     mode=420,
  #     permissions='-rw-r--r--',
  #     owner='user',
  #     group='user',
  #     modified_time=datetime.datetime(2026, 6, 3, 12, 14, 34, 745328),
  #     symlink_target=None
  # )

  sandbox.kill()
  ```
</CodeGroup>

***

## Getting metadata about a directory

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  // Create test directory
  const dirPathInSandbox = '/tmp/test-dir'
  await sandbox.files.makeDir(dirPathInSandbox)

  // Get directory metadata
  const info = await sandbox.files.getInfo(dirPathInSandbox)
  console.log(info)
  // {
  //   name: 'test-dir',
  //   type: 'dir',
  //   path: '/tmp/test-dir',
  //   size: 40,
  //   mode: 493,
  //   permissions: 'drwxr-xr-x',
  //   owner: 'user',
  //   group: 'user',
  //   modifiedTime: 2026-06-03T12:13:59.194Z,
  //   symlinkTarget: undefined
  // }

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  # Create test directory
  dir_path_in_sandbox = '/tmp/test-dir'
  sandbox.files.make_dir(dir_path_in_sandbox)

  # Get directory metadata
  info = sandbox.files.get_info(dir_path_in_sandbox)
  print(info)
  # EntryInfo(
  #     name='test-dir',
  #     type=<FileType.DIR: 'dir'>,
  #     path='/tmp/test-dir',
  #     size=40,
  #     mode=493,
  #     permissions='drwxr-xr-x',
  #     owner='user',
  #     group='user',
  #     modified_time=datetime.datetime(2026, 6, 3, 12, 16, 4, 858056),
  #     symlink_target=None
  # )

  sandbox.kill()
  ```
</CodeGroup>


# Read & Write files
Source: https://docs.novita.ai/guides/sandbox-filesystem-read-write



## Reading files

You can use the `files.read()` method to read files from the sandbox filesystem.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  // Create a file in the sandbox for testing
  const filePathInSandbox = '/tmp/test-file'
  await sandbox.files.write(filePathInSandbox, 'test-file-content')

  const fileContent = await sandbox.files.read(filePathInSandbox)
  console.log(fileContent)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  file_path_in_sandbox = '/tmp/test-file'
  sandbox.files.write(file_path_in_sandbox, 'test-file-content')

  file_content = sandbox.files.read(file_path_in_sandbox)
  print(file_content)

  sandbox.kill()
  ```
</CodeGroup>

## Writing single files

You can use the `files.write()` method to write single files to the sandbox filesystem.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  const filePathInSandbox = '/tmp/test-file'

  const result = await sandbox.files.write(filePathInSandbox, 'test-file-content')
  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  file_path_in_sandbox = '/tmp/test-file'
  result = sandbox.files.write(file_path_in_sandbox, 'test-file-content')
  print(result)

  sandbox.kill()
  ```
</CodeGroup>

## Writing multiple files

You can also use the `files.write()` method to write multiple files to the sandbox filesystem.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  const result = await sandbox.files.write([
    { path: '/tmp/test-file-1', data: 'file content 1' },
    { path: '/tmp/test-file-2', data: 'file content 2' },
  ])

  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  result = sandbox.files.write_files([
      {"path": "/tmp/test-file-1", "data": "file content 1"},
      {"path": "/tmp/test-file-2", "data": "file content 2"},
  ])
  print(result)

  sandbox.kill()
  ```
</CodeGroup>


# Upload
Source: https://docs.novita.ai/guides/sandbox-filesystem-upload



You can use the `files.write()` method to upload data to the sandbox.

Before running the example code in this document, please ensure you have properly configured environment variables. For details, please refer to Configure Environment Variables.

***

## Upload single file

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import fs from 'fs'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  // Read file from local filesystem
  const localFilePath = '../local-test-file'
  const content = fs.readFileSync(localFilePath, 'utf8')

  // Upload file to sandbox
  const filePathInSandbox = '/tmp/test-file'
  const result = await sandbox.files.write(filePathInSandbox, content)
  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  local_file_path = '../local-test-file'
  with open(local_file_path, 'rb') as file:
      file_path_in_sandbox = '/tmp/test-file'
      result = sandbox.files.write(file_path_in_sandbox, file)
      print(result)

  sandbox.kill()
  ```
</CodeGroup>

***

## Upload with pre-signed URL

Pre-signed upload URLs allow users to upload files securely from environments that do not hold Novita SDK credentials, such as web browsers. Create the sandbox with the `secure: true` option, then use the SDK on your trusted backend to generate the upload URL. Return it only to users authorized by your application, and set a short expiration time. The URL acts as a temporary bearer credential until it expires.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create({ secure: true })

  const filePathInSandbox = '/tmp/test-file'

  // Generate pre signature upload URL
  const publicUploadUrl = await sandbox.uploadUrl(filePathInSandbox, {
    useSignatureExpiration: 120, // optional, in seconds
  })

  // Simulate browser behavior
  const form = new FormData()
  form.append('file', new Blob(['file content']), 'test-file')

  const uploadRes = await fetch(publicUploadUrl, {
    method: 'POST',
    body: form,
  })
  if (!uploadRes.ok) {
    throw new Error(`Upload failed: ${uploadRes.status} ${await uploadRes.text()}`)
  }

  // SDK Read Back Verification
  const result = await sandbox.files.read(filePathInSandbox)
  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from pathlib import Path

  import requests
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create(secure=True)

  local_file_path = Path('../local-test-file')
  file_path_in_sandbox = '/tmp/test-file'

  # Generate pre signature upload URL
  signed_url = sandbox.upload_url(
      path=file_path_in_sandbox,
      use_signature_expiration=120,  # optional, in seconds
  )

  # Simulate browser behavior
  with open(local_file_path, 'rb') as f:
      response = requests.post(
          signed_url,
          files={'file': (local_file_path.name, f)},
          timeout=60,
      )
  response.raise_for_status()

  # SDK Read Back Verification
  result = sandbox.files.read(file_path_in_sandbox)
  print(result)

  sandbox.kill()
  ```
</CodeGroup>

***

## Upload directory / multiple files

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import fs from 'fs'
  import path from 'path'
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.codeInterpreter.create()

  const readDirectoryFiles = (directoryPath) => {
    const files = fs.readdirSync(directoryPath)

    const filesArray = files
      .filter((file) => {
        const fullPath = path.join(directoryPath, file)
        return fs.statSync(fullPath).isFile()
      })
      .map((file) => {
        const filePath = path.join(directoryPath, file)

        return {
          path: filePath,
          data: fs.readFileSync(filePath, 'utf8'),
        }
      })

    return filesArray
  }

  const localDirectoryPath = '../local-test-dir'
  const files = readDirectoryFiles(localDirectoryPath)
  console.log(files)

  files.forEach((file) => {
    file.path = file.path.replace('../local-test-dir', '/tmp')
  })

  const result = await sandbox.files.write(files)
  console.log(result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.code_interpreter.create()

  def read_directory_files(directory_path):
      files = []

      for filename in os.listdir(directory_path):
          file_path = os.path.join(directory_path, filename)

          if os.path.isfile(file_path):
              with open(file_path, 'rb') as file:
                  files.append({
                      'path': file_path,
                      'data': file.read(),
                  })
      return files

  local_directory_path = '../local-test-dir'
  files = read_directory_files(local_directory_path)
  print(files)

  for file in files:
      file['path'] = file['path'].replace('../local-test-dir', '/tmp')

  result = sandbox.files.write_files(files)
  print(result)

  sandbox.kill()
  ```
</CodeGroup>


# Watch Events
Source: https://docs.novita.ai/guides/sandbox-filesystem-watch



You can watch a directory in the sandbox for filesystem events (file created, written, removed, renamed, or permission changed). The JS/TS SDK and the Python async SDK use a **callback** model, while the Python sync SDK uses a **polling** model - both return a `WatchHandle` that you stop with `stop()`.

***

## Event object & event types

Each filesystem event is a `FilesystemEvent` with two fields:

| Field | Type | Description |
| - | - | - |
| `name` | string | Relative path to the filesystem object |
| `type` | FilesystemEventType | The kind of filesystem operation |

The `type` is one of the `FilesystemEventType` values:

| Constant | Value | Description |
| - | - | - |
| `FilesystemEventType.CHMOD` | `'chmod'` | Object permissions were changed |
| `FilesystemEventType.CREATE` | `'create'` | Object was created |
| `FilesystemEventType.REMOVE` | `'remove'` | Object was removed |
| `FilesystemEventType.RENAME` | `'rename'` | Object was renamed |
| `FilesystemEventType.WRITE` | `'write'` | Object was written to |

***

## Watch a directory (JavaScript / TypeScript)

`watchDir(path, onEvent, opts?)` starts watching and invokes `onEvent` for each event. It returns a `WatchHandle`; call `stop()` to stop watching.

| Parameter | Type | Description |
| - | - | - |
| `path` | string | Directory to watch |
| `onEvent` | (event: FilesystemEvent) => void | Called for each event |
| `opts.recursive` | boolean (default false) | Watch subdirectories too |
| `opts.timeoutMs` | number (default 60000) | Watch timeout in ms; `0` disables it |
| `opts.onExit` | (err?: Error) => void | Called when watching stops |
| `opts.user` | string | Run the operation as this user |

```js JavaScript & TypeScript icon="js" theme={"system"}
import { Novita } from 'novita-sandbox'

const novita = new Novita()
const sandbox = await novita.codeInterpreter.create()

const handle = await sandbox.files.watchDir(
  '/tmp',
  (event) => {
    console.log(`[${event.type}] ${event.name}`)
  },
  { recursive: true }
)

// ... trigger some file changes ...

await handle.stop()
await sandbox.kill()
```

***

## Watch a directory (Python)

The Python sync SDK uses a polling model: `watch_dir(path, recursive=False)` returns a `WatchHandle`, and you call `get_new_events()` to pull the events that occurred since the last call. Stop with `stop()`.

| Parameter | Type | Description |
| - | - | - |
| `path` | str | Directory to watch |
| `user` | str (optional) | Run the operation as this user |
| `request_timeout` | float (optional) | Request timeout in seconds |
| `recursive` | bool (default False) | Watch subdirectories too |

```python Python icon="python" theme={"system"}
import time
from novita_sandbox import Novita

novita = Novita()
sandbox = novita.code_interpreter.create()

watcher = sandbox.files.watch_dir('/tmp', recursive=True)

try:
    while True:
        events = watcher.get_new_events()
        for e in events:
            print(f'[{e.type.value}] {e.name}')
        time.sleep(1)
except KeyboardInterrupt:
    watcher.stop()
    sandbox.kill()
```

The Python **async** SDK uses a callback model instead: pass an `on_event` callback to `watch_dir`, which returns an `AsyncWatchHandle`. Stop with `await handle.stop()`.

```python Python icon="python" theme={"system"}
import asyncio
from novita_sandbox import AsyncCodeInterpreterSandbox

async def main():
    sandbox = await AsyncCodeInterpreterSandbox.create()

    def on_event(e):
        print(f'[{e.type.value}] {e.name}')

    handle = await sandbox.files.watch_dir('/tmp', on_event=on_event, recursive=True)

    # ... trigger some file changes ...

    await handle.stop()
    await sandbox.kill()

asyncio.run(main())
```

<Note>
  **Note:** Recursive watching (`recursive=True`) requires an up-to-date template. If the template is too old, the SDK raises a template error asking you to rebuild it with `novita template build`.
</Note>


# Git Integration
Source: https://docs.novita.ai/guides/sandbox-git-integration



Novita Sandbox exposes `sandbox.git` helpers for common repository workflows such as cloning, branching, committing, pulling, pushing, managing remotes, and configuring Git.

## Authentication and identity

### Inline credentials

For private HTTPS repositories, pass a username and password or token directly to operations such as `push`, `pull`, or `clone`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.git.push(repoPath, {
    username: process.env.GIT_USERNAME,
    password: process.env.GIT_TOKEN,
  })

  await sandbox.git.pull(repoPath, {
    username: process.env.GIT_USERNAME,
    password: process.env.GIT_TOKEN,
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita
  import os

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.git.push(
      repo_path,
      username=os.environ.get('GIT_USERNAME'),
      password=os.environ.get('GIT_TOKEN'),
  )

  sandbox.git.pull(
      repo_path,
      username=os.environ.get('GIT_USERNAME'),
      password=os.environ.get('GIT_TOKEN'),
  )
  ```
</CodeGroup>

### Authenticate once with the Git credential helper

You can use `dangerouslyAuthenticate()` in JavaScript or `dangerously_authenticate()` in Python to store credentials inside the sandbox credential helper.

<Note>
  Credentials are written to disk inside the sandbox and can be read by anything with sandbox access.
</Note>

Credentials may be stored for GitHub by default or for a custom HTTPS host.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.git.dangerouslyAuthenticate({
    username: process.env.GIT_USERNAME,
    password: process.env.GIT_TOKEN,
  })

  await sandbox.git.dangerouslyAuthenticate({
    username: process.env.GIT_USERNAME,
    password: process.env.GIT_TOKEN,
    host: 'git.example.com',
    protocol: 'https',
  })

  await sandbox.git.clone('https://git.example.com/org/repo.git', {
    path: '/home/user/repo',
  })

  await sandbox.git.push('/home/user/repo')
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita
  import os

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.git.dangerously_authenticate(
      username=os.environ.get('GIT_USERNAME'),
      password=os.environ.get('GIT_TOKEN'),
  )

  sandbox.git.dangerously_authenticate(
      username=os.environ.get('GIT_USERNAME'),
      password=os.environ.get('GIT_TOKEN'),
      host='git.example.com',
      protocol='https',
  )

  sandbox.git.clone(
      'https://git.example.com/org/repo.git',
      path='/home/user/repo',
  )

  sandbox.git.push('/home/user/repo')
  ```
</CodeGroup>

### Keep credentials in the remote URL

By default, credentials are removed from the remote URL after clone. To retain them in `.git/config`, set `dangerouslyStoreCredentials: true` (JS) or `dangerously_store_credentials=True` (Python).

<Note>
  Credentials kept in a remote URL remain in repo config and are readable by sandbox processes.
</Note>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.git.clone('https://git.example.com/org/repo.git', {
    path: '/home/user/repo',
    username: process.env.GIT_USERNAME,
    password: process.env.GIT_TOKEN,
  })

  await sandbox.git.clone('https://git.example.com/org/repo.git', {
    path: '/home/user/repo',
    username: process.env.GIT_USERNAME,
    password: process.env.GIT_TOKEN,
    dangerouslyStoreCredentials: true,
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita
  import os

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.git.clone(
      'https://git.example.com/org/repo.git',
      path='/home/user/repo',
      username=os.environ.get('GIT_USERNAME'),
      password=os.environ.get('GIT_TOKEN'),
  )

  sandbox.git.clone(
      'https://git.example.com/org/repo.git',
      path='/home/user/repo',
      username=os.environ.get('GIT_USERNAME'),
      password=os.environ.get('GIT_TOKEN'),
      dangerously_store_credentials=True,
  )
  ```
</CodeGroup>

## Configure commit identity

You can use `configureUser` (JS) or `configure_user` (Python) to set commit author details globally or locally for a repository.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.git.configureUser('Novita Bot', 'bot@example.com')

  await sandbox.git.configureUser('Novita Bot', 'bot@example.com', {
    scope: 'local',
    path: repoPath,
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.git.configure_user('Novita Bot', 'bot@example.com')

  sandbox.git.configure_user(
      'Novita Bot',
      'bot@example.com',
      scope='local',
      path=repo_path,
  )
  ```
</CodeGroup>

## Clone repositories

Supported clone options include destination path, branch selection, depth, username, password, and credential-storage behavior.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  await sandbox.git.clone(repoUrl, {
    path: repoPath,
  })

  await sandbox.git.clone(repoUrl, {
    path: repoPath,
    branch: 'main',
  })

  await sandbox.git.clone(repoUrl, {
    path: repoPath,
    depth: 1,
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  sandbox.git.clone(repo_url, path=repo_path)

  sandbox.git.clone(repo_url, path=repo_path, branch='main')

  sandbox.git.clone(repo_url, path=repo_path, depth=1)
  ```
</CodeGroup>

## Check repository status and branches

You can use `status()` to inspect current branch, ahead/behind counts, and file status.

You can use `branches()` to get the branch list and current branch.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  const status = await sandbox.git.status(repoPath)
  console.log(status.currentBranch)
  console.log(status.ahead)
  console.log(status.behind)
  console.log(status.fileStatus)

  const branches = await sandbox.git.branches(repoPath)
  console.log(branches.currentBranch)
  console.log(branches.branches)
  ```

  ```python Python icon="python" theme={"system"}
  status = sandbox.git.status(repo_path)
  print(status.current_branch)
  print(status.ahead)
  print(status.behind)
  print(status.file_status)

  branches = sandbox.git.branches(repo_path)
  print(branches.current_branch)
  print(branches.branches)
  ```
</CodeGroup>

## Create, switch, and delete branches

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.git.createBranch(repoPath, 'feature/new-docs')

  await sandbox.git.checkoutBranch(repoPath, 'main')

  await sandbox.git.deleteBranch(repoPath, 'feature/old-docs')

  await sandbox.git.deleteBranch(repoPath, 'feature/stale-docs', {
    force: true,
  })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.git.create_branch(repo_path, 'feature/new-docs')

  sandbox.git.checkout_branch(repo_path, 'main')

  sandbox.git.delete_branch(repo_path, 'feature/old-docs')

  sandbox.git.delete_branch(
      repo_path,
      'feature/stale-docs',
      force=True,
  )
  ```
</CodeGroup>

## Stage and commit changes

You can use `add` to stage all changes or selected files.

You can use `commit` to create commits. Options include custom author name, author email, and empty commits.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.git.add(repoPath)

  await sandbox.git.commit(repoPath, 'Initial commit')

  await sandbox.git.add(repoPath, {
    files: ['README.md', 'src/index.ts'],
  })

  await sandbox.git.commit(repoPath, 'Docs sync', {
    authorName: 'Novita Bot',
    authorEmail: 'bot@example.com',
    allowEmpty: true,
  })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.git.add(repo_path)

  sandbox.git.commit(repo_path, 'Initial commit')

  sandbox.git.add(
      repo_path,
      files=['README.md', 'src/index.ts'],
  )

  sandbox.git.commit(
      repo_path,
      'Docs sync',
      author_name='Novita Bot',
      author_email='bot@example.com',
      allow_empty=True,
  )
  ```
</CodeGroup>

## Pull and push

`push` and `pull` can use the configured upstream by default. You can also specify remote, branch, and upstream configuration.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.git.push(repoPath)

  await sandbox.git.pull(repoPath)

  await sandbox.git.push(repoPath, {
    remote: 'origin',
    branch: 'main',
    setUpstream: true,
  })

  await sandbox.git.pull(repoPath, {
    remote: 'origin',
    branch: 'main',
  })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.git.push(repo_path)

  sandbox.git.pull(repo_path)

  sandbox.git.push(
      repo_path,
      remote='origin',
      branch='main',
      set_upstream=True,
  )

  sandbox.git.pull(
      repo_path,
      remote='origin',
      branch='main',
  )
  ```
</CodeGroup>

## Manage remotes

You can use `remoteAdd` (JS) or `remote_add` (Python) to add remotes, optionally fetch after adding, or overwrite an existing remote.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.git.remoteAdd(repoPath, 'origin', repoUrl)

  await sandbox.git.remoteAdd(repoPath, 'origin', repoUrl, {
    fetch: true,
  })

  await sandbox.git.remoteAdd(repoPath, 'origin', repoUrl, {
    overwrite: true,
  })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.git.remote_add(repo_path, 'origin', repo_url)

  sandbox.git.remote_add(
      repo_path,
      'origin',
      repo_url,
      fetch=True,
  )

  sandbox.git.remote_add(
      repo_path,
      'origin',
      repo_url,
      overwrite=True,
  )
  ```
</CodeGroup>

## Git configuration

You can use `setConfig` / `set_config` and `getConfig` / `get_config` to manage Git settings globally or per repository.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.git.setConfig('pull.rebase', 'false')

  const value = await sandbox.git.getConfig('pull.rebase')

  await sandbox.git.setConfig('pull.rebase', 'false', {
    scope: 'local',
    path: repoPath,
  })

  const localValue = await sandbox.git.getConfig('pull.rebase', {
    scope: 'local',
    path: repoPath,
  })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.git.set_config('pull.rebase', 'false')

  value = sandbox.git.get_config('pull.rebase')

  sandbox.git.set_config(
      'pull.rebase',
      'false',
      scope='local',
      path=repo_path,
  )

  local_value = sandbox.git.get_config(
      'pull.rebase',
      scope='local',
      path=repo_path,
  )
  ```
</CodeGroup>


# PTY
Source: https://docs.novita.ai/guides/sandbox-interactive-terminal



A PTY (pseudo-terminal) is a virtual terminal interface that lets programs interact with a shell as if connected to a real terminal. Use the `sandbox.pty` module to create interactive terminal sessions that run commands, accept input, and can be resized while running — useful for REPLs and debuggers, interactive CLIs that prompt for input, and long-running processes you need to monitor.

<Note>
  **Note:** A PTY is identified by its process ID (`pid`), returned from `create()` on the handle. Pass that `pid` to send input, resize, connect, and kill. Output is delivered to a callback as raw bytes.
</Note>

| Operation | JavaScript / TypeScript | Python |
| - | - | - |
| Create | `sandbox.pty.create(opts)` | `sandbox.pty.create(size, ...)` |
| Connect | `sandbox.pty.connect(pid, opts?)` | `sandbox.pty.connect(pid, ...)` |
| Send input | `sandbox.pty.sendInput(pid, data)` | `sandbox.pty.send_stdin(pid, data)` |
| Resize | `sandbox.pty.resize(pid, size)` | `sandbox.pty.resize(pid, size)` |
| Kill | `sandbox.pty.kill(pid)` | `sandbox.pty.kill(pid)` |

***

## Create a PTY

Create a PTY and receive its output through a callback. It returns a handle whose `pid` identifies the PTY.

| Option | Type | Description |
| - | - | - |
| `cols` / `rows` | number | Terminal dimensions (Python: `PtySize(cols=…, rows=…)`) |
| `onData` (JS) | (data: Uint8Array) => void | Callback for PTY output. Python delivers output via `wait(on_pty=...)` |
| `user` | string (optional) | User to run the PTY as |
| `cwd` | string (optional) | Working directory |
| `envs` | object / dict (optional) | Environment variables (`TERM`, `LANG`, `LC_ALL` are defaulted) |
| `timeoutMs` / `timeout` | number (default 60000 ms / 60 s) | PTY timeout; JS uses ms, Python uses seconds |

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const pty = await sandbox.pty.create({
    cols: 120,
    rows: 30,
    cwd: '/home/user',
    envs: { TERM: 'xterm-256color' },
    onData: (data) => {
      process.stdout.write(new TextDecoder().decode(data))
    },
  })

  console.log('PTY pid:', pty.pid)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita
  from novita_sandbox.core.sandbox.commands.command_handle import PtySize

  novita = Novita()
  sandbox = novita.sandbox.create()

  def on_pty(data: bytes):
      print(data.decode('utf-8', errors='replace'), end='')

  # PtySize takes keyword args cols / rows
  pty = sandbox.pty.create(PtySize(cols=120, rows=30), cwd='/home/user', envs={'TERM': 'xterm-256color'})
  print('PTY pid:', pty.pid)
  ```
</CodeGroup>

***

## Send input

Send input to a running PTY by its `pid`. The data is raw bytes.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  const enc = new TextEncoder()
  await sandbox.pty.sendInput(pty.pid, enc.encode('echo hello\n'))
  await sandbox.pty.sendInput(pty.pid, enc.encode('exit\n'))
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.pty.send_stdin(pty.pid, b'echo hello\n')
  sandbox.pty.send_stdin(pty.pid, b'exit\n')
  ```
</CodeGroup>

***

## Resize a PTY

Call `resize` when the terminal window changes size.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.pty.resize(pty.pid, { cols: 150, rows: 40 })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.pty.resize(pty.pid, PtySize(cols=150, rows=40))
  ```
</CodeGroup>

***

## Connect to a running PTY

Connect to a PTY that is already running by its `pid`. You can obtain running processes with `sandbox.commands.list()`. The returned handle receives further output through the callback.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  const handle = await sandbox.pty.connect(pty.pid, {
    onData: (data) => process.stdout.write(new TextDecoder().decode(data)),
  })
  ```

  ```python Python icon="python" theme={"system"}
  handle = sandbox.pty.connect(pty.pid)
  ```
</CodeGroup>

***

## Kill a PTY

`kill` terminates the PTY with `SIGKILL`. It returns `true` if the PTY was killed, or `false` if no PTY with that `pid` was found.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  const killed = await sandbox.pty.kill(pty.pid)
  console.log('killed:', killed)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  killed = sandbox.pty.kill(pty.pid)
  print('killed:', killed)

  sandbox.kill()
  ```
</CodeGroup>

***

## Interactive example

Create a PTY, send an interactive command that prompts for input, then wait for it to finish and read the exit code. In Python, `wait(on_pty=...)` streams output to the callback and returns the result.

```python Python icon="python" theme={"system"}
from novita_sandbox import Novita
from novita_sandbox.core.sandbox.commands.command_handle import PtySize

novita = Novita()
sandbox = novita.sandbox.create()

output = []

def on_pty(data: bytes):
    output.append(data.decode('utf-8', errors='replace'))

terminal = sandbox.pty.create(PtySize(cols=80, rows=24), envs={'ABC': '123'}, cwd='/')

# Send a command and exit
sandbox.pty.send_stdin(terminal.pid, b'echo $ABC\nexit\n')

# Stream output and wait for completion
result = terminal.wait(on_pty=on_pty)
print('exit code:', result.exit_code)
print(''.join(output))

sandbox.kill()
```


# Legacy Auto Pause and Resume
Source: https://docs.novita.ai/guides/sandbox-legacy-auto-persistence



<Warning>
  This legacy page is kept for users who still use the 1.x or beta SDK line. New SDK 2.x integrations should follow the current Sandbox documentation.
</Warning>

<SandboxBetaVersionWarning />

When the sandbox instance is not currently needed but you need to be able to resume it at any time later, you can refer to the [documentation](/guides/sandbox-legacy-persistence) to manually pause and resume sandbox instances.

The **"Auto Pause and Resume"** feature allows your sandbox instance to automatically pause after [timeout](/guides/sandbox-overview) and resume when needed. When you attempt to perform operations (such as executing commands, running code, or file operations) on a paused sandbox, it will automatically resume without manual intervention.

## Enable Auto Pause

You can enable the auto pause feature by setting the `auto_pause` parameter to `true` when creating a sandbox instance. The sandbox instance will automatically pause after timeout and can be resumed later.

<Warning>
  Note: The resumed sandbox instance will still have the auto pause feature enabled and will automatically pause again after timeout.
</Warning>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter';

  const sandbox = await Sandbox.create(
      {
          timeoutMs: 5_000,
          autoPause: true
      },
  );
  console.log('Sandbox created', sandbox.sandboxId)

  console.log('Waiting for 10 seconds...')
  await new Promise(resolve => setTimeout(resolve, 10000));

  const resumedSandbox = await Sandbox.connect(sandbox.sandboxId)
  console.log('Sandbox resumed', resumedSandbox.sandboxId)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.core import Sandbox
  import time

  sandbox = Sandbox.create(
      timeout=5,
      auto_pause=True,
  )
  print('Sandbox created', sandbox.sandbox_id)

  print('Waiting for 10 seconds...')
  time.sleep(10)

  resumed_sandbox = Sandbox.connect(sandbox.sandbox_id)
  print('Sandbox resumed', resumed_sandbox.sandbox_id)

  sandbox.kill()
  ```
</CodeGroup>

## Enable Auto Resume

You can enable the auto resume feature by setting the `auto_resume` key in `metadata` to `true` when creating a sandbox instance. Once enabled, when you attempt to operate on a paused sandbox (such as executing commands, running code, file operations, etc.), the sandbox will automatically resume.

<Warning>
  Note: The default timeout for the resumed sandbox is 5 minutes. You can update the sandbox timeout using the `setTimeout` or `set_timeout` method.
</Warning>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter';

  const sandbox = await Sandbox.create(
      {
          metadata:{"auto_resume": "true"}
      }
  );
  console.log('Sandbox created', sandbox.sandboxId)

  await sandbox.betaPause()
  console.log('Sandbox paused', sandbox.sandboxId)

  const result = await sandbox.commands.run('ls -al')
  console.log('Command result', result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.core import Sandbox

  sandbox = Sandbox.create(
      metadata={
          "auto_resume": "true",
      }
  )
  print('Sandbox created', sandbox.sandbox_id)

  sandbox.beta_pause()
  print('Sandbox paused', sandbox.sandbox_id)

  result = sandbox.commands.run('ls -al')
  print('Command result', result)

  sandbox.kill()
  ```
</CodeGroup>


# Legacy Sandbox Clone
Source: https://docs.novita.ai/guides/sandbox-legacy-clone



<Warning>
  This legacy page is kept for users who still use the 1.x or beta SDK line. New SDK 2.x integrations should follow the current Sandbox documentation.
</Warning>

At the current stage, AI Agents face a fundamental limitation when executing complex tasks: the "n of 1" problem — AI and developers are confined to a single working environment for serial reasoning. This pattern leads to several key issues:

1. **Experiment Conflicts and Environment Contamination**. When an AI Agent tries multiple solutions, experimental code changes may interfere with the developer's main workflow or contaminate the current runtime environment. Once an experiment goes wrong, recovery often requires a rollback, and valuable exploration paths cannot be preserved.

2. **Inability to Explore Multiple Solutions in Parallel**. Constrained to a single environment, the AI Agent can only test different approaches sequentially. This serial mode is not only inefficient, but more importantly, it limits the breadth of AI exploration — unable to simultaneously validate multiple parallel hypotheses or implementation approaches.

3. **Limited Scalability of Computing Power**. When facing tasks that require "Wide-Research" (e.g., comparing 100 solutions simultaneously, batch-generating multiple implementation versions), the single-environment architecture fundamentally restricts the ability to parallelize task processing.

The **"Sandbox Clone"** feature enables the transition from "Deep-Research" to "Wide-Research":

1. **Multi-Timeline Exploration Architecture**: Like a decision tree, the AI Agent can start from the same baseline state and create multiple independent sandbox copies, each exploring a different solution path without interfering with each other.
2. **True Parallel Computing Capability**: By splitting large tasks into batch subtasks, the AI Agent can scale its computing power by tens or even hundreds of times, simultaneously processing dozens or hundreds of exploration branches.
3. **Zero-Risk Experimentation Environment**: Cloned sandboxes are completely isolated, allowing the AI to freely experiment and test various possibilities without affecting the original environment or the developer's main workflow.
4. **Efficient Resource Utilization**: Although multiple sandbox instances may be launched simultaneously, by dynamically managing and promptly terminating branches (sandbox instances) that are no longer valuable, overall computing resource consumption can be kept within a reasonable range.

This capability enables AI Agents to break through current performance bottlenecks, transitioning from providing theoretical suggestions to delivering reliable solutions that have been verified in parallel and actually tested, truly achieving the ability to autonomously explore, iterate, and solve complex problems.

<SandboxBetaVersionWarning />

## Terminology

* **Origin Sandbox**: The original sandbox instance being cloned.
* **New Sandbox**: The new sandbox instance created through the clone operation.

## Feature Overview

The sandbox clone feature currently supports the following two scenarios:

* Clone a sandbox in Running state
* Clone a sandbox in Paused state

### Cloning a Running Sandbox

**During the clone process:**

* The origin sandbox will be briefly suspended during cloning;
* The sandbox instance is unavailable during suspension;
* The suspension duration is close to the time required for a single pause operation.

**After cloning is complete:**

**Origin Sandbox:**

* Status is restored to running;
* The existing pause record will be refreshed to a new pause record, based on the current sandbox state;
* A new snapshot template record is generated (to delete this snapshot template, you need to first terminate both the origin sandbox and the cloned sandboxes);

**New Sandbox:**

* Status is running;
* Ready to use immediately.

### Cloning a Paused Sandbox

**During the clone process:**

When the origin sandbox is in a paused state:

* The clone process will not trigger the origin sandbox to start;
* The origin sandbox remains in paused state.

**After cloning is complete:**

**Origin Sandbox:**

* The existing pause record is not cleared;
* A new snapshot template record is generated (to delete this snapshot template, you need to first terminate both the origin sandbox and the cloned sandboxes);

**New Sandbox:**

* Status is running;
* Ready to use immediately.

### New Sandbox Attribute Inheritance Rules

| Attribute | Inherited |
| - | - |
| auto resume | Yes |
| auto pause | No |

## Parameter Description

* `count`: The number of sandbox instances to clone. The minimum value is 1, and the maximum must not exceed the platform's concurrent running sandbox instance limit (see: [Sandbox Quota Limit](/guides/sandbox-quota-limit));
* `strict`: Whether to strictly clone according to the number specified in the `count` parameter, default is false.
  * `true`: If the number of successfully cloned instances is less than `count`, a clone failure is returned; successfully created sandboxes will be automatically released.
  * `false`: Returns the actual number of successfully cloned sandbox instances.
* `timeout`(`timeoutMs`): The timeout for cloning sandbox instances.
  * If not specified:
    * When the origin sandbox is in running state, it inherits its timeout configuration;
    * When the origin sandbox is in paused state, the default value of 5 minutes is used.

## Return Value

After a successful clone operation, an object is returned containing the following properties:

| Property | Description |
| - | - |
| `sandboxes` | List of successfully cloned sandbox instances |
| `count` | Number of successfully cloned sandboxes |
| `snapshot_template_id` | Snapshot template ID generated during the clone process |

## Code Examples

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox.core import Sandbox

  # Create a sandbox
  sandbox = Sandbox.create(template="base")
  # clone
  clones = Sandbox.clone(sandbox.sandbox_id, 2)

  print("snapshot:", clones.snapshot_template_id)
  for sbx in clones:
      print("clone sandbox:", sbx.sandbox_id)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from '@novita-sandbox/core'

  // Create a sandbox
  const sandbox = await Sandbox.create('base')

  // clone
  const sbxClones = await Sandbox.clone(sandbox.sandboxId, { count: 2 })

  console.log("snapshot:", sbxClones.snapshotTemplateId)
  for (const idx in sbxClones.sandboxes) {
      console.log("clone sandboxID[" + idx + "]:", sbxClones.sandboxes[idx].sandboxId)
  }
  ```
</CodeGroup>

Additionally, you can also use the Novita Sandbox CLI to clone a specified sandbox instance:

```bash Bash icon="terminal" theme={"system"}
# novita sandbox clone [sandboxID] -c [count] -s [strict] -t [timeout]
novita sandbox clone 0r0efkbfwzfp9p7qpc1c -c 2
```


# Legacy Sandbox Snapshot Template
Source: https://docs.novita.ai/guides/sandbox-legacy-commit



<Warning>
  This legacy page is kept for users who still use the 1.x or beta SDK line. New SDK 2.x integrations should follow the current Sandbox documentation.
</Warning>

<SandboxBetaVersionWarning />

## Terminology

* **Commit**: A commit operation saves the current state of a sandbox as a template, resulting in a new template of type snapshot template.
* **Origin Sandbox**: The sandbox instance being committed.
* **Snapshot Template**: The template generated after a commit operation.

## Feature Description

### Committing a Running Sandbox

**During the commit process:**

* The origin sandbox will be briefly suspended during committing;
* The sandbox instance is unavailable during suspension;
* The suspension duration is close to the time required for a single pause operation.

**After committing is complete:**

**Origin Sandbox:**

* The existing pause record will be refreshed to a new pause record, based on the current sandbox state;
* The origin sandbox continues to run;
* A new template record is generated.

### Committing a Paused Sandbox

**During the commit process:**

* The commit process will not trigger the origin sandbox to start;
* The origin sandbox remains in paused state.

**After committing is complete:**

**Origin Sandbox:**

* The existing pause record is not cleared;
* A new template record is generated.

## Parameter Description

| Parameter | Type | Required | Description |
| - | - | - | - |
| `sandbox_id` | `string` | Yes | The ID of the sandbox instance to commit |
| `alias` | `string` | No | Set an alias for the generated template |

## Return Value

After a successful commit operation, a `Template` object is returned.

## Code Examples

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox.core import Sandbox

  # Commit an existing sandbox by ID to create a template snapshot
  # sandbox_id: str - Required. The ID of the sandbox to commit
  # alias: Optional[str] - Optional alias name for the created template
  # Note: Default timeout is 10 minutes for commit operation
  template = Sandbox.commit(
      sandbox_id="existing-sandbox-id",
      alias="my-template-alias"
  )
  print(f"Template ID: {template.template_id}")
  print(f"Build ID: {template.build_id}")
  ```

  ```js TypeScript icon="js" theme={"system"}
  import { Sandbox } from '@novita-sandbox/core'

  // Commit an existing sandbox by ID to create a template snapshot
  // sandboxId: string - Required. The ID of the sandbox to commit
  // opts?: { alias?: string } - Optional commit options
  const template = await Sandbox.commit('existing-sandbox-id', {
    alias: 'my-template-alias'
  })
  console.log(`Template ID: ${template.templateId}`)
  console.log(`Build ID: ${template.buildId}`)
  ```
</CodeGroup>

Additionally, you can also use the Novita Sandbox CLI to commit a specified sandbox instance:

```bash Bash icon="terminal" theme={"system"}
# Basic commit - creates a template snapshot from a sandbox
novita sandbox commit <sandboxID>

# Commit with alias - assign a friendly name to the template
novita sandbox commit <sandboxID> --alias my-template-alias

```


# Legacy SDKs & CLI
Source: https://docs.novita.ai/guides/sandbox-legacy-e2b-compatible



<Warning>
  This legacy page is kept for users who still use the 1.x or beta SDK line. New SDK 2.x integrations should follow the current Sandbox documentation.
</Warning>

This page lists the legacy Novita Agent Sandbox SDK and CLI versions for users who still need the 1.x stable or 1.x beta SDK line.

## Legacy Novita SDK Versions

If you need to stay on the legacy 1.x SDK line, pin one of the following versions explicitly:

| Package | Last 1.x stable version | Last 1.x beta version |
| - | - | - |
| Python SDK | `novita-sandbox==1.0.5` | `novita-sandbox==1.2.5b2` |
| JavaScript/TypeScript SDK | `novita-sandbox@1.0.2` | `novita-sandbox@1.2.3b1` |
| CLI | `novita-sandbox-cli@1.0.4` | `novita-sandbox-cli@1.2.3b1` |

## Installation

### Install Legacy Stable SDK

<CodeGroup>
  ```bash JavaScript & TypeScript icon="terminal" theme={"system"}
  npm i novita-sandbox@1.0.2
  ```

  ```bash Python icon="terminal" theme={"system"}
  pip install novita-sandbox==1.0.5
  ```
</CodeGroup>

### Install Legacy Stable CLI

<CodeGroup>
  ```bash Bash icon="terminal" theme={"system"}
  npm i -g novita-sandbox-cli@1.0.4
  ```
</CodeGroup>

### Install Legacy Beta SDK

<CodeGroup>
  ```bash JavaScript & TypeScript icon="terminal" theme={"system"}
  npm i novita-sandbox@1.2.3b1
  ```

  ```bash Python icon="terminal" theme={"system"}
  pip install novita-sandbox==1.2.5b2
  ```
</CodeGroup>

### Install Legacy Beta CLI

<CodeGroup>
  ```bash Bash icon="terminal" theme={"system"}
  npm i -g novita-sandbox-cli@1.2.3b1
  ```
</CodeGroup>

## Setup Configuration

Legacy SDK versions use `sandbox.novita.ai` by default. You only need to configure your Novita API key.

```bash Bash icon="terminal" theme={"system"}
export NOVITA_API_KEY="<Your Novita AI API Key>"
```

## Examples

Below is an example showing how to create a sandbox through the SDK and run specified commands.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sbx = await Sandbox.create()
  const execution = await sbx.commands.run('ls -l')
  console.log(execution)

  await sbx.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox

  sbx = Sandbox.create()
  execution = sbx.commands.run('ls -l')
  print(execution)

  sbx.kill()
  ```
</CodeGroup>

Below is an example showing how to use the Novita Sandbox CLI.

<CodeGroup>
  ```bash Bash icon="terminal" theme={"system"}
  # Authentication in CLI
  novita auth login

  # Start sandbox and connect to terminal
  novita sandbox spawn <template-id>

  # List sandboxes
  novita sandbox list

  # Shutdown running sandboxes
  novita sandbox kill <sandbox-id>
  ```
</CodeGroup>


# Legacy Idle Timeout
Source: https://docs.novita.ai/guides/sandbox-legacy-idle-timeout



<Warning>
  This legacy page is kept for users who still use the 1.x or beta SDK line. New SDK 2.x integrations should follow the current Sandbox documentation.
</Warning>

<SandboxBetaVersionWarning />

We can set a timeout for each sandbox (refer to [Lifecycle Management](/guides/sandbox-overview)). When the sandbox running time reaches the timeout, the sandbox will be automatically killed. However, in some scenarios, we cannot determine the expected running time of the sandbox, but we want the sandbox to be automatically killed when not in use to save costs. In this case, you can use the **"Idle Timeout"** feature.

When creating a sandbox, we can set the `idle_timeout` parameter in metadata (unit: seconds, minimum is 60) to enable the **"Idle Timeout"** feature. Once enabled, when the system detects that the sandbox has no operations (executing commands, running code, file operations, etc.) within the specified time range, the system will kill the sandbox instance. Otherwise, the sandbox will continue to run until it reaches the maximum time limit for sandbox operation (currently default is 3600 seconds).

Please refer to the following example:

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter';

  const sandbox = await Sandbox.create(
      {
          metadata: { "idle_timeout": "60" }
      }
  );
  console.log('Sandbox created', sandbox.sandboxId)

  const result = await sandbox.commands.run('ls -al')
  console.log('Command result', result)

  await new Promise(resolve => setTimeout(resolve, 90000));

  const isRunning = await sandbox.isRunning()
  console.log('Sandbox is running', isRunning)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.core import Sandbox
  import time

  sandbox = Sandbox.create(
      metadata={"idle_timeout": "60"}  # Minimum time is 60s, you can customize it
  )
  print('Sandbox created', sandbox.sandbox_id)

  result = sandbox.commands.run('ls -al')
  print('Command result', result)

  print('Waiting for 90 seconds...')
  time.sleep(90)

  is_running = sandbox.is_running()
  print('Sandbox is running', is_running)

  sandbox.kill()
  ```
</CodeGroup>


# Legacy SDK
Source: https://docs.novita.ai/guides/sandbox-legacy-overview



These pages are kept for users who still use the 1.x stable SDK or 1.x beta SDK line.

If you are starting a new integration or upgrading to SDK 2.x, follow the current Agent Sandbox documentation instead.

## Legacy Pages

* [SDKs & CLI](/guides/sandbox-legacy-e2b-compatible)
* [Sandbox Persistence](/guides/sandbox-legacy-persistence)
* [Auto Pause and Resume](/guides/sandbox-legacy-auto-persistence)
* [Sandbox Clone](/guides/sandbox-legacy-clone)
* [Sandbox Snapshot Template](/guides/sandbox-legacy-commit)
* [Idle Timeout](/guides/sandbox-legacy-idle-timeout)


# Legacy Sandbox Persistence
Source: https://docs.novita.ai/guides/sandbox-legacy-persistence



<Warning>
  This legacy page is kept for users who still use the 1.x or beta SDK line. New SDK 2.x integrations should follow the current Sandbox documentation.
</Warning>

The sandbox persistence allows you to pause your sandbox and resume it later from the same state it was in when you paused it.

This includes not only state of the sandbox's filesystem but also the sandbox's memory. This means all running processes, loaded variables, data, etc.

<SandboxConfigHint />

<Warning>
  Please note:

  * It takes about 4 seconds per 1 GB RAM to pause the sandbox.
  * It takes about 1 second to resume the sandbox.
  * The data of a paused sandbox is permanently retained until you explicitly call the `kill` method.
</Warning>

## Pausing sandbox

When you pause a sandbox, both the sandbox's <Link href="/guides/sandbox-filesystem">filesystem</Link> and memory state will be saved. This includes all the files in the sandbox's filesystem and all the running processes, loaded variables, data, etc.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()
  console.log('Sandbox created', sandbox.sandboxId)

  // Pause the sandbox.
  // You can save the sandbox ID in your database
  // to resume the sandbox later
  const result = await sandbox.betaPause()
  console.log('Sandbox paused', sandbox.sandboxId, result)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox

  sandbox = Sandbox.create()
  print('Sandbox created', sandbox.sandbox_id)

  # You can save the sandbox ID in your database
  # to resume the sandbox later
  sandbox.beta_pause()
  print('Sandbox paused', sandbox.sandbox_id)

  sandbox.kill()
  ```
</CodeGroup>

<Warning>
  **Note: Brief transition period after pausing**

  Pausing is an asynchronous operation. After calling `beta_pause()`, the sandbox pauses in the background.

  During this transition period, before the pause completes:

  * **Read-only operations** (e.g., `get_info`, `list`): return normally; status is shown as `paused`
  * **Execution operations** (e.g., `commands.run`, accessing sandbox public URLs): will return errors — this is expected behavior. Wait for pausing to complete before retrying (time varies with memory size, see note above)
  * **Resume operation** (`connect`): automatically waits for the pause to complete before resuming the sandbox — no manual polling or retry needed
</Warning>

## Resuming sandbox

When you resume a sandbox, it will be in the same state it was in when you paused it.
This means that all the files in the sandbox's filesystem will be restored and all the running processes, loaded variables, data, etc. will be restored.

<Tip>
  If you call `connect()` immediately after calling `beta_pause()`, `connect()` automatically waits for the pause to complete before resuming the sandbox. No manual polling is required — the entire process is transparent to the caller.
</Tip>

<Warning>
  If you try to resume a sandbox that has been destroyed or does not exist:

  * JavaScript SDK will throw `NotFoundError`
  * Python SDK will throw `NotFoundException`
</Warning>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()
  console.log('Sandbox created', sandbox.sandboxId)

  // Pause the sandbox.
  // You can save the sandbox ID in your database
  // to resume the sandbox later
  const result = await sandbox.betaPause()
  console.log('Sandbox paused', sandbox.sandboxId, result)

  // Resume the sandbox.
  // Even if called before pausing completes, connect() automatically waits for the pause to finish.
  const resumedSandbox = await sandbox.connect()
  console.log('Sandbox resumed', resumedSandbox.sandboxId)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox

  sandbox = Sandbox.create()
  print('Sandbox created', sandbox.sandbox_id)

  # Pause the sandbox.
  # You can save the sandbox ID in your database
  # to resume the sandbox later
  sandbox.beta_pause()
  print('Sandbox paused', sandbox.sandbox_id)

  # Resume the sandbox.
  # Even if called before pausing completes, connect() automatically waits for the pause to finish.
  connectedSandbox = sandbox.connect()
  print('Sandbox resumed', connectedSandbox.sandbox_id)

  sandbox.kill()
  ```
</CodeGroup>

## Listing paused sandboxes

You can list all paused sandboxes by calling the `Sandbox.list` method and supplying the `state` query parameter.
More information about using the method can be found in [List Sandboxes](/guides/sandbox-list).

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox, SandboxInfo } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  // List all paused sandboxes.
  const paginator = Sandbox.list({ query: { state: ['paused'] } })

  // Get all paused sandboxes.
  const sandboxes: SandboxInfo[] = []
  while (paginator.hasNext) {
    const items = await paginator.nextItems()
    sandboxes.push(...items)
  }

  console.log('all paused sandboxes', sandboxes)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox, SandboxQuery, SandboxState, SandboxInfo

  sandbox = Sandbox.create()

  sandbox.beta_pause()
  print('Sandbox paused', sandbox.sandbox_id)

  # List all paused sandboxes.
  paginator = Sandbox.list(query=SandboxQuery(state=[SandboxState.PAUSED]))

  # Get all paused sandboxes.
  sandboxes: list[SandboxInfo] = []
  while paginator.has_next:
    items = paginator.next_items()
    sandboxes.extend(items)

  print('all paused sandboxes', sandboxes)

  sandbox.kill()
  ```
</CodeGroup>

## Removing paused sandboxes

You can remove paused sandboxes by calling the `kill` method on the sandbox instance.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()
  console.log('Sandbox created', sandbox.sandboxId)

  // Pause the sandbox.
  await sandbox.betaPause()

  // Kill the paused sandbox.
  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox

  sandbox = Sandbox.create()

  # Pause the sandbox.
  sandbox.beta_pause()

  # Kill the paused sandbox.
  sandbox.kill()
  ```
</CodeGroup>

## Sandbox's timeout

When you resume a sandbox, the sandbox's timeout is reset to the default timeout of a sandbox - 5 minutes.

You can pass a custom timeout to the `Sandbox.connect()` method like this:

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const connectedSandbox = await Sandbox.connect(sandbox.sandboxId, { timeoutMs: 60 * 1000 })
  console.log('Sandbox connected', connectedSandbox.sandboxId)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox

  sandbox = Sandbox.create()

  connectedSandbox = Sandbox.connect(sandbox.sandbox_id, timeout=60) # 60 seconds
  print('Sandbox connected', connectedSandbox.sandbox_id)

  sandbox.kill()
  ```
</CodeGroup>

## Network

If you have a service (for example a server) running inside your sandbox and you pause the sandbox, the service won't be accessible from the outside and all the clients will be disconnected.
If you resume the sandbox, the service will be accessible again but you need to connect clients again.


# Log Streaming
Source: https://docs.novita.ai/guides/sandbox-log-streaming



When you run a command in a sandbox, its `stdout` and `stderr` output can be consumed in two ways: streamed in real time through callbacks, or retrieved in full after the command finishes. Real-time streaming suits long-running or background commands you want to monitor as they run; retrieving the full output suits commands with a predictable, short duration.

## Stream logs with callbacks

Pass `onStdout` / `onStderr` (JavaScript) or `on_stdout` / `on_stderr` (Python) to `commands.run(...)`. Each callback receives output as soon as it is produced, with stdout and stderr delivered separately.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const result = await sandbox.commands.run(
    'for i in 1 2 3; do echo line $i; sleep 1; done',
    {
      onStdout: (data) => process.stdout.write(`OUT: ${data}`),
      onStderr: (data) => process.stderr.write(`ERR: ${data}`),
    }
  )

  console.log('exit code:', result.exitCode)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  result = sandbox.commands.run(
      'for i in 1 2 3; do echo line $i; sleep 1; done',
      on_stdout=lambda data: print('OUT:', data, end=''),
      on_stderr=lambda data: print('ERR:', data, end=''),
  )
  print('exit code:', result.exit_code)

  sandbox.kill()
  ```
</CodeGroup>

<Note>
  Keep callbacks lightweight. Avoid long blocking work inside a callback — it holds up event processing and can cause the underlying connection to disconnect. Buffer or hand off to another task/thread if you need to do heavy processing.
</Note>

## Stream a background command

To keep your program running while a command streams, start it with `background: true` (JS) or `background=True` (Python). This returns a `CommandHandle` instead of waiting for the result. You can attach the same `onStdout` / `onStderr` callbacks, continue with other work, then call `wait()` to block until the command finishes and get its `CommandResult`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  // Returns a CommandHandle immediately (does not wait)
  const handle = await sandbox.commands.run(
    'for i in $(seq 1 5); do echo tick $i; sleep 1; done',
    {
      background: true,
      onStdout: (data) => process.stdout.write(data),
      onStderr: (data) => process.stderr.write(data),
    }
  )

  console.log('running as pid:', handle.pid)

  // ... do other work while logs stream in the background ...

  // Wait for completion and get the final result
  const result = await handle.wait()
  console.log('exit code:', result.exitCode)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  # Returns a CommandHandle immediately (does not wait)
  handle = sandbox.commands.run(
      'for i in $(seq 1 5); do echo tick $i; sleep 1; done',
      background=True,
  )

  print('running as pid:', handle.pid)

  # ... do other work while the command runs ...

  # Stream logs and wait for completion; callbacks fire as output arrives
  result = handle.wait(
      on_stdout=lambda data: print(data, end=''),
      on_stderr=lambda data: print(data, end=''),
  )
  print('exit code:', result.exit_code)

  sandbox.kill()
  ```
</CodeGroup>

## Retrieve all logs after completion

If you do not need real-time output, run the command in the foreground and read the full `stdout` and `stderr` from the returned `CommandResult`. This is the simplest approach for commands with a predictable, short duration.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create()

  const result = await sandbox.commands.run('echo hello && echo oops 1>&2')

  console.log('stdout:', result.stdout)
  console.log('stderr:', result.stderr)
  console.log('exit code:', result.exitCode)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create()

  result = sandbox.commands.run('echo hello && echo oops 1>&2')

  print('stdout:', result.stdout)
  print('stderr:', result.stderr)
  print('exit code:', result.exit_code)

  sandbox.kill()
  ```
</CodeGroup>

For a command started in the background, the accumulated output is also available on the handle after it completes.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  const handle = await sandbox.commands.run('echo done', { background: true })

  const result = await handle.wait()

  // Full output collected during execution
  console.log(result.stdout)
  console.log(result.stderr)
  ```

  ```python Python icon="python" theme={"system"}
  handle = sandbox.commands.run('echo done', background=True)

  result = handle.wait()

  # Full output collected during execution
  print(result.stdout)
  print(result.stderr)
  ```
</CodeGroup>

## CommandResult fields

| Field (JS / Python) | Description |
| - | - |
| `stdout` | Full standard output collected from the command. |
| `stderr` | Full standard error collected from the command. |
| `exitCode` / `exit_code` | Command exit code. `0` indicates success. |
| `error` | Error message when the command failed; otherwise empty. |

<Note>
  A command that exits with a non-zero exit code raises `CommandExitError` (JS) / `CommandExitException` (Python). The exception carries the same `stdout`, `stderr`, `exitCode`, and `error` fields, so you can still inspect the output on failure.
</Note>


# Secret Overview
Source: https://docs.novita.ai/guides/sandbox-secret



Secrets are a secure way to store and use sensitive values — such as API keys, tokens, and passwords — inside a Novita Sandbox. They let code running in a sandbox authenticate to external services without hardcoding credentials in source code, snapshots, or sandbox environment variables.

<Note>
  **Note:** Secret is only supported in [v2 regions](/guides/sandbox-overview#regions).
</Note>

## What is a Secret

A Secret is an encrypted credential managed within the scope of the current team. Novita uses the credential on eligible outbound HTTPS requests from a sandbox, but it never injects the real value into the sandbox itself.

Instead, the sandbox environment variable holds an opaque **placeholder** rather than the actual API key. The outbound proxy substitutes the placeholder with the real value only when the target host matches the Secret's allow list.

This lets a sandbox reach credentials such as an LLM API key or a database password, while keeping the real value out of the sandbox's environment variables, filesystem, and process arguments. Code running in the sandbox can use a Secret to access approved hosts, but it cannot read the real value through the Secret environment variable.

<Note>
  The real Secret value is never delivered into the sandbox. What the sandbox holds is an opaque placeholder; the substitution to the real value happens at the proxy layer, outside the sandbox.
</Note>

## How it works

In the normal Secret injection flow, the sandbox environment variable holds a placeholder, not the Secret's real value. Novita uses an external proxy to replace eligible placeholders with the actual value at request time.

1. You store a Secret value in the current team. Novita encrypts and stores it, and assigns an opaque placeholder such as `novita_secret_<random_hex>`, where `<random_hex>` is a randomly generated hexadecimal string.
2. When you create a sandbox, you map an environment variable to a Secret using `secret_envs`. Novita sets the environment variable to the placeholder, not the actual value.
3. When the sandbox makes an outbound HTTPS request, the proxy inspects it. If a request header carries the placeholder and the target host matches the Secret's allow list, the proxy replaces the placeholder with the real value before the request reaches its destination.
4. If the target host is not in the Secret's allow list, the real value is not substituted. The placeholder is forwarded as-is through the sandbox's normal outbound path, so the destination receives the placeholder rather than the real Secret.

Both request substitution and response filtering happen at the proxy layer, so the real Secret value is never injected into the sandbox through a Secret environment variable. For responses that match a Secret and use a supported format, the proxy also attempts to replace the real value back with the placeholder before returning the response to the sandbox.

<Note>
  Placeholders are only substituted in HTTPS request header values (for example `Authorization` or `X-Api-Key`). Request bodies, URL paths and query parameters, plain HTTP requests, and WebSocket message content are forwarded as-is and are not substituted. Prefer header-based authentication when using Secrets.
</Note>


# Create a Secret
Source: https://docs.novita.ai/guides/sandbox-secret-create



`novita.secret.create()` creates a team-scoped Secret and its first immutable version. It returns `SecretBinding` metadata and does not include the real value. The `hosts` allow list controls which hosts the real value may be substituted for on outbound HTTPS requests.

## Example

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  secret_name = "<your-secret-name>"

  novita = Novita()

  novita.secret.create(
      name=secret_name,
      value="<your-value>",
      hosts=["api.openai.com"],
      description="Sandbox Secrets example",
  )
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const secretName = '<your-secret-name>'

  const novita = new Novita()

  await novita.secret.create({
    name: secretName,
    value: '<your-value>',
    hosts: ['api.openai.com'],
    description: 'Sandbox Secrets example',
  })
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Set the value directly
  novita secret create <secret_name> --host <host> --from-value <secret_value>

  # Read the value from an environment variable
  novita secret create <secret_name> --host <host> --from-env <env_name>

  # Set the value from standard input
  novita secret create <secret_name> --host <host> --from-stdin

  # Set the value via a pipe
  echo "value" | novita secret create <secret_name> --host <host> --from-stdin
  ```
</CodeGroup>

## Parameters

| Parameter | Description |
| - | - |
| `name` | Secret name, unique among active Secrets in the current team. |
| `value` | The real credential to store. It is encrypted and never returned by the API. |
| `hosts` | Allow list of hosts the value may be substituted for. At least one host; hostname only (no protocol, path, port, or query). Wildcards support the `*.` prefix only. |
| `description` | Optional human-readable description. |

<Note>
  If an active Secret with the same name already exists in the current team, the call returns `409 Conflict`. `novita.secret.create()` returns `SecretBinding` metadata only — it never returns the real value.
</Note>


# Delete a Secret
Source: https://docs.novita.ai/guides/sandbox-secret-delete



`novita.secret.delete()` deletes an active Secret by name. A deleted Secret can no longer be referenced by new sandbox create, resume, or clone operations; already-running sandboxes are not rewritten. It returns the name of the deleted Secret.

## Example

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  secret_name = "<your-secret-name>"

  novita = Novita()

  deleted_name = novita.secret.delete(secret_name)
  print("Deleted:", deleted_name)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const secretName = '<your-secret-name>'

  const novita = new Novita()

  const deletedName = await novita.secret.delete(secretName)
  console.log('Deleted:', deletedName)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita secret delete <secret_name>
  ```
</CodeGroup>

<Note>
  Deleting a Secret does not rewrite already-running sandboxes; it only prevents new create, resume, and clone operations from referencing it. The underlying retired value is cleaned up asynchronously once no active execution authorization references it. You can reuse the same name to create a new Secret, which gets a new version and placeholder.
</Note>


# Get a Secret
Source: https://docs.novita.ai/guides/sandbox-secret-get



`novita.secret.get()` fetches the metadata of an active Secret by name. It returns `SecretBinding` metadata and does not return the real value. Use it to confirm a Secret exists and inspect its host allow list before referencing it from a sandbox.

## Example

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  secret_name = "<your-secret-name>"

  novita = Novita()

  # Fetch the Secret metadata by name (no real value returned).
  fetched = novita.secret.get(secret_name)
  print(fetched)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const secretName = '<your-secret-name>'

  const novita = new Novita()

  const fetched = await novita.secret.get(secretName)
  console.log(fetched)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita secret get <secret_name>

  # JSON output
  novita secret get <secret_name> --output json
  ```
</CodeGroup>

<Note>
  `novita.secret.get()` returns `SecretBinding` metadata only — it never returns the real value. If the name does not exist or has been deleted, the call returns `404 Not Found`.
</Note>


# List Secrets
Source: https://docs.novita.ai/guides/sandbox-secret-list



`novita.secret.list()` returns the metadata of all active Secrets for the current authenticated team. It does not return real values, retired versions, or unfinished pending versions. Use it to discover which Secrets are available before referencing one from a sandbox.

## Example

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # List all active Secrets for the current team (no real values).
  secrets = novita.secret.list()
  for binding in secrets:
      print(binding)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const secrets = await novita.secret.list()
  for (const binding of secrets) {
    console.log(binding)
  }
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita secret list

  # JSON output
  novita secret list --output json
  ```
</CodeGroup>

<Note>
  `novita.secret.list()` takes no business parameters and returns a list of `SecretBinding` metadata. An empty list means the current team has no active Secrets. Real values, retired versions, and pending versions are never returned.
</Note>


# Use Secrets in a Sandbox
Source: https://docs.novita.ai/guides/sandbox-secret-sandbox-use



This page shows an end-to-end example of using a Secret in a sandbox: create the Secret, create a sandbox that references it, run code inside the sandbox that authenticates to an external service, and kill the sandbox when done. The sandbox only ever sees an opaque placeholder — the outbound proxy substitutes the real value for requests to hosts on the Secret's allow list.

## Prerequisites

Set the upstream credential before running:

```bash CLI icon="terminal" theme={"system"}
export OPENAI_API_KEY="your-openai-api-key"
```

## End-to-end example

The example creates a Secret, launches a sandbox with `secret_envs`, runs a command inside the sandbox that calls the upstream API using the placeholder, and finally kills the sandbox.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  import os
  import uuid

  from novita_sandbox import Novita

  secret_name = f"openai-example-{uuid.uuid4().hex[:12]}"
  openai_api_key = os.environ["OPENAI_API_KEY"]
  novita = Novita()

  # 1. Create a team-scoped Secret. The real value is stored encrypted;
  #    substitution is only allowed for hosts in the allow list.
  novita.secret.create(
      name=secret_name,
      value=openai_api_key,
      hosts=["api.openai.com"],
      description="Sandbox Secrets example",
  )

  # 2. Create a sandbox and map an env var to the Secret name.
  #    The env var holds a placeholder, not the real value.
  sandbox = novita.sandbox.create(
      secret_envs={"OPENAI_API_KEY": secret_name},
  )

  try:
      # 3. Run code inside the sandbox. It reads the placeholder from the env var
      #    and sends it in an HTTPS header; the proxy substitutes the real value
      #    in flight because api.openai.com is on the allow list.
      result = sandbox.commands.run(
          'curl -s https://api.openai.com/v1/models '
          '-H "Authorization: Bearer $OPENAI_API_KEY"'
      )
      print(result.stdout)
  finally:
      # 4. Kill the sandbox when done.
      sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import 'dotenv/config'
  import { randomUUID } from 'crypto'
  import { Novita } from 'novita-sandbox'

  const secretName = `openai-example-${randomUUID().slice(0, 12)}`
  const openaiApiKey = process.env.OPENAI_API_KEY
  const novita = new Novita()

  // 1. Create a team-scoped Secret. The real value is stored encrypted;
  //    substitution is only allowed for hosts in the allow list.
  await novita.secret.create({
    name: secretName,
    value: openaiApiKey,
    hosts: ['api.openai.com'],
    description: 'Sandbox Secrets example',
  })

  // 2. Create a sandbox and map an env var to the Secret name.
  //    The env var holds a placeholder, not the real value.
  const sandbox = await novita.sandbox.create({
    secretEnvs: { OPENAI_API_KEY: secretName },
  })

  try {
    // 3. Run code inside the sandbox. It reads the placeholder from the env var
    //    and sends it in an HTTPS header; the proxy substitutes the real value
    //    in flight because api.openai.com is on the allow list.
    const result = await sandbox.commands.run(
      'curl -s https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"'
    )
    console.log(result.stdout)
  } finally {
    // 4. Kill the sandbox when done.
    await sandbox.kill()
  }
  ```
</CodeGroup>

<Note>
  The code inside the sandbox only ever reads the placeholder (for example `novita_secret_<random_hex>`). The real key is substituted at the proxy layer and is never present in the sandbox's environment variables, filesystem, or process arguments.
</Note>

## How each step works

| Step | What happens |
| - | - |
| `novita.secret.create(...)` | Creates a team-scoped Secret and its first immutable version. Returns `SecretBinding` metadata (no real value). The `hosts` allow list controls which hosts the real value may be substituted for. Returns `409 Conflict` if an active Secret with the same name already exists. |
| `Novita.s``andbox.create(secret_envs=...)` | Maps environment variable names to Secret names. The env var is set to a placeholder, not the real value. If any referenced Secret cannot be resolved, sandbox creation fails. |
| `sandbox.commands.run(...)` | Runs a command inside the sandbox. When it makes an HTTPS request to an allow-listed host with the placeholder in a header, the proxy replaces the placeholder with the real value before the request leaves. |
| `sandbox.kill()` | Shuts down and removes the sandbox. The Secret itself is unaffected and can be reused by other sandboxes. |

<Warning>
  `secret_envs` differs from regular `envs`: `envs` injects the given value directly, while `secret_envs` interprets the value as a Secret name and injects its placeholder. The same env var name cannot appear in both. Substitution only happens in HTTPS request header values for hosts on the allow list — request bodies, URL query parameters, plain HTTP, and WebSocket content are forwarded as-is.
</Warning>


# Update a Secret
Source: https://docs.novita.ai/guides/sandbox-secret-update



`novita.secret.update()` creates a new immutable version of an existing Secret and makes it active after the value is stored and the policy is persisted. Running executions keep the version and host policy selected at start; newly created, resumed, and cloned executions use the latest active version.

## Example

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  secret_name = "<your-secret-name>"

  novita = Novita()

  novita.secret.update(
      name=secret_name,
      value="<your-value>",
      hosts=["api.openai.com"],
      description="Updated Sandbox Secrets example",
  )
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const secretName = '<your-secret-name>'

  const novita = new Novita()

  await novita.secret.update({
    name: secretName,
    value: '<your-value>',
    hosts: ['api.openai.com'],
    description: 'Updated Sandbox Secrets example',
  })
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Update the value directly
  novita secret update <secret_name> --from-value <secret_value>

  # Read the value from an environment variable
  novita secret update <secret_name> --from-env <env_name>

  # Set the value from standard input
  novita secret update <secret_name> --from-stdin

  # Set the value via a pipe
  echo "value" | novita secret update <secret_name> --from-stdin
  ```
</CodeGroup>

<Note>
  Each update creates a new immutable version, which becomes active only after the value and policy are persisted. Already-running executions continue using the version selected at start; new, resumed, and cloned executions pick up the latest active version.
</Note>


# Secured access
Source: https://docs.novita.ai/guides/sandbox-secured-access



Secure access provides authentication for communication between the SDK and the sandbox controller.

The sandbox controller runs inside each sandbox, and it exposes the management interface used by the SDK, including file-system operations, command execution, and other sandbox control capabilities. When secure access is disabled, anyone with the sandbox ID can invoke these interfaces, which may allow unauthorized control of the sandbox from within the environment.

Starting with SDK version `v2.0.0`, secure access is turned on automatically whenever a sandbox is created. Older custom templates might not support secure access, in which case a rebuild may be required.

## Migration path

For custom templates created with envd earlier than `v0.2.0`, secure access is available only after the template is rebuilt.

You can set `secure` to `false` to temporarily disable secure access during sandbox creation, but disabling secure access is not recommended for production use because of security risks.

Use `novita template list` to check the template `Envd version`. You can also inspect templates in the dashboard.

## Disable secure access

Disabling secure access is not recommended because it may expose the sandbox to security risks.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create({ secure: false }) // Explicitly disable.
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create(secure=False) # Explicitly disable.
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Explicitly disable secure access (not recommended in production)
  novita sandbox create base --no-secure -d

  # Explicitly enable secure access
  novita sandbox create base --secure -d
  ```
</CodeGroup>


# Snapshot Overview
Source: https://docs.novita.ai/guides/sandbox-snapshot



A snapshot is a reusable saved state of a sandbox. It captures the sandbox filesystem and memory state so you can start new sandboxes from that point later.

<Note>
  **Note:** Snapshot is only supported in [v2 regions](/guides/sandbox-overview#regions).
</Note>

Use snapshots when you want to:

* Prepare dependencies once and reuse them in later sandboxes.
* Save a known-good state before running risky or expensive work.
* Start multiple sandboxes from the same captured environment.
* Keep a reusable state after the original sandbox is no longer needed.

***

**Snapshots vs Templates**

Snapshots and templates both let you spawn new sandboxes from a predefined starting point, but they differ in how that point is produced and what it captures.

**A template is built** from a Docker image with declared steps (install packages, copy files, set environment variables) — a clean, reproducible blueprint you author up front.

**A snapshot is captured** from a running sandbox at a moment in time, preserving its filesystem and live memory state, including anything created at runtime.

* Use a template for a clean, reproducible base environment built from a Docker image and declared setup steps.
* Use a snapshot when you want to preserve the exact state of a specific running sandbox, including files created at runtime and its live memory state.

## Terminology

| Term | Description |
| - | - |
| **Origin Sandbox** | The sandbox instance used to create the snapshot. |
| **Snapshot** | The saved state that can be reused to create new sandboxes. |
| **New Sandbox** | A sandbox created from the snapshot. |


# Create Snapshot
Source: https://docs.novita.ai/guides/sandbox-snapshot-create



<Note>
  **Note:** The sandbox is paused while the snapshot is being created.   Snapshots are persistent and remain available even after the source sandbox is deleted.
</Note>

***

## Create a snapshot

Call the method on a running sandbox instance.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox = novita.sandbox.create("base")

  # Prepare state you want to preserve
  sandbox.commands.run("pip install requests")

  # Capture the current state (the sandbox is paused during this)
  snapshot = sandbox.create_snapshot()
  print("Snapshot ID:", snapshot.snapshot_id)

  sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandbox = await novita.sandbox.create('base')

  // Prepare state you want to preserve
  await sandbox.commands.run('pip install requests')

  // Capture the current state (the sandbox is paused during this)
  const snapshot = await sandbox.createSnapshot()
  console.log('Snapshot ID:', snapshot.snapshotId)

  await sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita snapshot create <sandbox_id>
  ```
</CodeGroup>

***

## Create a snapshot by sandbox ID

You can also create a snapshot without an instance by passing the sandbox ID to the static method.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  snapshot_id = "<sandboxID>"
  snapshot = novita.sandbox.create_snapshot(sandbox_id=snapshot_id)
  print("Snapshot ID:", snapshot.snapshot_id)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandboxId = '<sandboxID>'
  const snapshot = await novita.sandbox.createSnapshot(sandboxId)
  console.log('Snapshot ID:', snapshot.snapshotId)
  ```
</CodeGroup>


# Delete Snapshot
Source: https://docs.novita.ai/guides/sandbox-snapshot-delete



Delete a snapshot you no longer need by its snapshot ID. Deleting a snapshot is permanent — sandboxes already created from it are unaffected, but you can no longer create new sandboxes from it.

***

## Delete a snapshot

The call returns `true` if the snapshot was deleted, or `false` if no snapshot with that ID was found.

<Note>
  **Note:** A snapshot may not be deletable while any running sandboxes were created from it. Kill those sandboxes before deleting the snapshot.
</Note>

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  snapshot_id = "<snapshotID>"
  deleted = novita.sandbox.delete_snapshot(snapshot_id)
  print("deleted" if deleted else "not found")
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const deleted = await novita.sandbox.deleteSnapshot('<snapshotID>')
  console.log(deleted ? 'deleted' : 'not found')
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita snapshot delete <snapshot_id>
  ```
</CodeGroup>


# List Snapshots
Source: https://docs.novita.ai/guides/sandbox-snapshot-list



You can list all snapshots, or narrow the list to those created from a specific sandbox.

***

## List snapshots

Call the method and page through the results. Each page is a list of `SnapshotInfo`.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  paginator = novita.sandbox.list_snapshots(limit=20)

  while paginator.has_next:
      snapshots = paginator.next_items()
      for s in snapshots:
          print(s.snapshot_id)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const paginator = novita.sandbox.listSnapshots({ limit: 20 })

  while (paginator.hasNext) {
    const snapshots = await paginator.nextItems()
    for (const s of snapshots) {
      console.log(s.snapshotId)
    }
  }
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # List snapshots (pretty table output by default)
  novita snapshot list

  # JSON output
  novita snapshot list --output json
  ```
</CodeGroup>

***

## Filter by source sandbox

Pass a sandbox ID to list only the snapshots created from that sandbox.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sandbox_id = "<sandboxID>"
  paginator = novita.sandbox.list_snapshots(sandbox_id=sandbox_id)

  while paginator.has_next:
      for s in paginator.next_items():
          print(s.snapshot_id)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sandboxId = '<sandboxID>'

  const paginator = novita.sandbox.listSnapshots({ sandboxId })

  while (paginator.hasNext) {
    for (const s of await paginator.nextItems()) {
      console.log(s.snapshotId)
    }
  }
  ```
</CodeGroup>


# Use Snapshots in a Sandbox
Source: https://docs.novita.ai/guides/sandbox-snapshot-sandbox-use



You can create a new sandbox directly from an existing snapshot. The new sandbox starts from the captured state — the same filesystem and memory filesystem and runtime state, so setup work from the origin sandbox is already available.

## Create a new sandbox from a snapshot

Pass the snapshot\_id to novita.sandbox.create(...) to spawn a new sandbox that starts from the captured state.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  snapshot_id = "<snapshot_id>"

  # Create a new sandbox from a previously captured snapshot
  new_sandbox = novita.sandbox.create(snapshot_id)

  # The state captured in the snapshot is already present
  result = new_sandbox.commands.run("python -c 'import requests; print(requests.__version__)'")
  print(result.stdout)

  new_sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const snapshotId = '<snapshot_id>'

  // Create a new sandbox from a previously captured snapshot
  const newSandbox = await novita.sandbox.create(snapshotId)

  // The state captured in the snapshot is already present
  const result = await newSandbox.commands.run("python -c 'import requests; print(requests.__version__)'")
  console.log(result.stdout)

  await newSandbox.kill()
  ```
</CodeGroup>


# SSH access
Source: https://docs.novita.ai/guides/sandbox-ssh-access



<Note>
  If you just need an interactive terminal, you can connect directly to the sandbox's Remote Shell with the CLI `connect` command — no SSH setup required. See [Remote Shell](/guides/sandbox-cli-remote-shell) for details.
</Note>

You can use a WebSocket proxy to connect to your sandbox via SSH.

By using SSH, you can securely connect to a remote command-line environment, transfer files through protocols such as SCP or SFTP, and work with software that relies on SSH-based connections.

## Quickstart

### 1. Build the SSH template

Define a template that includes an OpenSSH server for SSH access and [websocat](https://github.com/vi/websocat) for forwarding WebSocket-based connections.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita, waitForPort } from 'novita-sandbox'

  const novita = new Novita()

  const template = novita.template
    .fromUbuntuImage('25.04')
    .aptInstall(['openssh-server'])
    .runCmd(
      [
        'curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl',
        'chmod a+x /usr/local/bin/websocat',
      ],
      { user: 'root' }
    )
    .setStartCmd(
      'sudo websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22',
      waitForPort(8081)
    )

  await novita.template.build(template, 'ssh-ready', {
    cpuCount: 2,
    memoryMB: 2048,
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  template = (
      novita.template.from_ubuntu_image("25.04")
      .apt_install(["openssh-server"])
      .run_cmd(
          [
              "curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl",
              "chmod a+x /usr/local/bin/websocat",
          ],
          user="root",
      )
      .set_start_cmd(
          "sudo websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22",
          novita.template.wait_for_port(8081),
      )
  )

  novita.template.build(template, "ssh-ready", cpu_count=2, memory_mb=2048)
  ```
</CodeGroup>

### 2. Launch a sandbox from that template

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const sbx = await novita.sandbox.create('ssh-ready')
  console.log(sbx.sandboxId)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  sbx = novita.sandbox.create("ssh-ready")
  print(sbx.sandbox_id)
  ```
</CodeGroup>

### 3. Connect from your local machine

**macOS**

```bash CLI icon="terminal" theme={"system"}
# Install websocat
brew install websocat

# Connect to your sandbox
ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.sandbox.novita.ai' user@
```

**Linux**

```bash CLI icon="terminal" theme={"system"}
# Install websocat
sudo curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl
sudo chmod a+x /usr/local/bin/websocat

# Connect to your sandbox
ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.sandbox.novita.ai' user@
```

## How it works

[Websocat](https://github.com/vi/websocat) acts as the proxy, and SSH connections are forwarded over WebSocket through the sandbox's exposed ports.


# Defining Template
Source: https://docs.novita.ai/guides/sandbox-template-authoring



The template builder exposes a chainable API for defining how an image is built: choosing a base image, running commands, copying files, setting environment variables, installing packages, and cloning repositories. In the current SDK shape, use the `Novita` client as the main entry point. In JavaScript and TypeScript, start with `novita.template.new()`. In Python, the same builder is available through `novita.template.new()` as well.

## Private registry

To build from an image hosted in a private or cloud registry, pass credentials when selecting the base image. For a generic registry use `fromImage` / `from_image` with a username and password; dedicated helpers exist for AWS ECR, Google Container Registry, Oracle Cloud (OCI), and Huawei Cloud SWR.

| Registry | Method (JS / Python) | Credentials |
| - | - | - |
| Generic (basic auth) | `fromImage` / `from_image` | `username`, `password` |
| AWS ECR | `fromAWSRegistry` / `from_aws_registry` | `accessKeyId`, `secretAccessKey`, `region` |
| Google GCR / GAR | `fromGCPRegistry` / `from_gcp_registry` | `serviceAccountJSON` (path, JSON string, or object) |
| Oracle OCI | `fromOCIRegistry` / `from_oci_registry` | `tenancyOcid`, `userOcid`, `fingerprint`, `privateKey`, `region` |
| Huawei Cloud SWR | `fromHuaweiCloudRegistry` / `from_huawei_cloud_registry` | `accessKeyId`, `secretAccessKey`, `region` |

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Generic private registry (basic auth)
  const template = novita.template.new().fromImage('myregistry.com/team/app:latest', {
    username: process.env.REGISTRY_USERNAME,
    password: process.env.REGISTRY_PASSWORD,
  })

  // AWS ECR
  novita.template.new().fromAWSRegistry('123456789.dkr.ecr.us-west-2.amazonaws.com/app:latest', {
    accessKeyId: 'AKIA...',
    secretAccessKey: '...',
    region: 'us-west-2',
  })
  ```

  ```python Python icon="python" theme={"system"}
  import os
  from novita_sandbox import Novita

  novita = Novita()

  # Generic private registry (basic auth)
  template = novita.template.new().from_image(
      "myregistry.com/team/app:latest",
      username=os.environ.get("REGISTRY_USERNAME"),
      password=os.environ.get("REGISTRY_PASSWORD"),
  )

  # AWS ECR
  novita.template.new().from_aws_registry(
      "123456789.dkr.ecr.us-west-2.amazonaws.com/app:latest",
      access_key_id="AKIA...",
      secret_access_key="...",
      region="us-west-2",
  )
  ```
</CodeGroup>

## Run command

`runCmd` / `run_cmd` runs a shell command during the build. It accepts a single command string or an array/list of commands (joined with `&&`), and an optional `user` to run as.

| Parameter | Type | Description |
| - | - | - |
| `command` | string \| string\[] | A command, or a list of commands joined with `&&` |
| `user` / options.user | string (optional) | User to run the command as (e.g. `root`) |

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  template.runCmd('apt-get update')
  template.runCmd(['pip install numpy', 'pip install pandas'])
  template.runCmd('apt-get install vim', { user: 'root' })
  ```

  ```python Python icon="python" theme={"system"}
  template.run_cmd('apt-get update')
  template.run_cmd(['pip install numpy', 'pip install pandas'])
  template.run_cmd('apt-get install vim', user='root')
  ```
</CodeGroup>

## Copy files

`copy` includes local files or directories in the image. `src` may be a single path or a list of paths; `dest` is the destination in the template. Options control ownership, permissions, and upload behavior.

| Parameter | Type | Description |
| - | - | - |
| `src` | path \| path\[] | Source file(s) or directory path(s) |
| `dest` | path | Destination path in the template |
| `forceUpload` / `force_upload` | boolean (optional) | Force upload even if files are cached |
| `user` | string (optional) | Owner of the copied files |
| `mode` | number (optional) | File permissions, e.g. `0o755` |
| `resolveSymlinks` / `resolve_symlinks` | boolean (optional) | Resolve symlinks while copying |

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  template.copy('requirements.txt', '/home/user/')
  template.copy(['app.ts', 'config.ts'], '/app/', { mode: 0o755 })
  ```

  ```python Python icon="python" theme={"system"}
  template.copy('requirements.txt', '/home/user/')
  template.copy(['app.py', 'config.py'], '/app/', mode=0o755)
  ```
</CodeGroup>

## Set envs

`setEnvs` / `set_envs` sets environment variables from a key/value mapping.

<Note>
  **Important:** Environment variables defined with `setEnvs` / `set_envs` are available **only during the template build**, not at sandbox runtime.
</Note>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  template.setEnvs({ NODE_ENV: 'production', PORT: '8080' })
  ```

  ```python Python icon="python" theme={"system"}
  template.set_envs({'APP_ENV': 'production', 'PORT': '8000'})
  ```
</CodeGroup>

## Package setup (pip, npm, bun, apt)

Dedicated helpers wrap common package managers. Each accepts a single package name or a list; when packages are omitted, the language helpers install from the current project (`pip install .` or `package.json`).

| Method (JS / Python) | Options | Notes |
| - | - | - |
| `pipInstall` / `pip_install` | `g` (default **true**) | Installs globally by default; `g: false` installs with `--user`. No packages → `pip install .` |
| `npmInstall` / `npm_install` | `g`, `dev` | `g` for global (`-g`), `dev` for dev deps. No packages → installs from package.json |
| `bunInstall` / `bun_install` | `g`, `dev` | Same as npm but uses `bun` |
| `aptInstall` / `apt_install` | `noInstallRecommends` / `no_install_recommends`, `fixMissing` / `fix_missing` | Runs `apt-get update` then installs as root; packages are required |

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  template.pipInstall('numpy')
  template.pipInstall(['pandas', 'scikit-learn'])
  template.pipInstall('numpy', { g: false })
  template.pipInstall()

  template.npmInstall('express')
  template.npmInstall('tsx', { g: true })
  template.npmInstall('typescript', { dev: true })

  template.bunInstall(['lodash', 'axios'])

  template.aptInstall(['git', 'curl', 'wget'])
  template.aptInstall(['vim'], { noInstallRecommends: true })
  ```

  ```python Python icon="python" theme={"system"}
  template.pip_install('numpy')
  template.pip_install(['pandas', 'scikit-learn'])
  template.pip_install('numpy', g=False)
  template.pip_install()

  template.npm_install('express')
  template.npm_install('tsx', g=True)
  template.npm_install('typescript', dev=True)

  template.bun_install(['lodash', 'axios'])

  template.apt_install(['git', 'curl', 'wget'])
  template.apt_install(['vim'], no_install_recommends=True)
  ```
</CodeGroup>

## Git clone

`gitClone` / `git_clone` clones a repository into the image. Only the URL is required; an optional destination path and clone options are supported.

| Parameter | Type | Description |
| - | - | - |
| `url` | string | Repository URL (required) |
| `path` | path (optional) | Destination path for the clone |
| `branch` | string (optional) | Branch to clone (adds `--single-branch`) |
| `depth` | number (optional) | Shallow-clone depth |
| `user` | string (optional) | User to run the clone as |

<Note>
  **Note:**`gitClone` / `git_clone` has no dedicated auth parameters. For a private repository, embed credentials/token in the URL or set them up via a prior `runCmd` / `setEnvs` step.
</Note>

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  template.gitClone('https://github.com/user/repo.git', '/app/repo')
  template.gitClone('https://github.com/user/repo.git', undefined, {
    branch: 'main',
    depth: 1,
  })
  template.gitClone('https://github.com/user/repo.git', '/app/repo', { user: 'root' })
  ```

  ```python Python icon="python" theme={"system"}
  template.git_clone('https://github.com/user/repo.git', '/app/repo')
  template.git_clone(
      'https://github.com/user/repo.git',
      branch='main',
      depth=1,
  )
  template.git_clone('https://github.com/user/repo.git', '/app/repo', user='root')
  ```
</CodeGroup>


# Build Template
Source: https://docs.novita.ai/guides/sandbox-template-build-operations



A **template** is the blueprint a sandbox is created from — it defines the base image, installed packages, files, environment, and the commands that run at startup. Every sandbox is spawned from a template, so building a template is how you produce a reusable, ready-to-run environment.

Templates are conceptually close to container images: a template is **built from a Docker image**, and the build process is **similar to writing a Dockerfile** — you start from a base image and layer instructions on top. The difference is where and how you express those instructions.

| | Template | Dockerfile / Docker image |
| - | - | - |
| Starts from | A **Docker image** (e.g. `fromImage('python:3.12')`) or another template | A base image via `FROM` |
| How steps are written | SDK builder calls — `runCmd`, `copy`, `setEnvs`, … (like Dockerfile instructions, but in code) | Dockerfile instructions — `RUN`, `COPY`, `ENV`, … |
| Extra config | Also captures sandbox settings: start command, ready command, CPU / memory | Only the image contents |
| Result | A `templateId` you pass to `sandbox.create(...)` | An image you run as a container |

In short: a template **starts from a Docker image**. Building one feels similar to authoring a Dockerfile, but you describe the steps with the SDK (`fromImage`, `runCmd`, `copy`, `setEnvs`, …) instead of a `Dockerfile`. The output is a `templateId` that Novita uses to launch sandboxes quickly.

***

## Build

Once a template definition is ready, use `novita.template.build(...)` to build it.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const template = novita.template.new().fromImage('python:3.12')

  const build = await novita.template.build(template, 'my-python-template', {
    cpuCount: 2,
    memoryMB: 1024,
  })

  const sandbox = await novita.sandbox.create(build.templateId)
  console.log(sandbox.sandboxId)

  await sandbox.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  template = novita.template.new().from_image("python:3.12")

  build = novita.template.build(
      template,
      "my-python-template",
      cpu_count=2,
      memory_mb=1024,
  )

  sandbox = novita.sandbox.create(build.template_id)
  print(sandbox.sandbox_id)

  sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Build a Dockerfile into a Sandbox template (alias: template ct)
  # Reads ./novita.Dockerfile (or Dockerfile) in the root directory by default
  novita template create my-python-template

  # Specify a Dockerfile, resources, and start command
  novita template create my-python-template \
    --dockerfile ./novita.Dockerfile \
    --cpu-count 2 \
    --memory-mb 1024 \
    --cmd "python app.py"
  ```
</CodeGroup>

## Names

Every build needs a template name. Keep names stable for a logical template family. The returned template ID is the immutable build output you use at runtime.

## Base Image

A template definition always starts by choosing a base image (or an existing template) with one of the `from*` methods.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Novita's default base image
  const base = novita.template.new().fromBaseImage()

  // A specific language image
  const python = novita.template.new().fromPythonImage('3.12')
  const ubuntu = novita.template.new().fromUbuntuImage('24.04')

  // A custom image (optionally with private registry credentials)
  const custom = novita.template
    .new()
    .fromImage('myregistry.com/myimage:latest', {
      username: 'user',
      password: 'pass',
    })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # Novita's default base image
  base = novita.template.new().from_base_image()

  # A specific language image
  python = novita.template.new().from_python_image("3.12")
  ubuntu = novita.template.new().from_ubuntu_image("24.04")

  # A custom image (optionally with private registry credentials)
  custom = novita.template.new().from_image(
      "myregistry.com/myimage:latest",
      credentials={"username": "user", "password": "pass"},
  )
  ```
</CodeGroup>

## User and Workdir

After choosing a base image, you can control which user subsequent build steps run as and which directory they run in.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const template = novita.template
    .new()
    .fromUbuntuImage('24.04')
    .setUser('root')
    .aptInstall(['git'])
    .setUser('user')
    .setWorkdir('/home/user/app')
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  template = (
      novita.template.new()
      .from_ubuntu_image("24.04")
      .set_user("root")
      .apt_install(["git"])
      .set_user("user")
      .set_workdir("/home/user/app")
  )
  ```
</CodeGroup>

## Tags & versioning

Tags let you label builds for release management without changing the underlying template name.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const template = novita.template.new().fromPythonImage('3.12')

  const build = await novita.template.build(template, 'agent-runtime-base', {
    tags: ['v1.0.0', 'latest'],
  })

  await novita.template.assignTags('agent-runtime-base:v1.0.0', 'production')

  const tags = await novita.template.getTags(build.templateId)
  console.log(tags)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  template = novita.template.new().from_python_image("3.12")

  build = novita.template.build(
      template,
      "agent-runtime-base",
      tags=["v1.0.0", "latest"],
  )

  novita.template.assign_tags("agent-runtime-base:v1.0.0", "production")

  tags = novita.template.get_tags(build.template_id)
  print(tags)
  ```
</CodeGroup>

## Logging

Build logs help you inspect provisioning progress and diagnose failures.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const template = novita.template.new().fromPythonImage('3.12')

  const build = await novita.template.build(template, 'my-logged-template')
  console.log(build.buildId)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  template = novita.template.new().from_python_image("3.12")

  build = novita.template.build(template, "my-logged-template")
  print(build.build_id)
  ```
</CodeGroup>

## Error handling

Template builds can fail for several common reasons:

* invalid credentials for a private registry
* package install failures inside `runCmd(...)` / `run_cmd(...)`
* a start command that exits unexpectedly
* a ready command that never succeeds
* CPU and memory settings that do not satisfy platform limits


# Cache
Source: https://docs.novita.ai/guides/sandbox-template-caching



Template builds cache previously completed layers so repeated builds do not have to rerun every instruction, which speeds up iteration. Skip the cache only when you need to force a fresh build — skipping the cache makes builds significantly slower.

Caching is enabled by default and requires no configuration. When you want to skip the cache and force a rebuild, the SDK and CLI offer three levels of control:

| Scope | Effect | Usage |
| - | - | - |
| Whole build | Ignore all cache and rebuild the entire template from scratch | JS: `novita.template.build(t, name, { skipCache: true })`  <br />Python: `novita.template.build(t, name, skip_cache=True)` |
| From a layer onward | Force **this instruction and all following layers** to rebuild; if placed right on a `from*` (FROM) instruction, the whole template cache is invalidated | JS: `.skipCache()`  <br />Python: `.skip_cache()` |
| CLI | Ignore the cache during a build | `novita template build --no-cache` |

<Note>
  **Important:** The chained methods `skipCache()` / `skip_cache()` do not skip a single instruction only — they apply to **all subsequent layers** from the point they are called. The earlier you place the call, the more of the cache is invalidated.
</Note>

**Example:** skip the cache in the definition and force a full rebuild (the two approaches can be combined).

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const template = novita.template
    .new()
    .fromPythonImage('3.12')
    .skipCache() // no cache for any layer from here on
    .runCmd('pip install -U pip')

  const build = await novita.template.build(template, 'my-template-no-cache', {
    skipCache: true, // ignore the cache for the whole template
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  template = (
      novita.template.new()
      .from_python_image("3.12")
      .skip_cache()  # no cache for any layer from here on
      .run_cmd("pip install -U pip")
  )

  build = novita.template.build(
      template,
      "my-template-no-cache",
      skip_cache=True,  # ignore the cache for the whole template
  )
  ```
</CodeGroup>


# Delete Template
Source: https://docs.novita.ai/guides/sandbox-template-delete



You can delete a sandbox template from the CLI or programmatically through the SDK. Deleting a template is permanent.

<Warning>
  **Warning:** Deleting a template is irreversible. The CLI asks for confirmation by default (pass `-y` / `--yes` to skip) and also removes the local `novita.toml` config for the deleted template.
</Warning>

## Examples

<CodeGroup>
  ```bash CLI icon="terminal" theme={"system"}
  # Delete by template ID (alias: template dl)
  novita template delete <templateID>

  # Delete the template defined by novita.toml, without confirmation
  novita template delete -y

  # Pick from an interactive list
  novita template delete --select --team <teamID>
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const deleted = await novita.template.delete('tpl_123')
  console.log(deleted) // true if deleted, false if not found
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  deleted = novita.template.delete('tpl_123')
  print(deleted)  # True if deleted, False if not found
  ```
</CodeGroup>


# Examples
Source: https://docs.novita.ai/guides/sandbox-template-examples



These examples show the preferred `Novita` client entry point for template workflows.

## Build a Python image and run a sandbox

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  template = novita.template.new().from_python_image("3.12")
  build = novita.template.build(template, "example-python-template")

  sandbox = novita.sandbox.create(build.template_id)
  result = sandbox.commands.run("python --version")
  print(result.stdout)

  sandbox.kill()
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const template = novita.template.new().fromPythonImage('3.12')
  const build = await novita.template.build(template, 'example-python-template')

  const sandbox = await novita.sandbox.create(build.templateId)
  const result = await sandbox.commands.run('python --version')
  console.log(result.stdout)

  await sandbox.kill()
  ```

  ```bash CLI icon="terminal" theme={"system"}
  # Build a template from a Dockerfile, then create a sandbox and run a command
  novita template create example-python-template --dockerfile ./novita.Dockerfile
  sandbox_id=$(novita sandbox create example-python-template -d)
  novita sandbox exec "$sandbox_id" -- python --version
  novita sandbox kill "$sandbox_id"
  ```
</CodeGroup>

## Tag a release

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  template = novita.template.new().from_python_image("3.12")
  build = novita.template.build(template, "my-template", tags=["latest", "stable"])

  novita.template.assign_tags("my-template:v1.0", "production")
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const template = novita.template.new().fromPythonImage('3.12')
  const build = await novita.template.build(template, 'my-template', {
    tags: ['latest', 'stable'],
  })

  await novita.template.assignTags('my-template:v1.0', 'production')
  ```
</CodeGroup>

## Inspect templates

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  res = novita.template.list(template_type="template_build", page=1, limit=5)

  for item in res.items:
      print(item.template_id, item.aliases, item.cpu_count, item.memory_mb)
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const paginator = novita.template.list({ templateType: 'template_build', limit: 5 })
  const page = await paginator.nextPage()

  for (const item of page.templates) {
    console.log(item.templateId, item.aliases, item.cpuCount, item.memoryMB)
  }
  ```
</CodeGroup>


# List Template
Source: https://docs.novita.ai/guides/sandbox-template-list



You can list the sandbox templates available to you either from the CLI or programmatically through the SDK. The SDK returns each template's ID, aliases or names, resource sizing, visibility, and build metadata.

## Examples

<CodeGroup>
  ```bash CLI icon="terminal" theme={"system"}
  # Pretty table (default); alias: template ls
  novita template list

  # JSON output
  novita template list --output json

  # Scope to a team
  novita template list --team <teamID>
  ```

  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const paginator = novita.template.list({
    templateType: 'template_build', // or 'snapshot_template'
    page: 1,
    limit: 20,
  })

  const page = await paginator.nextPage()
  console.log(`total: ${page.total}, pages: ${page.totalPages}`)
  for (const t of page.templates) {
    console.log(t.templateId, t.aliases, t.cpuCount, t.memoryMB, t.public)
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  res = novita.template.list(
      template_type="template_build",  # or "snapshot_template"
      page=1,
      limit=20,
  )

  print(f"total: {res.total}, pages: {res.total_pages}")
  for t in res.items:
      print(t.template_id, t.aliases, t.cpu_count, t.memory_mb, t.public)
  ```
</CodeGroup>

## Response

The response is paginated. The top-level fields are:

| Field | Type | Description |
| - | - | - |
| `templates` | TemplateInfo\[] | The templates on this page |
| `total` | number | Total number of templates |
| `page` | number | Current page number |
| `limit` | number | Items per page |
| `totalPages` | number | Total number of pages |

Each entry in `templates` includes:

| Field (JS / Python) | Type | Description |
| - | - | - |
| `templateId` / `template_id` | string | Template ID |
| `aliases` / `names` | string\[] | Template aliases / names |
| `cpuCount` / `cpu_count` | number | vCPU count |
| `memoryMB` / `memory_mb` | number | Memory in MiB |
| `diskSizeMB` / `disk_size_mb` | number | Disk size in MiB |
| `envdVersion` / `envd_version` | string | Envd version |
| `public` | boolean | Whether the template is public |
| `buildId` / `build_id` | string | Latest build ID |
| `buildCount` / `build_count` | number | Number of builds |
| `createdAt` / `created_at` | Date | Creation time |
| `updatedAt` / `updated_at` | Date | Last update time |
| `lastSpawnedAt` / `last_spawned_at` | Date \| null | Last time a sandbox was spawned |
| `createdBy` / `created_by` | object \| null | Creator (`id`, `email`) |


# Template Tags
Source: https://docs.novita.ai/guides/sandbox-template-tags



Tags let you label template builds for release management without changing the underlying template name.

## Assign tags

Use `novita.template.assignTags` (JS) / `novita.template.assign_tags` (Python) to assign one or more tags to an existing template build.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  await novita.template.assignTags('my-template:v1.0', 'production')
  await novita.template.assignTags('my-template:v1.0', ['production', 'stable'])
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  novita.template.assign_tags('my-template:v1.0', 'production')
  novita.template.assign_tags('my-template:v1.0', ['production', 'stable'])
  ```
</CodeGroup>

## Remove tags

Use `novita.template.removeTags` (JS) / `novita.template.remove_tags` (Python) to remove one or more tags from a template by name.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  await novita.template.removeTags('my-template', 'production')
  await novita.template.removeTags('my-template', ['production', 'staging'])
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  novita.template.remove_tags('my-template', 'production')
  novita.template.remove_tags('my-template', ['production', 'staging'])
  ```
</CodeGroup>

## Assign tags at build time

You can also assign tags when a template is built, through the `tags` option of `novita.template.build`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const template = novita.template.new().fromPythonImage('3.12')

  const build = await novita.template.build(template, 'my-template', {
    tags: ['v1.0.0', 'latest'],
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  template = novita.template.new().from_python_image('3.12')

  build = novita.template.build(
      template,
      'my-template',
      tags=['v1.0.0', 'latest'],
  )
  ```
</CodeGroup>

<Note>
  `assignTags` / `assign_tags` returns tag info. `removeTags` / `remove_tags` returns nothing. Both accept either a single tag string or a list of tags.
</Note>


# V2 migration guide
Source: https://docs.novita.ai/guides/sandbox-template-v2-migration-guide



When updating older template examples to the current SDK shape, use the Novita client as the main entry point and follow the current template build flow:

* Start a template builder with `novita.template.new()`.
* Build the template with `novita.template.build(...)` before creating a sandbox.
* Pass the returned template ID into `novita.sandbox.create(...)`.
* Move startup logic into `setStartCmd(...)` / `set_start_cmd(...)` so the environment is ready before runtime requests arrive.
* Move repeated package installation and file-copy steps into the template build instead of running them after sandbox launch.
* Use tags for release promotion instead of changing template names for every rollout.

For JavaScript and TypeScript, use the standard ESM import path when working with the template API in a Node ESM project:

```js JavaScript & TypeScript icon="js" theme={"system"}
import { Novita } from 'novita-sandbox'

const novita = new Novita()
const template = novita.template.new().fromPythonImage('3.12')
const build = await novita.template.build(template, 'my-python-app')
const sandbox = await novita.sandbox.create(build.templateId)
```

For Python, the same flow uses snake\_case methods:

```python Python icon="python" theme={"system"}
from novita_sandbox import Novita

novita = Novita()
template = novita.template.new().from_python_image("3.12")
build = novita.template.build(template, "my-python-app")
sandbox = novita.sandbox.create(build.template_id)
```

This is the currently validated path for the template API in this documentation set.


# Volume Overview
Source: https://docs.novita.ai/guides/sandbox-volume



A volume is a persistent storage resource that can be mounted into sandboxes to share and preserve data across sandbox lifecycles. Data written to a volume outlives any single sandbox: it persists after the sandbox is killed and can be mounted again into other sandboxes.

<Note>
  **Note:** Volume is currently in **beta**. The API and behavior may change as we continue to develop the feature. Feedback is welcome.
</Note>

<Note>
  **Note:** Volume is only supported in [v2 regions](/guides/sandbox-overview#regions).
</Note>

Use volumes when you want to:

* Persist data that should survive sandbox restarts or deletion.
* Share data between multiple sandboxes.
* Keep large datasets or caches outside the sandbox filesystem.
* Control storage with capacity and inode quotas.

***

**Volumes vs Sandboxes**

A sandbox is ephemeral — its filesystem is destroyed when the sandbox is killed. A volume is independent: it exists on its own, and only lives inside a sandbox while mounted. You can attach the same volume to different sandboxes over time.

Volumes are managed through the `novita.volume` methods, and created with `novita.volume.create()`. Mounting is done via a running sandbox's `mount_volume` / `unmount_volume` methods, or by passing `volume_mounts` when creating the sandbox.

## Terminology

| Term | Description |
| - | - |
| **Volume** | A persistent storage resource independent of any sandbox. |
| **Quota** | The capacity (in GiB) and inode limits configured on a volume. |
| **Mount** | Attaching a volume to a sandbox at a path so its data is accessible inside the sandbox. |


# Connect to a Volume
Source: https://docs.novita.ai/guides/sandbox-volume-connect



`novita.volume.connect()` connects to an existing volume by its ID and returns a `Volume` instance you can mount into sandboxes. Use it to reuse a volume that was created earlier — in a different process, session, or service — without re-creating it.

## Connect to a volume

Pass the ID of an existing volume. The returned instance carries the volume ID, name, and auth token.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const volume = await novita.volume.connect('vol-123')

  console.log('Volume ID:', volume.volumeId)
  console.log('Name:', volume.name)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  volume = novita.volume.connect("vol-123")

  print("Volume ID:", volume.volume_id)
  print("Name:", volume.name)
  ```
</CodeGroup>

## Connect, then mount into a sandbox

A common flow is to connect to an existing volume, create a sandbox, mount the volume, read the previously persisted data, and clean up. The data written by an earlier sandbox is still there.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // 1. Connect to an existing volume by ID.
  const volume = await novita.volume.connect('vol-123')

  // 2. Create a sandbox.
  const sandbox = await novita.sandbox.create()

  try {
    // 3. Mount the volume at a path inside the sandbox.
    await sandbox.mountVolume(volume.name, '/mnt/data')

    // 4. Read data that was persisted to the volume earlier.
    const result = await sandbox.commands.run('cat /mnt/data/note.txt')
    console.log(result.stdout)
  } finally {
    // 5. Clean up the sandbox. The volume and its data persist.
    await sandbox.kill()
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # 1. Connect to an existing volume by ID.
  volume = novita.volume.connect("vol-123")

  # 2. Create a sandbox.
  sandbox = novita.sandbox.create()

  try:
      # 3. Mount the volume at a path inside the sandbox.
      sandbox.mount_volume(volume.name, "/mnt/data")

      # 4. Read data that was persisted to the volume earlier.
      result = sandbox.commands.run("cat /mnt/data/note.txt")
      print(result.stdout)
  finally:
      # 5. Clean up the sandbox. The volume and its data persist.
      sandbox.kill()
  ```
</CodeGroup>

<Tip>
  `novita.volume.connect()` looks up an existing volume and does not create a new one. If the volume ID does not exist, the call raises a not-found error (`404`). To create a new volume, use `novita.volume.create()` instead.
</Tip>


# Create a Volume
Source: https://docs.novita.ai/guides/sandbox-volume-create



`novita.volume.create()` creates a new persistent volume that can be mounted into sandboxes to share and persist data across sandbox lifecycles. It returns a `Volume` instance carrying the volume ID, name, and auth token. You can optionally set a capacity quota at creation time.

## Create a volume

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  // Create a volume with an optional capacity quota
  const volume = await novita.volume.create('my-volume', {
    quotaSizeGiB: 10,   // optional: capacity quota in GiB
  })

  console.log('Volume ID:', volume.volumeId)
  console.log('Name:', volume.name)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # Create a volume with an optional capacity quota
  volume = novita.volume.create(
      "my-volume",
      quota_size_gib=10,      # optional: capacity quota in GiB
  )

  print("Volume ID:", volume.volume_id)
  print("Name:", volume.name)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita volume create --name my-volume --quota-size 10
  ```
</CodeGroup>

## Create and mount into a sandbox

A common flow is to create a volume, create a sandbox, mount the volume at a path, use it, and clean up. The data written to the mount path persists in the volume after the sandbox is killed.

<CodeGroup>
  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  # 1. Create a persistent volume.
  volume = novita.volume.create("my-volume", quota_size_gib=10)

  # 2. Create a sandbox.
  sandbox = novita.sandbox.create()

  try:
      # 3. Mount the volume at a path inside the sandbox.
      sandbox.mount_volume(volume.name, "/mnt/data")

      # 4. Write data to the mounted volume — it persists in the volume.
      sandbox.commands.run("echo 'hello volume' > /mnt/data/note.txt")
      result = sandbox.commands.run("cat /mnt/data/note.txt")
      print(result.stdout)
  finally:
      # 5. Clean up the sandbox. The volume and its data persist.
      sandbox.kill()
  ```
</CodeGroup>

<Tip>
  A volume persists independently of any sandbox. Mount it into a sandbox with `sandbox.mount_volume(name, path)`; data written under the mount path remains in the volume after the sandbox is killed, and can be mounted again into other sandboxes.
</Tip>


# Delete a Volume
Source: https://docs.novita.ai/guides/sandbox-volume-delete



Use `novita.volume.destroy()` to permanently delete a volume by its ID. Deleting a volume is irreversible — its data cannot be recovered.

<Warning>
  **Warning:** Destroying a volume permanently deletes it and all data it holds. This cannot be undone.
</Warning>

***

## Delete a volume

`novita.volume.destroy()` returns a boolean: `true` if the volume was deleted, and `false` if no volume with that ID was found.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const deleted = await novita.volume.destroy('vol_123')
  if (deleted) {
    console.log('Volume deleted')
  } else {
    console.log('Volume not found')
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  deleted = novita.volume.destroy("vol_123")
  if deleted:
      print("Volume deleted")
  else:
      print("Volume not found")
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita volume delete <volume_id>
  ```
</CodeGroup>


# Get & List a Volume
Source: https://docs.novita.ai/guides/sandbox-volume-get-list



Use the `novita.volume` methods to look up a single volume or list all volumes in your account. Both return volume metadata such as ID, name, quota, and usage.

***

## Get a volume

`novita.volume.getInfo()` (JS) / `novita.volume.get_info()` (Python) returns information about a single volume by its ID, including its access `token`. If the volume does not exist, a not-found error is raised.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const info = await novita.volume.getInfo('vol_123')
  console.log(info.volumeId, info.name)
  console.log('quota:', info.quotaSizeGiB, 'GiB,', info.quotaInodes, 'inodes')
  console.log('token:', info.token)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  info = novita.volume.get_info("vol_123")
  print(info.volume_id, info.name)
  print("quota:", info.quota_size_gib, "GiB,", info.quota_inodes, "inodes")
  print("token:", info.token)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita volume get <volume_id>
  ```
</CodeGroup>

## List volumes

`novita.volume.list()` returns all volumes in your account as a list of volume info (without the token).

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()

  const volumes = await novita.volume.list()
  for (const vol of volumes) {
    console.log(`${vol.volumeId}  ${vol.name}  ${vol.quotaSizeGiB} GiB`)
  }
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()

  volumes = novita.volume.list()
  for vol in volumes:
      print(f"{vol.volume_id}  {vol.name}  {vol.quota_size_gib} GiB")
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita volume list

  # JSON output
  novita volume list --output json
  ```
</CodeGroup>


# Mount & Unmount a Volume
Source: https://docs.novita.ai/guides/sandbox-volume-mount-unmount



Volumes provide persistent storage that you can attach to a sandbox at a mount path. You can mount volumes when creating the sandbox, or mount and unmount them at runtime on a running sandbox.

***

## Mount at creation

Pass `volumeMounts` (JS) / `volume_mounts` (Python) to `novita.sandbox.create` — a mapping from mount path inside the sandbox to a `Volume` instance or a volume name.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Novita } from 'novita-sandbox'

  const novita = new Novita()
  const volume = await novita.volume.connect('vol_123')

  const sandbox = await novita.sandbox.create({
    volumeMounts: {
      '/mnt/data': volume,   // a Volume instance
      '/mnt/shared': 'my-volume-name', // or a volume name
    },
  })
  console.log('Sandbox created:', sandbox.sandboxId)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox import Novita

  novita = Novita()
  volume = novita.volume.connect("vol_123")

  sandbox = novita.sandbox.create(
      volume_mounts={
          "/mnt/data": volume,          # a Volume instance
          "/mnt/shared": "my-volume-name",  # or a volume name
      },
  )
  print("Sandbox created:", sandbox.sandbox_id)
  ```
</CodeGroup>

## Mount at runtime

`sandbox.mountVolume(name, path)` / `sandbox.mount_volume(name, path)` attaches a volume by name to a mount path on a running sandbox and returns the updated sandbox information.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.mountVolume('my-volume-name', '/mnt/data')
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.mount_volume("my-volume-name", "/mnt/data")
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita volume mount <sandbox_id> --name <volume_name> --path /mnt/data
  ```
</CodeGroup>

## Unmount

`sandbox.unmountVolume(path)` / `sandbox.unmount_volume(path)` detaches the volume mounted at the given path. Pass `force` to force the unmount.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  await sandbox.unmountVolume('/mnt/data')

  // Force unmount
  await sandbox.unmountVolume('/mnt/data', { force: true })
  ```

  ```python Python icon="python" theme={"system"}
  sandbox.unmount_volume("/mnt/data")

  # Force unmount
  sandbox.unmount_volume("/mnt/data", force=True)
  ```

  ```bash CLI icon="terminal" theme={"system"}
  novita volume unmount <sandbox_id> --path /mnt/data

  # Force unmount
  novita volume unmount <sandbox_id> --path /mnt/data --force
  ```
</CodeGroup>


# Overview
Source: https://docs.novita.ai/guides/serverless-gpus-overview



The GPU elastic container product designed specifically for **AI inference**. You only need to specify the container image address and make slight configurations according to your business scenarios to quickly deploy an AI inference service.

## Features

* With the capability of **elastic scaling**, it automatically scales up during peak business traffic and scales down during low traffic periods, ensuring service stability while saving costs as much as possible;
* Built-in efficient **load balancing** ensures that request loads are evenly distributed to each GPU container instance;
* Billing is **accurate to the second**, charging only for the actual running time of the GPU container instances;
* Through technologies such as GPU container instance reservation, image preheating, and high-performance hardware, it can achieve **second-level cold-start**, easily handling traffic peaks;
* Complete **log panel** supports querying real-time log streams, helping you quickly identify and resolve potential issues.

## Terminology

* **Container Image Address**: Refers to the Docker container image address, currently supporting **"public image"** and **"private image"**.
* **Serverless Endpoint**: Represents a GPU elastic container instance. It includes components such as **worker**, **load balancer**, and **elastic scaler**.
* **Worker**: A GPU container instance used by the serverless endpoint to handle specific requests. One Worker corresponds to one GPU container instance.
* **Scale Policy**: Used to control the logic of automatically scaling up during traffic peaks and scaling down during low traffic periods.
* **Health Check Path**: The built-in load balancer performs health checks through this path, determining whether to forward requests to the Worker based on whether the returned status code is `200`.


# Serverless Pricing
Source: https://docs.novita.ai/guides/serverless-gpus-pricing



## Pricing Items

* **Computing Resources**: On-Demand. For Serverless, you pay for the actual computing resources consumed; no charge is incurred for unused resources.
* **Container Disk**: On-Demand. A certain amount of free capacity is supported. Charges will apply to any usage beyond the free allowance, based on capacity and duration.
* **Volume Disk**: On-Demand. A certain amount of free capacity is supported. Charges will apply to any usage beyond the free allowance, based on capacity and duration.
* **Network Volume**: On-Demand. Charged separately based on capacity and duration of use.

## Pricing Method

Charges are calculated based on the GPU specifications configured when creating the Serverless Endpoint, according to the runtime duration of the Workers.

**The billing formula is as follows:**

<u>Serverless Endpoint Cost</u> = Total cost generated by all Workers running on that Endpoint;

<u>Worker Cost</u> = Worker Running Duration (time in running state, in seconds) \* Worker Unit Price (in \$/second).

### Details

* The Worker unit price is determined by the GPU specifications assigned to that Worker. And you can check the pricing on the <a href="https://novita.ai/gpus-console/serverless">Serverless GPUs Console</a>.

<Frame>
  <img />
</Frame>

* If a Worker is configured with multiple GPUs, the unit price of that Worker will change; the final unit price on the configuration page shall prevail.

<Frame>
  <img />
</Frame>

* Also, after creating a Serverless Endpoint, the actual pricing can be checked on the Serverless Endpoint management page.

<Frame>
  <img />
</Frame>

## Account Delinquency Policy

### Handling of Delinquent Accounts

After your account becomes delinquent (insufficient credits in both account balance and vouchers), the platform will send you notifications and suspend the services. The specific impacts are as follows:

* Creation of new Serverless Endpoints is not supported;
* Only "viewing" or "deleting" of existing Serverless Endpoints is supported, modifications are not allowed, wherein:
  * Workers in running state will no longer accept new requests, but Workers currently processing requests will continue to run until all existing requests are completed;
  * Running Workers will be automatically released after processing existing requests;
  * Eventually, the number of Workers in the Serverless Endpoint will scale down to 0, and no new Workers will be created.

### Service Restoration

When your account returns to a non-delinquent status, existing Serverless Endpoints will automatically scale up according to their configurations and resume service.


# Create Async Serverless Endpoint
Source: https://docs.novita.ai/guides/serverless-gpus-quickstart-create-async-endpoint



If you don't have a Novita account, <Link href="https://novita.ai/user/register">sign up</Link> first. For details, see the <Link href="/guides/quickstart">Quickstart guide</Link>.

This article uses the ComfyUI worker image `novitalabs/comfyui-worker:v0.0.1` as an example to show how to create and call an Async Serverless Endpoint.

## 1. Prepare Container Image

Package your runtime environment into a Docker image and upload it to an image registry in advance. Both public and private image registries are supported. Private registries require image pull credentials.

* You can upload your image to Docker Hub. The platform currently provides an [image warm-up service](https://novita.ai/gpus-console/image) for Docker Hub images.

This example uses `novitalabs/comfyui-worker:v0.0.1`. The image includes ComfyUI and the Novita worker SDK. The task input is a ComfyUI workflow JSON, and the worker handler returns generated image results. We recommend configuring object storage environment variables such as `BUCKET_ENDPOINT_URL`, so generated images and videos can be uploaded to your bucket and returned as URLs in the job output.

## 2. Select Instance Specification

Async Serverless Endpoint currently supports the following GPU instance types:

* RTX 4090 24GB
* H100 SXM 80GB

For this `comfyui-worker` example, we recommend **RTX 4090 24GB**.

For additional requirements, [contact us](mailto:support@novita.ai).

## 3. Create Cloud Storage (Optional)

If you need shared or persistent storage, create cloud storage on the [storage management page](https://novita.ai/gpus-console/storage), then mount the storage when creating the endpoint. For details, see [Manage Cloud Storage](https://novita.ai/docs/guides/gpu-instance-quickstart-manage-network-volume).

## 4. Create Endpoint

1. Go to the [Async Serverless GPUs](https://novita.ai/gpus-console/serverless) page, select an instance type, and click "Create Endpoint".
2. Complete the Endpoint parameter configuration.

* **Endpoint Name**: Used to uniquely identify the Endpoint. It is part of the URL when creating jobs. The system generates a random default name. You can customize it, but using the default name is recommended.
* **Worker Configuration**

<table>
  <thead>
    <tr>
      <th>Configuration Item</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Min Worker Count</td>
      <td>The minimum number of worker instances to keep for the Endpoint. Setting a higher minimum helps reduce cold start time. If set to 0, there will be no idle workers when there are no requests, which may increase response latency for new requests. Use 0 with caution for latency-sensitive scenarios.</td>
    </tr>

    <tr>
      <td>Max Worker Count</td>
      <td>The maximum number of worker instances that the Endpoint can scale up to. When request volume increases, the platform automatically increases workers up to this maximum. This limit helps control costs.</td>
    </tr>

    <tr>
      <td>Idle Timeout (seconds)</td>
      <td>When a worker is about to be released due to scale-down, the platform keeps it for the configured idle timeout so it can respond quickly to new requests. You are charged for the worker during this period.</td>
    </tr>

    <tr>
      <td>Max Concurrent Requests</td>
      <td>The maximum number of concurrent requests handled by one worker. If this is exceeded, requests are routed to other workers. If all workers are fully occupied, excess requests are queued until execution is possible.</td>
    </tr>

    <tr>
      <td>GPUs / Worker</td>
      <td>Number of GPU cards allocated to each worker.</td>
    </tr>

    <tr>
      <td>CUDA Version</td>
      <td>CUDA version used by the worker.</td>
    </tr>
  </tbody>
</table>

For this example, select **RTX 4090 24GB** and set `GPUs / Worker` to `1`.

* **Type**:
  * Select **Async**.
* **Elastic Policy**:
  * Select **Queue request policy**.
  * Set **Single worker target concurrency** to `1`. The ComfyUI worker in this example processes one job at a time. When queued requests exceed current worker capacity, the platform scales workers based on the queue request count until reaching the maximum worker count.
* **Image Configuration**:
  * Image address: `novitalabs/comfyui-worker:v0.0.1`.
  * Image repository credentials: If the image is private, provide image pull credentials. You can create credentials on the [security credentials management page](https://novita.ai/gpu-instance/console/settings).
  * HTTP Port: Worker HTTP port.
  * Container start command: Command executed when the container starts.
* **Storage Configuration**:
  * System disk: System disk size per worker instance.
  * Cloud storage: Select cloud storage if you need to mount it. For details, see [Manage Cloud Storage](https://novita.ai/docs/guides/gpu-instance-quickstart-manage-network-volume).
* **Other**:
  * Health check path: This parameter is currently not enabled.
  * Environment variables: Set environment variables required by the service. Example S3 configuration:

```bash theme={"system"}
BUCKET_ENDPOINT_URL=https://s3.<aws-region>.amazonaws.com
BUCKET_ACCESS_KEY_ID=<your-access-key-id>
BUCKET_SECRET_ACCESS_KEY=<your-secret-access-key>
BUCKET_NAME=<your-bucket-name>
```

When using `comfyui-worker`, we strongly recommend configuring object storage so output images are uploaded to a bucket and returned as URLs.

3. Review pricing and click "Deploy with One Click".

## 5. Access the Service

1. On the [Async Serverless GPUs](https://novita.ai/gpus-console/serverless) page, find the newly created Endpoint and ensure its status is "Running".
2. Ensure that at least one Worker in the Endpoint is running.
3. Ensure you have an API Key for authentication. The Endpoint creator and the API Key owner must belong to the same team.

You need the following information to call an Async Serverless Endpoint:

| Parameter | Description |
| - | - |
| Public base URL | `https://async-public.serverless.novita.ai/v1` |
| Endpoint Name | The name generated after creating the Endpoint, for example `0f43a6867e05fddd`. This name is part of the job URL. |
| API Key | Create or copy an API Key from the API Key / Key Management page. Pass it in the `Authorization: Bearer <API_KEY>` request header. |

Get an API Key:

1. Log in to the Novita console.
2. Go to the API Key / Key Management page.
3. Create an API Key and copy the generated `sk_...` value.
4. Ensure the API Key owner and Endpoint owner are in the same team.

### 5.1 Create a Job and Retrieve Output via Curl

The following request is an executable `comfyui-worker` example and matches the tested case. Replace `0f43a6867e05fddd` in the URL with your real Endpoint name, and replace `sk_xxxx` with your real API Key.

<Note>
  The maximum job size accepted by Async Serverless Endpoint is 4 MiB.
</Note>

```bash theme={"system"}
curl -X POST https://async-public.serverless.novita.ai/v1/0f43a6867e05fddd/run \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer sk_xxxx' \
  -d '{
    "input": {
      "workflow": {
        "4": {
          "class_type": "CheckpointLoaderSimple",
          "inputs": {
            "ckpt_name": "flux1-dev-fp8.safetensors"
          }
        },
        "5": {
          "class_type": "EmptyLatentImage",
          "inputs": {
            "width": 512,
            "height": 512,
            "batch_size": 1
          }
        },
        "6": {
          "class_type": "CLIPTextEncode",
          "inputs": {
            "clip": ["4", 1],
            "text": "a red apple on a table"
          }
        },
        "7": {
          "class_type": "CLIPTextEncode",
          "inputs": {
            "clip": ["4", 1],
            "text": "blurry, low quality"
          }
        },
        "3": {
          "class_type": "KSampler",
          "inputs": {
            "model": ["4", 0],
            "positive": ["6", 0],
            "negative": ["7", 0],
            "latent_image": ["5", 0],
            "seed": 42,
            "steps": 10,
            "cfg": 7,
            "sampler_name": "euler",
            "scheduler": "normal",
            "denoise": 1
          }
        },
        "8": {
          "class_type": "VAEDecode",
          "inputs": {
            "samples": ["3", 0],
            "vae": ["4", 2]
          }
        },
        "9": {
          "class_type": "SaveImage",
          "inputs": {
            "filename_prefix": "test",
            "images": ["8", 0]
          }
        }
      },
      "output_node_id": "9"
    }
}'
```

Response example, where `id` is the `job_id`:

```json theme={"system"}
{"id":"8cb6a77c-62aa-4eb4-9226-1ca5724fd9dd","status":"PENDING"}
```

**Check job status and retrieve results**

<Note>
  The maximum output size returned by the Async Serverless Endpoint `status` API is 4 MiB. To avoid this limitation, configure object storage environment variables and return uploaded file URLs in the output.

  Job results are kept in the Async Serverless Endpoint for up to 6 hours after completion.
</Note>

```bash theme={"system"}
curl -X GET https://async-public.serverless.novita.ai/v1/0f43a6867e05fddd/status/33a0bc4b-7312-41f6-ad15-eb9016bd68f9 \
  -H 'Authorization: Bearer sk_xxxx'
```

**Cancel Job**

```bash theme={"system"}
curl -X POST https://async-public.serverless.novita.ai/v1/0f43a6867e05fddd/cancel/e5f3c3c0-c3b1-49c2-9452-bb96eaa34ce6 \
  -H 'Authorization: Bearer sk_xxxx'
```

**Check Endpoint Job Queue Status**

```bash theme={"system"}
curl -X GET https://async-public.serverless.novita.ai/v1/0f43a6867e05fddd/health \
  -H 'Authorization: Bearer sk_xxxx'
```

Response example:

```json theme={"system"}
{
  "workers": {
    "idle": 0,
    "running": 0,
    "throttled": 0,
    "total": 0
  },
  "jobs": {
    "completed": 0,
    "failed": 0,
    "inProgress": 0,
    "inQueue": 0,
    "retried": 0
  }
}
```

### 5.2 Create Job and Get Results via Novita SDK

Install the SDK:

```bash theme={"system"}
pip install novita-gpus
```

```python theme={"system"}
import novita_gpus

novita_gpus.api_key = "sk_xxxx"

input_payload = {
    "workflow": {
        "4": {
            "class_type": "CheckpointLoaderSimple",
            "inputs": {"ckpt_name": "flux1-dev-fp8.safetensors"},
        },
        "5": {
            "class_type": "EmptyLatentImage",
            "inputs": {"width": 512, "height": 512, "batch_size": 1},
        },
        "6": {
            "class_type": "CLIPTextEncode",
            "inputs": {"clip": ["4", 1], "text": "a red apple on a table"},
        },
        "7": {
            "class_type": "CLIPTextEncode",
            "inputs": {"clip": ["4", 1], "text": "blurry, low quality"},
        },
        "3": {
            "class_type": "KSampler",
            "inputs": {
                "model": ["4", 0],
                "positive": ["6", 0],
                "negative": ["7", 0],
                "latent_image": ["5", 0],
                "seed": 42,
                "steps": 10,
                "cfg": 7,
                "sampler_name": "euler",
                "scheduler": "normal",
                "denoise": 1,
            },
        },
        "8": {
            "class_type": "VAEDecode",
            "inputs": {"samples": ["3", 0], "vae": ["4", 2]},
        },
        "9": {
            "class_type": "SaveImage",
            "inputs": {"filename_prefix": "test", "images": ["8", 0]},
        },
    },
    "output_node_id": "9",
}

endpoint = novita_gpus.Endpoint("0f43a6867e05fddd")
job = endpoint.run(input_payload)

print(job.status())
output = job.output(timeout=300)
print(output)
```

The `novita-gpus` SDK default request URL is `https://async-public.serverless.novita.ai/v1`.

## 6. Manage Async Serverless Endpoint

See [Manage Serverless Endpoint](https://novita.ai/docs/guides/serverless-gpus-quickstart-manage-endpoint).


# Create Serverless Endpoint
Source: https://docs.novita.ai/guides/serverless-gpus-quickstart-create-endpoint



This guide takes deploying a **Llama 3.1 8B model** as an example to introduce how to create a Serverless Endpoint from scratch.

## Step 1: Prepare Container Image

You need to package the runtime environment into a Docker image and upload it to an image repository in advance. Currently, <a href="https://novita.ai">Novita AI</a> supports specifying **"public image repository"** and **"private image repository"** (including image repository access credentials).

We use the vLLM official image repository for serving the Llama 3.1 8B model: `vllm/vllm-openai:latest`. You can use this image address directly.

## Step 2: Go to the Console

Go to the <a href="https://novita.ai/gpu-instance/console/serverless">Serverless Console</a>, select the appropriate GPU container instance specification, and click **"Create Endpoint"**.

<Tip>
  Currently, only **RTX 4090 24GB** instance specifications are supported. More options will be available gradually, please stay tuned for official updates.
</Tip>

## Step 3: Configure Serverless Endpoint

Here we use the public image `vllm/vllm-openai:latest` as an example for configuration:

* **Endpoint Name**: Used to uniquely identify your Endpoint. The system will generate a name for you by default, but you can also specify one yourself.
* **App Name**: The application name is part of the Endpoint URL. It is optional and supports customization, **only lowercase letters, numbers, and hyphens are allowed**. If not specified, the system will generate one automatically.
* **Worker Configuration**
  * **Workers Min**: The minimum number of Workers to retain. It can be set to 0, so no Workers will be retained when your service has no requests. Note that setting it to 0 may not respond quickly to subsequent requests. Be cautious if your scenario requires quick response time.
  * **Workers Max**: The maximum number of Workers to retain. When your service request volume increases and triggers the auto-scaling mechanism, the number of Workers will increase. This configuration limits the maximum number of Workers to help control costs.
  * **Idle Timeout (seconds)**: When the auto-scaling mechanism triggers a Worker to be taken offline, the platform will retain the Worker for the specified time to quickly handle potential subsequent request increases. Note that the platform will charge you for this time.
  * **Max Concurrency**: The maximum number of concurrent requests each Worker can handle. When the concurrency exceeds the maximum value, requests will be dispatched to other Workers. If all Workers are fully occupied, requests will be queued for execution.
  * **GPUs/Worker**: The number of GPUs each Worker occupies.
  * **CUDA Version**: Supports specifying the CUDA version.
* **Scale Policy**:
  * **Queue Delay**: Adjusts the number of Workers based on the waiting time of requests in the queue. You need to configure the "Queue Delay Time (seconds)". When the waiting time of requests in the queue exceeds this value, auto-scaling will be triggered. When it is less than this value, auto-scaling will be triggered to scale down (combined with the "Idle Timeout (seconds)" set earlier to determine the specific time for Worker to go offline).
  * **Request Count**: Adjusts the number of Workers based on the number of requests in the queue. You need to configure the "Queue Max Request Count". When the number of requests in the queue exceeds this value, auto-scaling will be triggered. When it is less than this value, auto-scaling will be triggered to scale down (combined with the "Idle Timeout (seconds)" set earlier to determine the specific time for Worker to go offline).
* **Image Configuration**:
  * **Container Image**: The image repository address, such as `vllm/vllm-openai:latest`.
  * **Container Registry Credentials**: When you specify a private image repository, you need to set the access credentials. Please configure it through the <a href="https://novita.ai/gpu-instance/console/settings">Settings - Container Registry Auth</a>.
  * **HTTP Port**: The HTTP port exposed by the Workers. The load balancer will forward requests to this port of the Workers, for example, `8080` in this case.
  * **Container Start Command**: The command executed when the container starts. For example: `--model meta-llama/Llama-3.1-8B-Instruct --max-model-len 4096`.
* **Storage Configuration**: Here you can configure the container disk, volume disk and network volume. Currently, the container disk and volume disk sizes are fixed. More options will be available gradually, please stay tuned for official updates.
* **Others**:
  * **Health Check Path**: The built-in **load balancer** will perform health checks through this path. It will determine whether to forward requests to the Worker based on whether the returned status code is `200`, such as `/health`.
  * **Environment Variables**: Here you can configure the environment variables that the service depends on. For example: `HUGGING_FACE_HUB_TOKEN={Your Hugging Face Access Token (with read permission)}`.

After confirming that the configuration is correct, click **"Deploy"** to complete the creation process.

## Step 4: Access the Deployed Llama 3 8B Model Service

You can find the Serverless Endpoint you just created in the <a href="https://novita.ai/gpu-instance/console/serverless">Serverless Console</a>. When the Endpoint's status changes to **"Serving"**, and at least one Worker is in the **"Running"** state, click the **"Copy"** button to copy the URL for accessing the model service.

And you can use the `curl` command to access the service like below:

```bash theme={"system"}
curl "{URL you just copied}/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-3.1-8B-Instruct",
    "messages": [
        {
            "role": "system",
            "content": "Act like you are a helpful assistant."
        },
        {
            "role": "user",
            "content": "Hi there!"
        }
    ],
    "max_tokens": 512
}'
```

## Next Step

Next, please refer to the document [Manage Serverless Endpoint](/guides/serverless-gpus-quickstart-manage-endpoint) to learn how to check and modify the serverless endpoint you just created.


# Manage Serverless Endpoint
Source: https://docs.novita.ai/guides/serverless-gpus-quickstart-manage-endpoint



## Check Serverless Endpoint

Go to the <a href="https://novita.ai/gpu-instance/console/serverless">Serverless Console</a> to check all the Serverless Endpoints you have created.

Click the **"Dropdown"** button at the bottom right corner of the target serverless endpoint to view the number of running Workers, Worker logs, and advanced operation options for the serverless endpoint.

## Modify Serverless Endpoint

Click the **"Modify"** button to modify the configuration of the target serverless endpoint in the subsequent pop-up window. Except for the "App Name", all other configurations can be modified. For details, refer to: [Configure Serverless Endpoint](/guides/serverless-gpus-quickstart-create-endpoint#step-3-configure-serverless-endpoint).


# Preparations
Source: https://docs.novita.ai/guides/serverless-gpus-quickstart-preparations



Before creating a **serverless endpoint**, you need to be aware of the following information:

* Please ensure that you have completed the [Novita AI](https://novita.ai) account registration;
* Please refer to the [Introduction Document](/guides/serverless-gpus-overview) to know about the product features, terminology, and other information.


# GPUs SDK Usage Guide
Source: https://docs.novita.ai/guides/serverless-gpus-sdk-usage



This guide explains how to use the `novita-gpus` SDK to call an Async Serverless Endpoint and how to customize a worker handler.

## 1. Install SDK

Install the SDK in your client environment or worker runtime environment:

```bash theme={"system"}
pip install novita-gpus
```

## 2. Submit Jobs with SDK

The `novita-gpus` SDK default request URL is `https://async-public.serverless.novita.ai/v1`. To call an Endpoint, set your API Key and create a client with the Endpoint name.

```python theme={"system"}
import novita_gpus

novita_gpus.api_key = "sk_xxxx"

endpoint = novita_gpus.Endpoint("0f43a6867e05fddd")
job = endpoint.run({
    "prompt": "a red apple on a table"
})

print(job.status())
output = job.output(timeout=300)
print(output)
```

## 3. ComfyUI Job Example

For `novitalabs/comfyui-worker:v0.0.1`, the job input must include a ComfyUI workflow. The following minimal example matches the tested case:

```python theme={"system"}
import novita_gpus

novita_gpus.api_key = "sk_xxxx"

endpoint = novita_gpus.Endpoint("0f43a6867e05fddd")

job = endpoint.run({
    "workflow": {
        "4": {
            "class_type": "CheckpointLoaderSimple",
            "inputs": {"ckpt_name": "flux1-dev-fp8.safetensors"},
        },
        "5": {
            "class_type": "EmptyLatentImage",
            "inputs": {"width": 512, "height": 512, "batch_size": 1},
        },
        "6": {
            "class_type": "CLIPTextEncode",
            "inputs": {"clip": ["4", 1], "text": "a red apple on a table"},
        },
        "7": {
            "class_type": "CLIPTextEncode",
            "inputs": {"clip": ["4", 1], "text": "blurry, low quality"},
        },
        "3": {
            "class_type": "KSampler",
            "inputs": {
                "model": ["4", 0],
                "positive": ["6", 0],
                "negative": ["7", 0],
                "latent_image": ["5", 0],
                "seed": 42,
                "steps": 10,
                "cfg": 7,
                "sampler_name": "euler",
                "scheduler": "normal",
                "denoise": 1,
            },
        },
        "8": {
            "class_type": "VAEDecode",
            "inputs": {"samples": ["3", 0], "vae": ["4", 2]},
        },
        "9": {
            "class_type": "SaveImage",
            "inputs": {"filename_prefix": "test", "images": ["8", 0]},
        },
    },
    "output_node_id": "9",
})

print(job.status())
print(job.output(timeout=300))
```

## 4. Custom Handler

On the worker side, call `novita_gpus.start({"handler": handler})` to start the task loop. The platform passes task data to `handler(job)`:

* `job["id"]`: current job id
* `job["input"]`: input content submitted by the client
* The handler return value is used as the job output
* If the returned dict contains an `error` field, the job is marked as failed

Minimal handler example:

```python theme={"system"}
import time
import novita_gpus


def handler(job: dict) -> dict:
    job_id = job["id"]
    job_input = job.get("input", {})
    prompt = job_input.get("prompt", "hello")

    novita_gpus.progress_update(job, {
        "status": "running",
        "message": "job accepted",
    })

    time.sleep(1)

    return {
        "job_id": job_id,
        "prompt": prompt,
        "result": "ok",
    }


if __name__ == "__main__":
    novita_gpus.start({"handler": handler})
```

## 5. Complete Worker Examples

For complete worker source code, Docker image build instructions, and task submission scripts, see the <Link href="https://github.com/novitalabs/gpus-python-example">Novita GPUs Python examples</Link>.

The repository includes two examples:

* <Link href="https://github.com/novitalabs/gpus-python-example/tree/main/workers/comfyui-worker">comfyui-worker</Link>: runs ComfyUI from `handler.py` and returns generated images.
* <Link href="https://github.com/novitalabs/gpus-python-example/tree/main/workers/sleep-worker">sleep-worker</Link>: a minimal `handler.py` that waits for a requested duration and returns a JSON result.

Each example directory contains:

* `handler.py`: defines `handler(job)` and starts the worker with `novita_gpus.start({"handler": handler})`.
* `Dockerfile`: builds the worker image.
* `requirements.txt`: installs `novita-gpus`.
* `submit_task.py`: submits a task with the `novita-gpus` client SDK.

Build and push your worker image to your own registry:

```bash theme={"system"}
IMAGE=<your-registry>/comfyui-worker:v0.0.1

docker buildx build --platform linux/amd64 \
  -t "$IMAGE" \
  --push .
```

Task submission uses the Endpoint name format `<endpoint-id>-<app-name>`.
For example, Endpoint ID `o8UJWkag5WTn` and app name `async` produce:

```text theme={"system"}
o8UJWkag5WTn-async
```

The `submit_task.py` examples accept the Endpoint ID and app name separately, then compose the final Endpoint name:

```bash theme={"system"}
export NOVITA_API_KEY="sk_xxxx"
export NOVITA_ENDPOINT_ID="o8UJWkag5WTn"
export NOVITA_APP_NAME="async"

python submit_task.py \
  --endpoint "$NOVITA_ENDPOINT_ID" \
  --app-name "$NOVITA_APP_NAME" \
  --api-key "$NOVITA_API_KEY"
```

Inside the script, the SDK client is created with the composed Endpoint name:

```python theme={"system"}
endpoint_name = f"{args.endpoint}-{args.app_name}"
endpoint = novita_gpus.Endpoint(endpoint_name, api_key=args.api_key)
```

## 6. Return Images or Files

The Async Serverless Endpoint `status` API has an output size limit. For large files such as images and videos, upload them to object storage first and return URLs in the output.

Configure object storage environment variables in the Endpoint:

```bash theme={"system"}
BUCKET_ENDPOINT_URL=https://<your-bucket-endpoint>
BUCKET_ACCESS_KEY_ID=<your-access-key-id>
BUCKET_SECRET_ACCESS_KEY=<your-secret-access-key>
BUCKET_NAME=<your-bucket-name>
```

Upload an image in the handler:

```python theme={"system"}
import novita_gpus


def handler(job: dict) -> dict:
    image_url = novita_gpus.upload_image(job["id"], "/tmp/output.png")

    return {
        "images": [
            {
                "filename": "output.png",
                "url": image_url,
            }
        ]
    }


if __name__ == "__main__":
    novita_gpus.start({"handler": handler})
```

You can also upload regular files or bytes:

```python theme={"system"}
import novita_gpus

file_url = novita_gpus.upload_file("result.json", "/tmp/result.json")
bytes_url = novita_gpus.upload_bytes("result.txt", b"hello")
```

## 7. FAQ

### Do I need to configure an API Key in the worker?

Usually no. API Keys are mainly used by clients to submit, query, and cancel jobs.

### What handler return value marks a job as failed?

If the handler returns a dict that contains an `error` field, the job is marked as failed:

```python theme={"system"}
return {"error": "invalid input"}
```


# Team
Source: https://docs.novita.ai/guides/team



With Team feature, you can convert your personal account into a team account or join an existing team to collaborate with others.

<Warning>Please note that once you convert your personal account into a team account, you will <u>not be able to downgrade</u> to a personal account.</Warning>

Additionally, you can:

* Invite other users to join your team, and share the team's resources with them;
* Manage your members' permissions to the team's resources by assigning different roles;
* Analyze usage data for each team member.

## Upgrade to Team Account

By default, your account is a personal account. To convert your personal account into a team account, go to [Team Settings](https://novita.ai/settings/team), click the **Upgrade to Team Account** button, and follow the instructions to complete the upgrade.

## Role

Role is a set of permissions that define what actions a member can perform within a team. There are 5 roles in one team:

**<u>Owner</u>**

The Owner member has the same permissions as Admin members. The Owner is the member who created the team. It's a special role and each team can only have one Owner.

**<u>Admin</u>**

Admin members can edit any Novita AI settings, make purchases, update billing, and manage memberships. They can also revoke the access of other Super Administrators.

**<u>Developer</u>**

Developer members can access the full account resources, except for membership management and billing.

**<u>Basic</u>**

Basic members can only access resources they have personally created and cannot view or manage resources created by other team members.

**<u>Billing</u>**

Billing members can manage all billing-related functions, including handling payments, reviewing invoices, and managing subscriptions.

## Permission Details

Permissions define what each team member can do on Novita AI. Every permission is determined by the following dimensions:

* **Role**: The role assigned to a member determines their default permission scope.
* **Action**: Either **Read/Write** (create, update, delete, query) or **View** (query and list only).
* **Data Scope**:
  * **All**: Access resources created by any team member.
  * **Self**: Access only the resources the member created.

<Note>
  If a role is not listed for a given resource, that role has **no permission** on that resource.
</Note>

### Role Access Principles

| Role | Access Principle |
| - | - |
| **Owner** | The highest level of permission. Manages the team, billing, and all resources. Each team has exactly one Owner. |
| **Admin** | Same permissions as Owner, except for Owner-exclusive actions (e.g., model upload). |
| **Developer** | Read/Write access to most business resources (instances, templates, model APIs, etc.). Cannot manage members or billing. |
| **Basic** | By default, only accesses resources they personally created. |
| **Billing** | Manages billing, top-up, payment, and budget-related features only. Cannot operate on business resources. |

### Account

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| Identity Verification | Read/Write | — | — | — | — | All |

### Billing

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| Balance | Read/Write | Read/Write | — | — | Read/Write | All |
| Vouchers | Read/Write | Read/Write | — | — | Read/Write | All |
| Transactions | Read/Write | Read/Write | — | — | Read/Write | All |
| Billing Details | Read/Write | Read/Write | — | — | Read/Write | All |
| Balance Alert | Read/Write | Read/Write | — | — | Read/Write | All |
| Budget | Read/Write | Read/Write | View | View | Read/Write | All / Self |
| Top-up | Read/Write | Read/Write | — | — | Read/Write | All |
| Auto Top-up | Read/Write | Read/Write | — | — | Read/Write | All |
| Payment Methods | Read/Write | Read/Write | — | — | Read/Write | All |
| Dedicated Endpoints Billing | Read/Write | Read/Write | — | — | Read/Write | All |
| Overview | Read/Write | Read/Write | — | — | Read/Write | All |

> Budget: Developer and Basic members can only view their own budgets.

### GPU Instance Settings

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| Container Registry Auth | Read/Write | Read/Write | View | View | — | All |
| Single-NUMA & Auto Migration | Read/Write | Read/Write | View | View | — | All |
| SSH Public Keys | Read/Write | Read/Write | View | View | — | All |

### GPU Resources

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| Instance | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |
| Template | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |
| Image | Read/Write | Read/Write | View | View | — | All |
| Image Prewarm | Read/Write | Read/Write | Read/Write | Read/Write | — | All |
| Storage | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |
| Jobs | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |
| VPC | Read/Write | Read/Write | View | View | — | All |
| Serverless | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |

> Basic members can only operate on their own Instances, Templates, Storage, Jobs, and Serverless resources.

### Model APIs

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| API Key | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |
| LLM API Metrics | Read/Write | Read/Write | Read/Write | — | — | All |
| LLM Settings | Read/Write | Read/Write | View | View | — | All |
| LLM Dedicated Endpoints | Read/Write | Read/Write | Read/Write | Read/Write | — | All / Self |
| Dedicated Endpoints | Read/Write | Read/Write | — | — | — | All |
| Dedicated Endpoints Subscribe | Read/Write | Read/Write | — | — | — | All |
| Playground | Read/Write | Read/Write | Read/Write | Read/Write | — | All |
| Model Upload | Read/Write | — | — | — | — | All |

> API Key: Developer and Basic members can only manage their own keys.
> LLM Dedicated Endpoints: Basic members can only access their own endpoints.

### Bare Metal

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| Quote | Read/Write | Read/Write | Read/Write | View | — | All |
| Order & Test Order | Read/Write | Read/Write | Read/Write | View | — | All |
| Bare Metal Instance | View | View | View | View | — | All |
| Bare Metal Storage | View | View | View | View | — | All |

> Only Owner, Admin, and Developer can submit quote requests and place orders. Basic members can only view.

### Team Management

| Resource | Owner | Admin | Developer | Basic | Billing | Data Scope |
| - | :-: | :-: | :-: | :-: | :-: | :-: |
| Member Management | Read/Write | Read/Write | View | View | View | All / Self |
| Audit Log | Read/Write | Read/Write | — | — | — | All |
| Quota Request | Read/Write | Read/Write | — | — | — | All |
| Affiliate Program | Read/Write | — | — | — | — | All |

> Member Management: Developer, Basic, and Billing members can only view the member list. Inviting, removing, or changing roles is restricted to Owner and Admin.

## Invite Members

Once you upgrade to a team account, you can invite other users to join your team by clicking the **Invite Members** button in the [Team Settings](https://novita.ai/settings/team).

<Tip>
  **Please note that:**

  * Currently, you can invite up to <u>200</u> members to your team.
  * Each member need to be assigned a <u>Role</u> to access the team's resources.
  * The email address you input will be used to send an invitation link to the member. The member can join the team after logging into Novita AI with <u>the consistent email address</u>.
</Tip>

## Manage Members

In the [Team Settings](https://novita.ai/settings/team), you can manage your team members or pending invitations.

### Edit Member Role

To edit a member's role, click the **Setting Icon** on the right side of the member's row, then click the **Edit** button and follow the instructions to change the role.

Note that after the role is changed, the member will need to log in to Novita AI again for the new role to take effect.

### Remove Member

To remove a active member from your team, click **Setting Icon** in the right side of the target member's row, then click **Remove** button, and a confirmation dialog will be shown. Click **Remove** to confirm the removal.

### Cancel Pending Invitation

To cancel a pending invitation, click the **Setting Icon** in the right side of the member's row, then click **Cancel Invitation** button, and a confirmation dialog will be shown. Click **Confirm** to confirm the cancellation.

## Audit Logs

Audit logs provide a detailed record of important resource actions performed within a team and can be used to track team members' activities. For now, only the owner and admin members can view the audit logs.

To view the audit logs, please get to the [Team Settings](https://novita.ai/settings/team).


# How to Explore the Templates in Novita?
Source: https://docs.novita.ai/guides/template_library

The Template Community enables you to easily create, share, and deploy templates. Join us and become a part of the community!

## Let’s start by creating a template!

There are two ways to access the `Create New Template` :

1. **Option 1**: In the Console, go to the [Explore ](https://novita.ai/gpus-console/explore)section and find the `Create My Template` button.

<Frame>
  <img alt="Temlib1 Pn" />
</Frame>

2. **Option 2**: In the Console, go to the [Template ](https://novita.ai/gpus-console/templates)section and find the `New Template` button.

<Frame>
  <img alt="Temlib2 Pn" />
</Frame>

Once you click on it, you’ll open the `Create Template` popup.

<Frame>
  <img alt="Temlib3 Pn" />
</Frame>

**Here, you’ll need to configure your template:**

1. **Name Your Template:** Choose a clear name that makes it easy to identify and use. We suggest selecting a name that relates to the content of the image.
2. **Set up the image:** Bundle your runtime environment into an image and upload it to an image repository ahead of time. Then, paste the image URL into the `Container Image` field.\
   Novita AI supports both public and private image repositories (with optional access credentials). If you're using a **private image repository**, you must provide Container Registry Credentials, which can be added under  [**Settings > Container Registry Auth.**](https://novita.ai/gpus-console/settings)
3. **Set Template Visibility:** You can choose `Private`, which makes the template accessible only to you and your team. However, we strongly recommend selecting `Public` to share your work with the broader community. Public templates will appear in the Template Library, where all users can view, deploy, favorite, and share them—helping your work gain visibility and appreciation.

   **Please note:** for security reasons, **public templates only support public image repositories**, and the Container Registry Credentials input will be disabled.
4. **Specify Container Disk Size:** Determine the disk size based on your needs. We provide 60 GB of free disk space by default.
5. **Advanced Configuration Options:** To improve usability, you can optionally provide advanced configuration options such as `Container Start Command`, `Local Mount`, `Expose HTTP/TCP Ports`, and `Environment Variables`.

You can find detailed explanations of these terms [here](/guides/gpu-instance-overview).

<Frame>
  <img alt="Readmechanchanchan Pn" />
</Frame>

We strongly encourage you to create a `README` that clearly explains the purpose and configuration of your template. A well-written README helps both you and your team quickly understand the template and makes it easier for others to use—especially if you choose to share it publicly.

* To ensure your template is easy to adopt, we recommend keeping your README **concise and beginner-friendly**.
* If your template requires any **external dependencies**, please include **clear setup instructions** within the README. This guidance helps others successfully deploy your template and ensures a smooth experience

**Unsure What Kind of Template to Create?**

* We suggest checking out popular open-source projects such as **vLLM**, **SGLang**, **Ollama**, **ComfyUI**, **Stable Diffusion WebUI**, or base environments like **CUDA**, **PyTorch**, or OS-specific setups (e.g., **CentOS** / **Ubuntu**, different versions).

We look forward to seeing what you create!

***

## Then try to explore some templates in the library!

If you've created a public template, you’ll see it in the [**Console > Template Library**](https://novita.ai/gpus-console/templates-library) section.

<Frame>
  <img alt="Temlib5 Pn" />
</Frame>

In the `Template Library`, you’ll find templates uploaded by both Novita and the community. Clicking on a template will display the information about it, including the `README` and `configuration details`. If you like the template, you can click `Deploy` to instantly deploy it.

<Frame>
  <img alt="Temlib6 Pn" />
</Frame>

If you really like a template, you can click `Favorite` to save it to your favorites. This makes it easier to find later, and your `favoriting` helps bring attention to the template, allowing more users to discover it. Your favorited templates will be displayed when you filter by `My favorites`.

<Frame>
  <img alt="Temlib7 Pn" />
</Frame>

**Want to share this template with your friends?** Click the `Copy Link` button to easily share it. This way, they can check it out and use it!

<Frame>
  <img alt="Temlib8 Pn" />
</Frame>

***

## Use a template to create an instance

The `Template Library` is integrated into the `Explore` section during instance creation.

<Frame>
  <img alt="Temlib9 Pn" />
</Frame>

Click `Change Template` to view both personal/team-created templates and all templates from the Template Library. Here, you can also filter to select your favorited templates.

<Frame>
  <img alt="Temlib10 Pn" />
</Frame>

Click on a template’s icon to view its details. Once you’ve found the template you need, click on it to select it and proceed with instance creation.

<Frame>
  <img alt="Temlib11 Pn" />
</Frame>

***

## Join Us!

**Now that you know how to create, upload, and share templates, come join us in building a community-friendly template ecosystem for tech enthusiasts!**

**Get in Touch:**

* Discord: [novita.ai](https://discord.com/invite/a3vd9r3uET)


# Query API Key Billing
Source: https://docs.novita.ai/api-reference/basic-query-apikey-bill

GET https://api.novita.ai/openapi/v1/billing/apikey/bill/list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Billing granularity. The maximum query range depends on the granularity you pick:

  * `Hour` (up to 7 days per request)
  * `Day` (up to 31 days per request)
  * `Week` (up to 31 days per request)
  * `Month` (up to 31 days per request)
</ParamField>

<ParamField type="string">
  Product type or business category. Common values: `llm`, `gen_api`, `web_search`.
</ParamField>

<ParamField type="string">
  Product name. Supports fuzzy matching.
</ParamField>

<ParamField type="integer">
  Query start time, Unix timestamp in seconds (int64). Must be greater than or equal to `2026-01-01 00:00:00`.
</ParamField>

<ParamField type="integer">
  Query end time, Unix timestamp in seconds (int64). Must be greater than `startTime`.
</ParamField>

<Info>
  This endpoint only supports data after `2026-01-01 00:00:00`. It does not support pagination. The range between `startTime` and `endTime` must stay within the limit for the `cycleType` you request: 7 days for `Hour`, 31 days for `Day`, `Week`, and `Month`. To cover a longer period, split it into several requests.
</Info>

<Warning>
  **The query range limit is now enforced by the API.** It used to be applied only in the Novita console, so requests sent directly to this endpoint were never rejected for being too long. If your integration queries more than 31 days at once (or more than 7 days with `cycleType=Hour`), it now fails with HTTP `400` and a message in the form:

  ```text theme={"system"}
  time range must not exceed 31 days for cycleType "Month"
  ```

  The message always names the limit and the granularity that was rejected, so you can split the range and retry.
</Warning>

<Note>
  The API Key billing page in the Novita console offers only hourly and daily granularity. This endpoint still accepts all four values, so `Week` and `Month` remain available to API callers even though they are no longer offered in the console.
</Note>

## Response Parameters

<ResponseField name="bills" type="object[]">
  API Key bill list.

  <Expandable title="properties">
    <ResponseField name="userId" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="startTime" type="string">
      Bill start time, Unix timestamp in seconds. It marks the first second of the billing period. With `cycleType=Hour`, it is the top of that hour, for example `10:00:00`.
    </ResponseField>

    <ResponseField name="endTime" type="string">
      Bill end time, Unix timestamp in seconds. It marks the last second of the billing period. With `cycleType=Hour`, it is minute `59` and second `59` of the same hour, for example `10:59:59`.
    </ResponseField>

    <ResponseField name="billingMethod" type="int32">
      Billing method: `1` (pay-as-you-go), `2` (subscription).
    </ResponseField>

    <ResponseField name="productName" type="string">
      Product name.
    </ResponseField>

    <ResponseField name="category" type="string">
      Product type or subcategory.
    </ResponseField>

    <ResponseField name="ownerID" type="string">
      API Key ID.
    </ResponseField>

    <ResponseField name="apikeyName" type="string">
      API Key name.
    </ResponseField>

    <ResponseField name="apikeyMask" type="string">
      Masked API Key.
    </ResponseField>

    <ResponseField name="productId" type="string">
      Product ID.
    </ResponseField>

    <ResponseField name="billNum0" type="string">
      Usage field 0. For `llm`, this usually means input price per 1M tokens; GPU: price per card per hour; storage: price per GB per day.
    </ResponseField>

    <ResponseField name="billNum1" type="string">
      Usage field 1. For `llm`, this usually means output tokens.
    </ResponseField>

    <ResponseField name="billNum2" type="string">
      Usage field 2. For `llm`, this usually means read cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="billNum3" type="string">
      Usage field 3. For `llm`, this usually means write 5-minute cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="billNum4" type="string">
      Usage field 4. For `llm`, this usually means reasoning token price per 1M tokens.
    </ResponseField>

    <ResponseField name="billNum5" type="string">
      Usage field 5. For `llm`, this usually means write 1-hour cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="billNum6" type="string">
      Usage field 6.
    </ResponseField>

    <ResponseField name="billNum7" type="string">
      Usage field 7.
    </ResponseField>

    <ResponseField name="billNum8" type="string">
      Usage field 8.
    </ResponseField>

    <ResponseField name="billNum9" type="string">
      Usage field 9.
    </ResponseField>

    <ResponseField name="billNum10" type="string">
      Usage field 10.
    </ResponseField>

    <ResponseField name="billNum11" type="string">
      Usage field 11.
    </ResponseField>

    <ResponseField name="billNum12" type="string">
      Usage field 12.
    </ResponseField>

    <ResponseField name="billNum13" type="string">
      Usage field 13.
    </ResponseField>

    <ResponseField name="billNum14" type="string">
      Usage field 14.
    </ResponseField>

    <ResponseField name="billNum15" type="string">
      Usage field 15.
    </ResponseField>

    <ResponseField name="basePrice0" type="string">
      Original price field 0. For `llm`: input price per 1M tokens; GPU: price per card per hour; storage: price per GB per day.
    </ResponseField>

    <ResponseField name="basePrice1" type="string">
      Original price field 1. For `llm`, output price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice2" type="string">
      Original price field 2. For `llm`, read cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice3" type="string">
      Original price field 3. For `llm`, write 5-minute cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice4" type="string">
      Original price field 4. For `llm`, reasoning token price per 1M tokens.
    </ResponseField>

    <ResponseField name="basePrice5" type="string">
      Original price field 5. For `llm`, write 1-hour cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice0" type="string">
      Discounted price field 0. For `llm`, discounted input token price.
    </ResponseField>

    <ResponseField name="discountPrice1" type="string">
      Discounted price field 1. For `llm`, discounted output token price.
    </ResponseField>

    <ResponseField name="discountPrice2" type="string">
      Discounted price field 2. For `llm`, discounted read cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice3" type="string">
      Discounted price field 3. For `llm`, discounted write 5-minute cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice4" type="string">
      Discounted price field 4. For `llm`, discounted reasoning token price per 1M tokens.
    </ResponseField>

    <ResponseField name="discountPrice5" type="string">
      Discounted price field 5. For `llm`, discounted write 1-hour cache price per 1M tokens.
    </ResponseField>

    <ResponseField name="amount" type="string">
      Total amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="voucherAmount" type="string">
      Amount deducted by vouchers, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="payAmount" type="string">
      Cash payment amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="payAmountDisplay" type="float">
      Display value of cash payment amount, in USD.
    </ResponseField>

    <ResponseField name="pricePrecision" type="int32">
      Price precision. Unit price (USD) = unit price / 10000 / `pricePrecision`.
    </ResponseField>

    <ResponseField name="llmSeries" type="string">
      LLM series.
    </ResponseField>

    <ResponseField name="requestCount" type="string">
      Request count.
    </ResponseField>

    <ResponseField name="multimodalPricing" type="object">
      Multimodal model billing details.
    </ResponseField>

    <ResponseField name="tieredConfig" type="object">
      Tiered billing configuration.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash Daily granularity theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/billing/apikey/bill/list?cycleType=Day&category=llm&startTime=1767225600&endTime=1769817599' \
    --header 'Authorization: Bearer <API_KEY>'
  ```

  ```bash Hourly granularity theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/billing/apikey/bill/list?cycleType=Hour&category=llm&startTime=1767225600&endTime=1767830399' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "bills": [
      {
        "userId": "<string>",
        "startTime": "1767225600",
        "endTime": "1767311999",
        "billingMethod": 1,
        "productName": "<string>",
        "category": "llm",
        "ownerID": "<api_key_id>",
        "billNum0": "1000",
        "billNum1": "500",
        "basePrice0": "0",
        "basePrice1": "0",
        "discountPrice0": "0",
        "discountPrice1": "0",
        "amount": "10000",
        "voucherAmount": "0",
        "payAmount": "10000",
        "payAmountDisplay": 1,
        "pricePrecision": 1,
        "productId": "<string>",
        "apikeyName": "<string>",
        "apikeyMask": "sk-****"
      }
    ]
  }
  ```
</ResponseExample>


# Query Transactions
Source: https://docs.novita.ai/api-reference/basic-query-transaction

GET https://api.novita.ai/openapi/v1/bill/transaction

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="int32">
  Page number, starting from `1`.
</ParamField>

<ParamField type="int32">
  Number of items per page.
</ParamField>

<ParamField type="string">
  Transaction serial number.
</ParamField>

<ParamField type="integer">
  Transaction start time, Unix timestamp in seconds (int64).
</ParamField>

<ParamField type="integer">
  Transaction end time, Unix timestamp in seconds (int64).
</ParamField>

<ParamField type="string">
  Transaction type. Common values: `recharge`, `refund`, `consume`.
</ParamField>

<ParamField type="string">
  Transaction channel. Common values: `AliPay`, `WeixinPay`, `PublicRemittance`.
</ParamField>

<ParamField type="string">
  Transaction status: `pending`, `success`, `failed`, `expired`.
</ParamField>

<ParamField type="string">
  Transaction channel filter. Same as `transactionChannel`. Prefer `transactionChannel`.
</ParamField>

<ParamField type="string">
  Order type: `recharge`, `refund`.
</ParamField>

## Response Parameters

<ResponseField name="data" type="object[]">
  Transaction record list.

  <Expandable title="properties">
    <ResponseField name="customerId" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="serialNumber" type="string">
      Transaction serial number.
    </ResponseField>

    <ResponseField name="transactionTime" type="string">
      Transaction time, Unix timestamp in seconds.
    </ResponseField>

    <ResponseField name="transactionType" type="string">
      Transaction type.
    </ResponseField>

    <ResponseField name="transactionChannel" type="string">
      Transaction channel.
    </ResponseField>

    <ResponseField name="transactionAmount" type="string">
      Transaction amount, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="walletBalance" type="string">
      Wallet balance after the transaction, unit is 1/10000 USD.
    </ResponseField>

    <ResponseField name="note" type="string">
      Transaction note.
    </ResponseField>

    <ResponseField name="state" type="string">
      Transaction status.
    </ResponseField>

    <ResponseField name="invoiceURL" type="string">
      Transaction voucher or invoice URL.
    </ResponseField>

    <ResponseField name="orderType" type="string">
      Order type. Values: `recharge`, `refund`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="int32">
  Total number of transaction records matching the query.
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/bill/transaction?pageNo=1&pageSize=20&transactionTimeStart=1764547200&transactionTimeEnd=1767225599' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "data": [
      {
        "customerId": "<string>",
        "serialNumber": "<string>",
        "transactionTime": "1764547200",
        "transactionType": "recharge",
        "transactionChannel": "AliPay",
        "transactionAmount": "1000000",
        "walletBalance": "1000000",
        "note": "",
        "state": "success",
        "invoiceURL": "",
        "orderType": "recharge"
      }
    ],
    "total": 1
  }
  ```
</ResponseExample>


# Check If API Key Budget Is Sufficient
Source: https://docs.novita.ai/api-reference/budget-check-key-enough

POST https://api.novita.ai/openapi/v1/user/team/check-key-budget

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Sub-user account ID.
</ParamField>

<ParamField type="string">
  API Key ID.
</ParamField>

## Response Parameters

<ResponseField name="enough" type="bool">
  Whether the API Key's budget is sufficient.
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request POST \
    --url https://api.novita.ai/openapi/v1/user/team/check-key-budget \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"member_id": "<string>", "key_id": "<string>"}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "enough": true
  }
  ```
</ResponseExample>


# Check If API Key Budget Is Sufficient for a Given Cost
Source: https://docs.novita.ai/api-reference/budget-check-key-enough-by-cost

POST https://api.novita.ai/openapi/v1/user/team/check-key-budget-by-cost

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Sub-user account ID.
</ParamField>

<ParamField type="string">
  API Key ID.
</ParamField>

<ParamField type="int64">
  Cost to be deducted, in units of 1/10000 USD (e.g., 10000 = \$1.00).
</ParamField>

## Response Parameters

<ResponseField name="enough" type="bool">
  Whether the API Key's budget is sufficient after deducting the given cost.
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request POST \
    --url https://api.novita.ai/openapi/v1/user/team/check-key-budget-by-cost \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"member_id": "<string>", "key_id": "<string>", "cost": 10000}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "enough": true
  }
  ```
</ResponseExample>


# Check If Member Budget Is Sufficient
Source: https://docs.novita.ai/api-reference/budget-check-member-enough

POST https://api.novita.ai/openapi/v1/user/team/check-budget

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The sub-user account ID of the member to check.
</ParamField>

## Response Parameters

<ResponseField name="enough" type="bool">
  Whether the member's remaining budget is sufficient (i.e., remaining balance is greater than zero).
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request POST \
    --url https://api.novita.ai/openapi/v1/user/team/check-budget \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"member_id": "<string>"}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "enough": true
  }
  ```
</ResponseExample>


# Check If Member Budget Is Sufficient for a Given Cost
Source: https://docs.novita.ai/api-reference/budget-check-member-enough-by-cost

POST https://api.novita.ai/openapi/v1/user/team/check-budget-by-cost

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The sub-user account ID of the member to check.
</ParamField>

<ParamField type="int64">
  The cost amount to be deducted, in units of 1/10000 USD (e.g., `10000` = \$1.00).
</ParamField>

## Response Parameters

<ResponseField name="enough" type="bool">
  Whether the member's remaining budget is sufficient to cover the given cost (i.e., remaining balance is greater than or equal to the specified cost).
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request POST \
    --url https://api.novita.ai/openapi/v1/user/team/check-budget-by-cost \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"member_id": "<string>", "cost": 10000}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "enough": true
  }
  ```
</ResponseExample>


# Query API Key Budget List for a Member
Source: https://docs.novita.ai/api-reference/budget-query-key-list

GET https://api.novita.ai/openapi/v1/user/team/key-budget-list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  The sub-user account ID to query.
</ParamField>

## Response Parameters

<ResponseField name="key_budgets" type="object[]">
  List of API Key budget entries for the specified member. API Keys with no budget configured appear in the list with default values (`budget_type=Unlimited`, `used=0`, `budget_limit=0`).

  <Expandable title="properties">
    <ResponseField name="key_id" type="string">
      API Key ID.
    </ResponseField>

    <ResponseField name="key_name" type="string">
      API Key name.
    </ResponseField>

    <ResponseField name="budget_type" type="string">
      Budget type. Possible values: `Unlimited`, `One-time`, `Recurring`.
    </ResponseField>

    <ResponseField name="budget_limit" type="int64">
      Budget limit, in units of 1/10000 USD (e.g., 10000 = \$1.00).
    </ResponseField>

    <ResponseField name="used" type="int64">
      Amount used, in units of 1/10000 USD (e.g., 10000 = \$1.00).
    </ResponseField>

    <ResponseField name="remaining" type="int64">
      Remaining budget amount, in units of 1/10000 USD (e.g., 10000 = \$1.00). May be negative when usage exceeds the limit.
    </ResponseField>

    <ResponseField name="cycle" type="string">
      Billing cycle (e.g., `Monthly`). Only present when `budget_type=Recurring`.
    </ResponseField>

    <ResponseField name="period_start" type="string">
      Current billing period start time, formatted as an ISO 8601 string. Only present when `budget_type=Recurring`.
    </ResponseField>

    <ResponseField name="period_end" type="string">
      Current billing period end time, formatted as an ISO 8601 string. Only present when `budget_type=Recurring`.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/user/team/key-budget-list?member_id=<string>' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "key_budgets": [
      {
        "key_id": "<string>",
        "key_name": "<string>",
        "budget_type": "<string>",
        "budget_limit": 0,
        "used": 0,
        "remaining": 0,
        "cycle": "<string>",
        "period_start": "<string>",
        "period_end": "<string>"
      }
    ]
  }
  ```
</ResponseExample>


# Query Team Member Budget List
Source: https://docs.novita.ai/api-reference/budget-query-member-list

GET https://api.novita.ai/openapi/v1/user/team/budget-list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response Parameters

<ResponseField name="member_count" type="int64">
  Total number of team members.
</ResponseField>

<ResponseField name="budget_count" type="int64">
  Number of members with a budget configured.
</ResponseField>

<ResponseField name="budgets" type="object[]">
  List of team member budget entries.

  <Expandable title="properties">
    <ResponseField name="email" type="string">
      Member email address.
    </ResponseField>

    <ResponseField name="role" type="string">
      Member role within the team.
    </ResponseField>

    <ResponseField name="status" type="string">
      Member account status.
    </ResponseField>

    <ResponseField name="member_id" type="string">
      Sub-user account ID of the member.
    </ResponseField>

    <ResponseField name="phone" type="string">
      Member phone number.
    </ResponseField>

    <ResponseField name="user_id" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="remark_name" type="string">
      Remark or alias name for the member.
    </ResponseField>

    <ResponseField name="budget_type" type="string">
      Budget type. Possible values: `Unlimited`, `One-time`, `Recurring`.
    </ResponseField>

    <ResponseField name="budget_limit" type="int64">
      Budget limit in units of 1/10000 USD (e.g., `10000` = \$1.00). Only meaningful when `budget_type` is `One-time` or `Recurring`.
    </ResponseField>

    <ResponseField name="used" type="int64">
      Amount used in units of 1/10000 USD (e.g., `10000` = \$1.00).
    </ResponseField>

    <ResponseField name="remaining" type="int64">
      Remaining budget in units of 1/10000 USD (e.g., `10000` = \$1.00). May be negative when usage exceeds the limit.
    </ResponseField>

    <ResponseField name="cycle" type="string">
      Billing cycle for budget reset. Currently only `Monthly` is supported. Present only when `budget_type` is `Recurring`.
    </ResponseField>

    <ResponseField name="period_start" type="string">
      Start time of the current billing period, formatted as an ISO 8601 string. Present only when `budget_type` is `Recurring`.
    </ResponseField>

    <ResponseField name="period_end" type="string">
      End time of the current billing period, formatted as an ISO 8601 string. Present only when `budget_type` is `Recurring`.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url https://api.novita.ai/openapi/v1/user/team/budget-list \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "member_count": 0,
    "budget_count": 0,
    "budgets": [
      {
        "email": "<string>",
        "role": "<string>",
        "status": "<string>",
        "member_id": "<string>",
        "phone": "<string>",
        "user_id": "<string>",
        "remark_name": "<string>",
        "budget_type": "<string>",
        "budget_limit": 0,
        "used": 0,
        "remaining": 0,
        "cycle": "<string>",
        "period_start": "<string>",
        "period_end": "<string>"
      }
    ]
  }
  ```
</ResponseExample>


# Update API Key Budget Configuration
Source: https://docs.novita.ai/api-reference/budget-update-key-config

PUT https://api.novita.ai/openapi/v1/user/team/key-budget

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Sub-user account ID of the API Key owner.
</ParamField>

<ParamField type="string">
  Target API Key ID.
</ParamField>

<ParamField type="string">
  Budget type. Possible values: `Unlimited`, `One-time`, `Recurring`.
</ParamField>

<ParamField type="int64">
  Budget limit, in units of 1/10000 USD (e.g., 10000 = $1.00). Required when `budget_type` is not `Unlimited`. Minimum value is `10000` ($1.00).
</ParamField>

<ParamField type="string">
  Reset cycle. Required when `budget_type=Recurring`. Currently only `Monthly` is supported.
</ParamField>

## Response

Returns HTTP 200 with an empty response body on success.

<RequestExample>
  ```bash theme={"system"}
  curl --request PUT \
    --url https://api.novita.ai/openapi/v1/user/team/key-budget \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"member_id": "<string>", "key_id": "<string>", "budget_type": "Recurring", "budget_limit": 100000, "cycle": "Monthly"}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {}
  ```
</ResponseExample>


# Update Team Member Budget Configuration
Source: https://docs.novita.ai/api-reference/budget-update-member-config

PUT https://api.novita.ai/openapi/v1/user/team/member-budget

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The sub-user account ID of the target member whose budget configuration is being updated.
</ParamField>

<ParamField type="string">
  Budget type. Possible values: `Unlimited`, `One-time`, `Recurring`.
</ParamField>

<ParamField type="int64">
  Budget limit in units of 1/10000 USD (e.g., `10000` = $1.00). Minimum value is `10000` ($1.00). Required when `budget_type` is `One-time` or `Recurring`. Ignored when `budget_type` is `Unlimited`.
</ParamField>

<ParamField type="string">
  Budget reset cycle. Currently only `Monthly` is supported. Required when `budget_type` is `Recurring`.
</ParamField>

## Response

Returns HTTP 200 with an empty response body on success.

<RequestExample>
  ```bash theme={"system"}
  curl --request PUT \
    --url https://api.novita.ai/openapi/v1/user/team/member-budget \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"member_id": "<string>", "budget_type": "Recurring", "budget_limit": 100000, "cycle": "Monthly"}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {}
  ```
</ResponseExample>


# List Clusters
Source: https://docs.novita.ai/api-reference/gpu-instance-list-clusters

GET https://api.novita.ai/gpu-instance/openapi/v1/clusters

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseField name="data" type="object[]">
  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Cluster ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Cluster name.
    </ResponseField>

    <ResponseField name="availableGpuType" type="[string]">
      Supported GPU types in the cluster.
    </ResponseField>

    <ResponseField name="supportNetworkStorage" type="boolean">
      Whether the cluster supports creating cloud storage.
    </ResponseField>

    <ResponseField name="supportInstanceNetwork" type="boolean">
      Whether the cluster supports creating VPC networks.
    </ResponseField>
  </Expandable>
</ResponseField>


# Stop Job
Source: https://docs.novita.ai/api-reference/gpus-v2-break-job

api-reference/gpus-v2-spec/break-job.json POST /gpus/v2/jobs/{job_id}/stop



# Update Instance Billing Mode
Source: https://docs.novita.ai/api-reference/gpus-v2-change-billing-instance

api-reference/gpus-v2-spec/change-billing-instance.json PUT /gpus/v2/instances/{instance_id}/billing-mode



# Create Endpoint
Source: https://docs.novita.ai/api-reference/gpus-v2-create-endpoint

api-reference/gpus-v2-spec/create-endpoint.json POST /gpus/v2/endpoints



# Create Image Prewarm Task
Source: https://docs.novita.ai/api-reference/gpus-v2-create-image-prewarm

api-reference/gpus-v2-spec/create-image-prewarm.json POST /gpus/v2/prewarm-tasks



# Create Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-create-instance

api-reference/gpus-v2-spec/create-instance.json POST /gpus/v2/instances



# Create Template
Source: https://docs.novita.ai/api-reference/gpus-v2-create-template

api-reference/gpus-v2-spec/create-template.json POST /gpus/v2/templates



# Create Network
Source: https://docs.novita.ai/api-reference/gpus-v2-create-vpc

api-reference/gpus-v2-spec/create-vpc.json POST /gpus/v2/networks



# Delete Endpoint
Source: https://docs.novita.ai/api-reference/gpus-v2-delete-endpoint

api-reference/gpus-v2-spec/delete-endpoint.json DELETE /gpus/v2/endpoints/{endpoint_id}



# Delete Image
Source: https://docs.novita.ai/api-reference/gpus-v2-delete-image

api-reference/gpus-v2-spec/delete-image.json DELETE /gpus/v2/images/{image_id}



# Batch Delete Image Prewarm Tasks
Source: https://docs.novita.ai/api-reference/gpus-v2-delete-image-prewarm

api-reference/gpus-v2-spec/delete-image-prewarm.json DELETE /gpus/v2/prewarm-tasks



# Delete Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-delete-instance

api-reference/gpus-v2-spec/delete-instance.json DELETE /gpus/v2/instances/{instance_id}



# Delete Template
Source: https://docs.novita.ai/api-reference/gpus-v2-delete-template

api-reference/gpus-v2-spec/delete-template.json DELETE /gpus/v2/templates/{template_id}



# Delete Network
Source: https://docs.novita.ai/api-reference/gpus-v2-delete-vpc

api-reference/gpus-v2-spec/delete-vpc.json DELETE /gpus/v2/networks/{network_id}



# Expand Instance Rootfs
Source: https://docs.novita.ai/api-reference/gpus-v2-expand-rootfs

api-reference/gpus-v2-spec/expand-rootfs.json PUT /gpus/v2/instances/{instance_id}/rootfs-expand



# Get Endpoint
Source: https://docs.novita.ai/api-reference/gpus-v2-get-endpoint

api-reference/gpus-v2-spec/get-endpoint.json GET /gpus/v2/endpoints/{endpoint_id}



# Get Image Prewarm Task
Source: https://docs.novita.ai/api-reference/gpus-v2-get-image-prewarm

api-reference/gpus-v2-spec/get-image-prewarm.json GET /gpus/v2/prewarm-tasks/{task_id}



# Get Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-get-instance

api-reference/gpus-v2-spec/get-instance.json GET /gpus/v2/instances/{instance_id}



# Get Job
Source: https://docs.novita.ai/api-reference/gpus-v2-get-job

api-reference/gpus-v2-spec/get-job.json GET /gpus/v2/jobs/{job_id}



# Get Template
Source: https://docs.novita.ai/api-reference/gpus-v2-get-template

api-reference/gpus-v2-spec/get-template.json GET /gpus/v2/templates/{template_id}



# Get Network
Source: https://docs.novita.ai/api-reference/gpus-v2-get-vpc

api-reference/gpus-v2-spec/get-vpc.json GET /gpus/v2/networks/{network_id}



# List Endpoints
Source: https://docs.novita.ai/api-reference/gpus-v2-list-endpoint

api-reference/gpus-v2-spec/list-endpoint.json GET /gpus/v2/endpoints



# List Images
Source: https://docs.novita.ai/api-reference/gpus-v2-list-image

api-reference/gpus-v2-spec/list-image.json GET /gpus/v2/images



# List Image Prewarm Tasks
Source: https://docs.novita.ai/api-reference/gpus-v2-list-image-prewarm

api-reference/gpus-v2-spec/list-image-prewarm.json GET /gpus/v2/prewarm-tasks



# List Instances
Source: https://docs.novita.ai/api-reference/gpus-v2-list-instances

api-reference/gpus-v2-spec/list-instances.json GET /gpus/v2/instances



# List Jobs
Source: https://docs.novita.ai/api-reference/gpus-v2-list-jobs

api-reference/gpus-v2-spec/list-jobs.json GET /gpus/v2/jobs



# List Products
Source: https://docs.novita.ai/api-reference/gpus-v2-list-products

api-reference/gpus-v2-spec/list-products.json GET /gpus/v2/products



# List Regions
Source: https://docs.novita.ai/api-reference/gpus-v2-list-regions

api-reference/gpus-v2-spec/list-regions.json GET /gpus/v2/regions



# List Templates
Source: https://docs.novita.ai/api-reference/gpus-v2-list-templates

api-reference/gpus-v2-spec/list-templates.json GET /gpus/v2/templates



# List Networks
Source: https://docs.novita.ai/api-reference/gpus-v2-list-vpc

api-reference/gpus-v2-spec/list-vpc.json GET /gpus/v2/networks



# Migrate Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-migrate-instance

api-reference/gpus-v2-spec/migrate-instance.json PUT /gpus/v2/instances/{instance_id}/migrate



# Renew Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-renewal-instance

api-reference/gpus-v2-spec/renewal-instance.json PUT /gpus/v2/instances/{instance_id}/renew



# Restart Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-restart-instance

api-reference/gpus-v2-spec/restart-instance.json PUT /gpus/v2/instances/{instance_id}/restart



# Save Image
Source: https://docs.novita.ai/api-reference/gpus-v2-save-image

api-reference/gpus-v2-spec/save-image.json POST /gpus/v2/jobs/save-image



# Set Instance Auto Migrate
Source: https://docs.novita.ai/api-reference/gpus-v2-set-auto-migrate

api-reference/gpus-v2-spec/set-auto-migrate.json PUT /gpus/v2/instances/{instance_id}/auto-migrate



# Set Instance Auto Renew
Source: https://docs.novita.ai/api-reference/gpus-v2-set-autorenew

api-reference/gpus-v2-spec/set-autorenew.json PUT /gpus/v2/instances/{instance_id}/auto-renew



# Set Instance Web Terminal
Source: https://docs.novita.ai/api-reference/gpus-v2-set-web-terminal

api-reference/gpus-v2-spec/set-web-terminal.json PUT /gpus/v2/instances/{instance_id}/web-terminal



# Start Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-start-instance

api-reference/gpus-v2-spec/start-instance.json PUT /gpus/v2/instances/{instance_id}/start



# Stop Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-stop-instance

api-reference/gpus-v2-spec/stop-instance.json PUT /gpus/v2/instances/{instance_id}/stop



# Update Endpoint
Source: https://docs.novita.ai/api-reference/gpus-v2-update-endpoint

api-reference/gpus-v2-spec/update-endpoint.json PUT /gpus/v2/endpoints/{endpoint_id}



# Update Instance Ports
Source: https://docs.novita.ai/api-reference/gpus-v2-update-ports

api-reference/gpus-v2-spec/update-ports.json PUT /gpus/v2/instances/{instance_id}/ports



# Update Template
Source: https://docs.novita.ai/api-reference/gpus-v2-update-template

api-reference/gpus-v2-spec/update-template.json PUT /gpus/v2/templates/{template_id}



# Update Network
Source: https://docs.novita.ai/api-reference/gpus-v2-update-vpc

api-reference/gpus-v2-spec/update-vpc.json PUT /gpus/v2/networks/{network_id}



# Upgrade Instance
Source: https://docs.novita.ai/api-reference/gpus-v2-upgrade-instance

api-reference/gpus-v2-spec/upgrade-instance.json PUT /gpus/v2/instances/{instance_id}/upgrade



# Delete API Key IP Access Policy
Source: https://docs.novita.ai/api-reference/key-delete-ip-access-policy

DELETE https://api.novita.ai/openapi/v2/user/key/{stringId}/ip-access-policy

Clear the IP access policy of a single API key, restoring it to no source-IP restriction. After clearing, any source IP can use the key to invoke models.

## Permissions

| Role | Access |
| :- | :- |
| Owner / Admin | Can clear the IP access policy of any API key in the team. |
| Developer | Can clear only keys they own. |
| Basic | Cannot clear. |
| Billing | Cannot clear. |

The target API key must belong to the caller's team. When clearing a key that the caller has no permission for or that does not belong to the team, the endpoint does not reveal whether the target key exists and returns a permission error uniformly.

## Request Headers

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The stringId of the target API key.
</ParamField>

## Response

Returns an empty object on success.

<RequestExample>
  ```bash theme={"system"}
  curl --request DELETE \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/ip-access-policy \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {}
  ```
</ResponseExample>


# Reset API Key Model Access Policy
Source: https://docs.novita.ai/api-reference/key-delete-model-access-policy

DELETE https://api.novita.ai/openapi/v2/user/key/{stringId}/model-access-policy

Delete the model access policy of a single API key and reset it to the default policy (`mode=all_enabled`, access to all of the team's enabled models).

## Permissions

Only the team Owner / Admin can call this endpoint. Developer, Basic, and Billing members cannot reset the policy. The target API key must belong to the caller's team.

## Request Headers

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The stringId of the target API key.
</ParamField>

## Request Body

No request body.

## Response

Returns HTTP 200 with an empty object on success. Querying the key afterward returns the default policy.

<RequestExample>
  ```bash theme={"system"}
  curl --request DELETE \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/model-access-policy \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {}
  ```
</ResponseExample>


# Get API Key IP Access Policy
Source: https://docs.novita.ai/api-reference/key-get-ip-access-policy

GET https://api.novita.ai/openapi/v2/user/key/{stringId}/ip-access-policy

Retrieve the current IP access policy of a single API key, that is, the list of source IPs allowed to use the key. If the key has never been configured, an empty list is returned, meaning the source IP is not restricted.

<Info>
  The IP access policy applies to model invocation endpoints only. When the source IP of a request is not in the allowed list, the model call is rejected; management endpoints (including these IP access policy read/write endpoints, billing queries, Sandbox, etc.) are not restricted by source IP.
</Info>

## Permissions

| Role | Access |
| :- | :- |
| Owner / Admin | Can query the IP access policy of any API key in the team. |
| Developer | Can query only keys they own. |
| Basic | Can query only keys they own. |
| Billing | Can query the IP access policy of any API key in the team. |

The target API key must belong to the caller's team. When querying a key that the caller has no permission for or that does not belong to the team, the endpoint does not reveal whether the target key exists and returns a permission error uniformly.

## Request Headers

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The stringId of the target API key.
</ParamField>

## Response Parameters

<ResponseField name="stringId" type="string">
  The stringId of the API key.
</ResponseField>

<ResponseField name="policy" type="object">
  The IP access policy object.

  <Expandable title="properties">
    <ResponseField name="allowedIps" type="string[]">
      The list of allowed source IPs. Each entry is an IPv4 address or an IPv4 CIDR range. An empty list means the source IP is not restricted.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="policyStatus" type="string">
  Policy status. Value: `active`.
</ResponseField>

<ResponseField name="allowedIpCount" type="int">
  The number of allowed IP entries. `0` means the source IP is not restricted.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  The last update time, in ISO 8601 format. Empty string if never configured.
</ResponseField>

<ResponseField name="updatedBy" type="string">
  The user UUID of the last operator. Empty string if never configured.
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/ip-access-policy \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 (with allowed IPs configured) theme={"system"}
  {
    "stringId": "key_xxx",
    "policy": {
      "allowedIps": ["203.0.113.5", "198.51.100.0/24"]
    },
    "policyStatus": "active",
    "allowedIpCount": 2,
    "updatedAt": "2026-08-13T10:20:30Z",
    "updatedBy": "a3f1c9e2-5b7d-4e81-9c34-2f6a8d0b1e57"
  }
  ```

  ```json 200 (not configured, source IP not restricted) theme={"system"}
  {
    "stringId": "key_xxx",
    "policy": {
      "allowedIps": []
    },
    "policyStatus": "active",
    "allowedIpCount": 0,
    "updatedAt": "",
    "updatedBy": ""
  }
  ```
</ResponseExample>


# Get API Key Model Access Policy
Source: https://docs.novita.ai/api-reference/key-get-model-access-policy

GET https://api.novita.ai/openapi/v2/user/key/{stringId}/model-access-policy

Retrieve the current model access policy of a single API key. If the key has never been configured, the default policy (`mode=all_enabled`) is returned.

<Info>
  `allowedModels` / `excludedModels` may be returned as `null` or an empty array; callers should treat both as an empty array.
</Info>

## Permissions

| Role | Access |
| :- | :- |
| Owner / Admin | Can query the full policy of any API key in the team. |
| Developer | Can query only keys they own, and the response omits the `allowedModels` / `excludedModels` details. |

The target API key must belong to the caller's team.

## Request Headers

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The stringId of the target API key.
</ParamField>

## Response Parameters

<ResponseField name="stringId" type="string">
  The stringId of the API key.
</ResponseField>

<ResponseField name="policy" type="object">
  The model access policy object.

  <Expandable title="properties">
    <ResponseField name="schemaVersion" type="int32">
      Policy schema version. Currently `1`.
    </ResponseField>

    <ResponseField name="productScope" type="string">
      Product scope the policy applies to. Currently only `model_api`.
    </ResponseField>

    <ResponseField name="mode" type="string">
      Access mode. Possible values: `all_enabled` (can access all enabled models), `selected` (can access only the specified models).
    </ResponseField>

    <ResponseField name="allowedModels" type="object[]">
      Accessible models. Meaningful only when `mode=selected`; empty when `mode=all_enabled`. Not returned in detail for Developer queries.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Model type. Possible values: `llm`, `multimodal`.
        </ResponseField>

        <ResponseField name="id" type="string">
          Model identifier, for example `deepseek/deepseek-v3`.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="excludedModels" type="object[]">
      Excluded models. Meaningful only when `mode=all_enabled`; empty when `mode=selected`. Same structure as `allowedModels`. Not returned in detail for Developer queries.
    </ResponseField>

    <ResponseField name="source" type="object">
      Policy source, returned by the server and ignored on write.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Source type. Possible values: `default` (never configured, default policy returned), `private_policy` (policy embedded on this key).
        </ResponseField>

        <ResponseField name="policyId" type="string">
          Associated policy ID, returned only when applicable.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="policyStatus" type="string">
  Policy status. `active` means the policy is effective; `no_available_models` means no accessible model remains (for example, all selected models are offline).
</ResponseField>

<ResponseField name="allowedModelCount" type="int32">
  Number of accessible models.
</ResponseField>

<ResponseField name="excludedModelCount" type="int32">
  Number of excluded models.
</ResponseField>

<ResponseField name="updatedAt" type="int64">
  Last update time, as a Unix timestamp in seconds. `0` for the default policy.
</ResponseField>

<ResponseField name="updatedBy" type="string">
  Identifier of the user who last updated the policy. Empty string for the default policy.
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/model-access-policy \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 (selected) theme={"system"}
  {
    "stringId": "key_xxx",
    "policy": {
      "schemaVersion": 1,
      "productScope": "model_api",
      "mode": "selected",
      "allowedModels": [
        {
          "type": "llm",
          "id": "deepseek/deepseek-v3"
        }
      ],
      "excludedModels": [],
      "source": {
        "type": "private_policy"
      }
    },
    "policyStatus": "active",
    "allowedModelCount": 1,
    "excludedModelCount": 0,
    "updatedAt": 1783764000,
    "updatedBy": "user_uuid_xxx"
  }
  ```

  ```json 200 (default) theme={"system"}
  {
    "stringId": "key_xxx",
    "policy": {
      "schemaVersion": 1,
      "productScope": "model_api",
      "mode": "all_enabled",
      "allowedModels": [],
      "excludedModels": [],
      "source": {
        "type": "default"
      }
    },
    "policyStatus": "active",
    "allowedModelCount": 0,
    "excludedModelCount": 0,
    "updatedAt": 0,
    "updatedBy": ""
  }
  ```
</ResponseExample>


# List API Keys (with Model Access Summary)
Source: https://docs.novita.ai/api-reference/key-list-with-model-access

GET https://api.novita.ai/openapi/v2/user/key

List the API keys of the current team. When `includePolicySummary=true`, each key includes a `modelAccessPolicy` summary, which is convenient for showing model access status on a list page. On page initialization, call this endpoint to get the summaries first, then query a single key's full policy as needed.

## Permissions

Returned according to the platform's existing API key read permissions: Owner / Admin can list all keys in the team; Developer and Basic members see the keys they own, within their accessible scope.

## Request Headers

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="boolean">
  Whether to return the `modelAccessPolicy` summary for each key. Recommended to pass `true` on the frontend. When omitted or `false`, `modelAccessPolicy` may not be returned.
</ParamField>

## Response Parameters

<ResponseField name="keys" type="object[]">
  The list of API keys.

  <Expandable title="properties">
    <ResponseField name="name" type="string">
      API Key name.
    </ResponseField>

    <ResponseField name="id" type="string">
      Numeric ID of the API key.
    </ResponseField>

    <ResponseField name="stringId" type="string">
      The stringId of the API key, used as the path parameter for the model access policy endpoints.
    </ResponseField>

    <ResponseField name="maskedKey" type="string">
      The masked API key, for example `sk_xxx***`. The list endpoint does not return the plaintext key; the plaintext is shown only once at creation time.
    </ResponseField>

    <ResponseField name="memberId" type="string">
      The sub-user account ID that owns the API key.
    </ResponseField>

    <ResponseField name="uuid" type="string">
      The UUID of the user that owns the API key.
    </ResponseField>

    <ResponseField name="expireTime" type="string">
      Expiration time. `never` when the key never expires.
    </ResponseField>

    <ResponseField name="createTime" type="string">
      Creation time.
    </ResponseField>

    <ResponseField name="lastUsedAt" type="int64">
      Last used time, as a Unix timestamp in seconds. `0` if never used.
    </ResponseField>

    <ResponseField name="modelAccessPolicy" type="object">
      Model access summary, returned only when `includePolicySummary=true`.

      <Expandable title="properties">
        <ResponseField name="mode" type="string">
          Access mode. Possible values: `all_enabled`, `selected`.
        </ResponseField>

        <ResponseField name="allowedModelCount" type="int32">
          Number of accessible models.
        </ResponseField>

        <ResponseField name="excludedModelCount" type="int32">
          Number of excluded models.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v2/user/key?includePolicySummary=true' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "keys": [
      {
        "name": "default",
        "id": "123456",
        "stringId": "key_xxx",
        "maskedKey": "sk_xxx***",
        "memberId": "member_xxx",
        "uuid": "user_uuid_xxx",
        "expireTime": "never",
        "createTime": "2026-07-14 12:00:00",
        "lastUsedAt": 1783764000,
        "modelAccessPolicy": {
          "mode": "selected",
          "allowedModelCount": 1,
          "excludedModelCount": 0
        }
      }
    ]
  }
  ```
</ResponseExample>


# Set API Key IP Access Policy
Source: https://docs.novita.ai/api-reference/key-put-ip-access-policy

PUT https://api.novita.ai/openapi/v2/user/key/{stringId}/ip-access-policy

Set or update the IP access policy of a single API key. This is a full replacement: after submission, the key's allowed IPs are exactly the list you submit. To add a single IP, query the current list first, then submit it together with the new IP.

<Info>
  The IP access policy applies to model invocation endpoints only and does not affect these IP access policy read/write endpoints themselves. Even if the configured IPs do not include the current request source, you can still call this endpoint or the delete endpoint to change it back, so you cannot lock yourself out.
</Info>

## Permissions

| Role | Access |
| :- | :- |
| Owner / Admin | Can set the IP access policy of any API key in the team. |
| Developer | Can set only keys they own. |
| Basic | Cannot set. |
| Billing | Cannot set. |

The target API key must belong to the caller's team. When setting a key that the caller has no permission for or that does not belong to the team, the endpoint does not reveal whether the target key exists and returns a permission error uniformly.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The stringId of the target API key.
</ParamField>

## Request Body

<ParamField type="string[]">
  The list of allowed source IPs, up to 100 entries. Each entry is an IPv4 address (e.g. `203.0.113.5`) or an IPv4 CIDR range (e.g. `198.51.100.0/24`). An empty array means the source IP is not restricted.
</ParamField>

<Note>
  Write validation:

  * Each entry must be a valid IPv4 address or IPv4 CIDR range; IPv6, domain names, and wildcards are rejected.
  * `0.0.0.0/0` is rejected; use an empty array to express "not restricted".
  * More than 100 entries is rejected.
  * Duplicate entries are deduplicated by the server; the normalized result is authoritative.
  * If any entry fails validation the whole request fails with no partial write. Validation failures return HTTP 400, with `message` distinguishing the specific cause (invalid format / exceeds limit / all-zero range).
</Note>

## Response

The response has the same structure as <a href="/api-reference/key-get-ip-access-policy">Get API Key IP Access Policy</a>, returning the normalized latest policy object.

<RequestExample>
  ```bash theme={"system"}
  curl --request PUT \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/ip-access-policy \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{
      "allowedIps": ["203.0.113.5", "198.51.100.0/24"]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "stringId": "key_xxx",
    "policy": {
      "allowedIps": ["203.0.113.5", "198.51.100.0/24"]
    },
    "policyStatus": "active",
    "allowedIpCount": 2,
    "updatedAt": "2026-08-13T10:25:12Z",
    "updatedBy": "a3f1c9e2-5b7d-4e81-9c34-2f6a8d0b1e57"
  }
  ```
</ResponseExample>


# Set API Key Model Access Policy
Source: https://docs.novita.ai/api-reference/key-put-model-access-policy

PUT https://api.novita.ai/openapi/v2/user/key/{stringId}/model-access-policy

Set or update the model access policy of a single API key. The request body is the `policy` object itself.

## Permissions

Only the team Owner / Admin can call this endpoint. Developer, Basic, and Billing members cannot set the policy. The target API key must belong to the caller's team.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The stringId of the target API key.
</ParamField>

## Request Body

<ParamField type="int32">
  Policy schema version. Currently `1`.
</ParamField>

<ParamField type="string">
  Product scope the policy applies to. Only `model_api` is supported when provided.
</ParamField>

<ParamField type="string">
  Access mode. Possible values: `all_enabled` (can access all enabled models), `selected` (can access only the specified models).
</ParamField>

<ParamField type="object[]">
  Accessible models. Required when `mode=selected` and must contain at least one model; ignored when `mode=all_enabled`.

  <Expandable title="properties">
    <ParamField type="string">
      Model type. Possible values: `llm`, `multimodal`.
    </ParamField>

    <ParamField type="string">
      Model identifier, for example `deepseek/deepseek-v3`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Excluded models. Applies when `mode=all_enabled`; ignored when `mode=selected`. Same structure as `allowedModels`.
</ParamField>

<Note>
  Write constraints:

  * `mode` must be `all_enabled` or `selected`.
  * When `mode=selected`, `allowedModels` must contain at least one model.
  * Every model written must be within the team's enabled model range and currently available; requests containing offline, unavailable, or not-enabled models are rejected.
  * Model arrays that do not apply to the current mode are normalized to empty, and duplicate models are deduplicated. The response reflects the server-normalized object.
  * `source` is managed by the server and ignored on write.
</Note>

## Response

The response has the same structure as [Get API Key Model Access Policy](/api-reference/key-get-model-access-policy) and reflects the latest, normalized policy object.

<RequestExample>
  ```bash Selected models theme={"system"}
  curl --request PUT \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/model-access-policy \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{
      "schemaVersion": 1,
      "productScope": "model_api",
      "mode": "selected",
      "allowedModels": [
        { "type": "llm", "id": "deepseek/deepseek-v3" }
      ]
    }'
  ```

  ```bash All enabled models theme={"system"}
  curl --request PUT \
    --url https://api.novita.ai/openapi/v2/user/key/<stringId>/model-access-policy \
    --header 'Authorization: Bearer <API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{
      "schemaVersion": 1,
      "productScope": "model_api",
      "mode": "all_enabled"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "stringId": "key_xxx",
    "policy": {
      "schemaVersion": 1,
      "productScope": "model_api",
      "mode": "selected",
      "allowedModels": [
        {
          "type": "llm",
          "id": "deepseek/deepseek-v3"
        }
      ],
      "excludedModels": [],
      "source": {
        "type": "private_policy"
      }
    },
    "policyStatus": "active",
    "allowedModelCount": 1,
    "excludedModelCount": 0,
    "updatedAt": 1783764000,
    "updatedBy": "user_uuid_xxx"
  }
  ```
</ResponseExample>


# Exa Answer
Source: https://docs.novita.ai/api-reference/model-apis-exa-answer

POST https://api.novita.ai/v3/exa/answer

Generate an answer to a question using Exa search results, with optional citations and source text. This is a passthrough to Exa's Answer API, exposed through the Novita gateway with a platform route prefix.

To authenticate with your Novita API key, get one from the [Novita dashboard](https://novita.ai/settings/key-management). Base URL: `https://api.novita.ai`.

## Quickstart

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/answer' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "How tall is the Burj Khalifa?"
  }'
```

Running the quickstart above returns the following. The request returned 8 citations; three are shown here.

```json theme={"system"}
{
  "requestId": "50bd53d7599d1415761aaefea608f113",
  "answer": "The Burj Khalifa has a roof height of 828 meters (2,717 feet) [1][2][3]. When measured to the tip of its antenna, the total height is 829.8 meters (2,722 to 2,723 feet) [1][2][4].",
  "citations": [
    {
      "id": "https://en.wikipedia.org/wiki/Burj_Khalifa",
      "title": "Burj Khalifa - Wikipedia",
      "url": "https://en.wikipedia.org/wiki/Burj_Khalifa",
      "publishedDate": "2026-05-31T00:00:00.000Z"
    },
    {
      "id": "https://www.skyscrapercenter.com/building/burj-khalifa/3",
      "title": "Burj Khalifa - The Skyscraper Center",
      "url": "https://www.skyscrapercenter.com/building/burj-khalifa/3"
    },
    {
      "id": "https://www.britannica.com/topic/Burj-Khalifa",
      "title": "Burj Khalifa | Height, Architect, Top Floor, & Facts | Britannica",
      "url": "https://www.britannica.com/topic/Burj-Khalifa"
    }
  ]
}
```

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer YOUR_API_KEY`.
</ParamField>

## Request Body

<ParamField type="string">
  Natural-language question or instruction. Minimum length is 1.
</ParamField>

<ParamField type="boolean">
  Whether to return source page text. Default is `false`.
</ParamField>

<ParamField type="object">
  JSON Schema Draft 7 object for structured answer output.

  <Expandable title="outputSchema properties">
    <ParamField type="string">
      JSON Schema root type, commonly `object`.
    </ParamField>

    <ParamField type="object">
      Field definitions for the structured answer.
    </ParamField>

    <ParamField type="string[]">
      Required property names.
    </ParamField>

    <ParamField type="string">
      Description of the structured output to guide generation.
    </ParamField>

    <ParamField type="boolean | object">
      Whether undeclared fields are allowed, or a schema for additional fields.
    </ParamField>
  </Expandable>
</ParamField>

## Examples

### Basic answer

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/answer' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "What year was the Eiffel Tower completed?",
    "text": true
  }'
```

### Structured answer

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/answer' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "How tall is Mount Everest and where is it located?",
    "outputSchema": {
      "type": "object",
      "properties": {
        "heightMeters": {"type": "number"},
        "country": {"type": "string"}
      },
      "required": ["heightMeters"]
    }
  }'
```

### Answer with source text

Set `text: true` to return the full source text alongside each citation, useful when you want to display or re-verify the evidence.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/answer' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "What are the main causes of coral bleaching?",
    "text": true
  }'
```

### List-shaped structured answer

An `outputSchema` can return an array when you expect several items rather than a single fact.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/answer' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "Which countries border Switzerland?",
    "outputSchema": {
      "type": "object",
      "properties": {
        "countries": {
          "type": "array",
          "items": {"type": "string"}
        }
      },
      "required": ["countries"]
    }
  }'
```

## Response

<ResponseField name="requestId" type="string">
  Unique request identifier.
</ResponseField>

<ResponseField name="answer" type="string | object">
  Generated answer. It can be structured according to the `outputSchema` provided in the request.
</ResponseField>

<ResponseField name="citations" type="object[]">
  Search results used to generate the answer.

  <Expandable title="citation properties">
    <ResponseField name="title" type="string">
      Source title.
    </ResponseField>

    <ResponseField name="url" type="string">
      Source URL.
    </ResponseField>

    <ResponseField name="publishedDate" type="string">
      Published date when available.
    </ResponseField>

    <ResponseField name="author" type="string">
      Author when available.
    </ResponseField>

    <ResponseField name="id" type="string">
      Exa document ID.
    </ResponseField>

    <ResponseField name="text" type="string">
      Source text when requested.
    </ResponseField>
  </Expandable>
</ResponseField>

## Errors

Errors are returned as a JSON envelope with `code`, `reason`, `message`, and `metadata`. The platform validates and authenticates the request before forwarding it to Exa, so some errors originate at the platform and others are surfaced from upstream.

| Status | `reason` | Meaning |
| - | - | - |
| `400` | `MISSING_API_KEY` | The `Authorization` header is absent. |
| `400` | `INVALID_REQUEST_BODY` | The body failed validation, such as a missing or malformed `query`. |
| `403` | `INVALID_API_KEY` | The API key is invalid or not authorized. |
| `404` | `PATH_NOT_FOUND` | The route does not exist. |
| `429` | `RATE_LIMIT_EXCEEDED` | Too many requests. Slow down and retry with backoff. |
| `500` | `TASK_FAILED` | Internal or upstream provider failure. Transient failures are safe to retry. |
| `503` | `SERVICE_UNAVAILABLE` | The service is temporarily overloaded or down. Retry with backoff. |

For validation failures, `message` is a JSON-encoded string carrying the detailed `error` and `tag`. The examples below show that inner detail decoded for readability.

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "requestId": "string",
    "answer": "string",
    "citations": [
      {
        "id": "string",
        "title": "string",
        "url": "string",
        "publishedDate": "string",
        "author": "string",
        "text": "string"
      }
    ]
  }
  ```

  ```json 400 theme={"system"}
  {
    "code": 400,
    "reason": "INVALID_REQUEST_BODY",
    "message": "Invalid request body | Validation error: expected string, received undefined at \"query\"",
    "metadata": {
      "trace_id": "0748a32faccde3d924752336fd5c1a31"
    }
  }
  ```

  ```json 403 theme={"system"}
  {
    "code": 403,
    "reason": "INVALID_API_KEY",
    "message": "invalid api-key",
    "metadata": {}
  }
  ```

  ```json 404 theme={"system"}
  {
    "code": 404,
    "reason": "PATH_NOT_FOUND",
    "message": "path not found for model",
    "metadata": {
      "trace_id": "3c3433f0cb475a04f9a4969b8c6ed903"
    }
  }
  ```

  ```json 429 theme={"system"}
  {
    "code": 429,
    "reason": "RATE_LIMIT_EXCEEDED",
    "message": "rate limit exceeded, please retry later",
    "metadata": {
      "trace_id": "5f2a9c7e41b8d0a36e9f4c2b7d81a0e5"
    }
  }
  ```

  ```json 500 theme={"system"}
  {
    "code": 500,
    "reason": "TASK_FAILED",
    "message": "upstream provider temporarily unavailable, please retry",
    "metadata": {
      "trace_id": "8846379c692d7218173460afe95640f1"
    }
  }
  ```

  ```json 503 theme={"system"}
  {
    "code": 503,
    "reason": "SERVICE_UNAVAILABLE",
    "message": "service temporarily unavailable",
    "metadata": {
      "trace_id": "b7e3f1a9d24c05e8f63a1b9c7e05d2f4"
    }
  }
  ```
</ResponseExample>

## Notes

* All request bodies are JSON.
* Extra Exa parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Exa Answer API reference](https://exa.ai/docs/reference/answer).


# Exa Contents
Source: https://docs.novita.ai/api-reference/model-apis-exa-contents

POST https://api.novita.ai/v3/exa/contents

Retrieve page content, summaries, metadata, and fetch statuses for URLs or Exa document IDs. This is a passthrough to Exa's Get Contents API, exposed through the Novita gateway with a platform route prefix.

To authenticate with your Novita API key, get one from the [Novita dashboard](https://novita.ai/settings/key-management). Base URL: `https://api.novita.ai`.

## Quickstart

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/contents' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://arxiv.org/abs/1706.03762"],
    "text": true
  }'
```

Running the quickstart above returns:

```json theme={"system"}
{
  "requestId": "0a6bd28991288444b8748646c5bd4bd9",
  "results": [
    {
      "id": "https://arxiv.org/abs/1706.03762",
      "title": "Attention Is All You Need",
      "url": "https://arxiv.org/abs/1706.03762",
      "publishedDate": "2025-08-23T00:00:00.000Z",
      "author": "Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, et al.",
      "text": "# Attention Is All You Need\n\n2025. Preprint. 6,569 citations.\n\n## Authors\n\n- Ashish Vaswani (Google (United States)): h-index 28; 10,049 citations…",
      "entities": [
        {
          "id": "https://exa.ai/library/publication/985dxcclwqj",
          "type": "publication",
          "version": 1,
          "properties": {
            "title": "Attention Is All You Need",
            "year": 2025,
            "date": "2025-08-23",
            "type": "preprint",
            "language": "en",
            "citationCount": 6569,
            "authors": [
              {"name": "Ashish Vaswani", "id": null}
            ]
          }
        }
      ]
    }
  ],
  "statuses": [
    {
      "id": "https://arxiv.org/abs/1706.03762",
      "status": "success",
      "source": "cached"
    }
  ],
  "searchTime": 8.12
}
```

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer YOUR_API_KEY`.
</ParamField>

## Request Body

<ParamField type="string[]">
  URLs to retrieve. This field is also backwards compatible with values previously supplied through `ids`. Range is `1` to `100` entries; each URL may be up to 2048 characters. Provide either `urls` or `ids`, but not both — supplying both returns a `400`.
</ParamField>

<ParamField type="string[]">
  Exa document IDs to retrieve. Range is `1` to `100` entries; each ID may be up to 2048 characters. Provide either `urls` or `ids`, but not both — supplying both returns a `400`.
</ParamField>

<ParamField type="string">
  Enterprise compliance mode. Only `hipaa` is supported.
</ParamField>

<ParamField type="boolean | object">
  Text extraction options. Default is `false`.

  <Expandable title="text properties">
    <ParamField type="integer">
      Maximum number of text characters to return. Range is `1` to `10000`.
    </ParamField>

    <ParamField type="boolean">
      Include lightweight HTML tags instead of plain text.
    </ParamField>

    <ParamField type="string">
      Requested text verbosity. One of `compact`, `standard`, or `full`.
    </ParamField>

    <ParamField type="string[]">
      Semantic page sections to keep. Best-effort classification. Each entry is one of `header`, `navigation`, `banner`, `body`, `sidebar`, `footer`, or `metadata`.
    </ParamField>

    <ParamField type="string[]">
      Semantic page sections to drop. Best-effort classification. Each entry is one of `header`, `navigation`, `banner`, `body`, `sidebar`, `footer`, or `metadata`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean | object">
  Highlight extraction options. Default is `false`.

  <Expandable title="highlights properties">
    <ParamField type="string">
      Custom query used to select highlights.
    </ParamField>

    <ParamField type="integer">
      Maximum characters to return for highlights. Range is `1` to `10000`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean | object">
  Summary generation options. Pass `true` for a default summary, or an object to configure it.

  <Expandable title="summary properties">
    <ParamField type="string">
      Custom instruction for summary generation.
    </ParamField>

    <ParamField type="object">
      Optional JSON schema for structured summary output.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  Extra outputs to return from each page.

  <Expandable title="extras properties">
    <ParamField type="integer">
      Number of links to return. Range is `0` to `1000`.
    </ParamField>

    <ParamField type="integer">
      Number of image links to return. Range is `0` to `1000`.
    </ParamField>

    <ParamField type="integer">
      Number of rich image links to return. Range is `0` to `1000`.
    </ParamField>

    <ParamField type="integer">
      Number of rich links to return. Range is `0` to `1000`.
    </ParamField>

    <ParamField type="integer">
      Number of code blocks to return. Range is `0` to `1000`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="integer">
  Live crawl timeout in milliseconds. Range is greater than `0` and up to `90000`; default is `10000`.
</ParamField>

<ParamField type="integer">
  Maximum cache age in hours. Use `0` for fresh content and `-1` to always use cache. Range is `-1` to `720`.
</ParamField>

<ParamField type="integer">
  Number of subpages to crawl. Range is `0` to `100`; default is `0`.
</ParamField>

<ParamField type="string | string[]">
  Term or terms used to find targeted subpages. String values are limited to 100 characters.
</ParamField>

## Examples

### Extract text and highlights

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/contents' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching"],
    "text": {
      "maxCharacters": 4000
    },
    "highlights": {
      "query": "cache invalidation"
    }
  }'
```

### Summarize a page

Use `summary` with a query to get a focused summary instead of raw text.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/contents' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://en.wikipedia.org/wiki/CRISPR"],
    "summary": {
      "query": "What is this technology used for?"
    }
  }'
```

### Include linked subpages

Use `subpages` with `subpageTarget` to also pull related pages the URL links to.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/contents' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://docs.astro.build"],
    "text": true,
    "subpages": 3,
    "subpageTarget": "installation"
  }'
```

### Force fresh content

Set `maxAgeHours` to `0` to bypass the cache and live-crawl the page.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/contents' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://www.githubstatus.com"],
    "text": true,
    "maxAgeHours": 0
  }'
```

## Response

<ResponseField name="requestId" type="string">
  Unique request identifier.
</ResponseField>

<ResponseField name="results" type="object[]">
  Content results.

  <Expandable title="result properties">
    <ResponseField name="title" type="string">
      Page title.
    </ResponseField>

    <ResponseField name="url" type="string">
      Page URL.
    </ResponseField>

    <ResponseField name="publishedDate" type="string">
      Published date when available.
    </ResponseField>

    <ResponseField name="author" type="string">
      Author when available.
    </ResponseField>

    <ResponseField name="id" type="string">
      Exa document ID.
    </ResponseField>

    <ResponseField name="image" type="string">
      Associated image URL.
    </ResponseField>

    <ResponseField name="favicon" type="string">
      Site favicon URL.
    </ResponseField>

    <ResponseField name="text" type="string">
      Extracted text when requested.
    </ResponseField>

    <ResponseField name="highlights" type="string[]">
      Highlight snippets when requested.
    </ResponseField>

    <ResponseField name="summary" type="string">
      Generated summary when requested.
    </ResponseField>

    <ResponseField name="subpages" type="object[]">
      Subpage results when requested.

      <Expandable title="subpage properties">
        <ResponseField name="title" type="string">
          Subpage title.
        </ResponseField>

        <ResponseField name="url" type="string">
          Subpage URL.
        </ResponseField>

        <ResponseField name="publishedDate" type="string">
          Published date when available.
        </ResponseField>

        <ResponseField name="author" type="string">
          Author when available.
        </ResponseField>

        <ResponseField name="id" type="string">
          Exa document ID.
        </ResponseField>

        <ResponseField name="image" type="string">
          Associated image URL.
        </ResponseField>

        <ResponseField name="favicon" type="string">
          Site favicon URL.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="extras" type="object">
      Container for extra page data. Each child field is present only when its matching `extras` count is requested, and may be an empty array when the page has no such data.

      <Expandable title="extras properties">
        <ResponseField name="links" type="string[]">
          Extracted links when `extras.links` is requested.
        </ResponseField>

        <ResponseField name="imageLinks" type="string[]">
          Image links when `extras.imageLinks` is requested.
        </ResponseField>

        <ResponseField name="richImageLinks" type="object[]">
          Rich image links when `extras.richImageLinks` is requested.
        </ResponseField>

        <ResponseField name="richLinks" type="object[]">
          Rich links when `extras.richLinks` is requested.
        </ResponseField>

        <ResponseField name="codeBlocks" type="object[]">
          Code blocks when `extras.codeBlocks` is requested.

          <Expandable title="codeBlock properties">
            <ResponseField name="text" type="string">
              Code block content.
            </ResponseField>

            <ResponseField name="source" type="string">
              Where the block was extracted from, such as `text`.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="entities" type="object[]">
      Structured entities extracted from the page. Each entity is a `company`, `person`, or `publication`. It may be absent or empty depending on the page.

      <Expandable title="entity properties">
        <ResponseField name="id" type="string">
          Stable entity identifier.
        </ResponseField>

        <ResponseField name="type" type="string">
          Entity discriminator. One of `company`, `person`, or `publication`.
        </ResponseField>

        <ResponseField name="version" type="integer">
          Entity schema version.
        </ResponseField>

        <ResponseField name="properties" type="object">
          Type-specific fields. The shape depends on `type`.

          <Expandable title="company properties">
            <ResponseField name="name" type="string">
              Company name.
            </ResponseField>

            <ResponseField name="foundedYear" type="integer">
              Year the company was founded.
            </ResponseField>

            <ResponseField name="description" type="string">
              Short company description.
            </ResponseField>

            <ResponseField name="workforce" type="object">
              Workforce data.

              <Expandable title="workforce properties">
                <ResponseField name="total" type="number">
                  Total headcount.
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="headquarters" type="object">
              Headquarters location.

              <Expandable title="headquarters properties">
                <ResponseField name="address" type="string">
                  Street address.
                </ResponseField>

                <ResponseField name="city" type="string">
                  City.
                </ResponseField>

                <ResponseField name="postalCode" type="string">
                  Postal code.
                </ResponseField>

                <ResponseField name="country" type="string">
                  Country.
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="financials" type="object">
              Financial data.

              <Expandable title="financials properties">
                <ResponseField name="revenueAnnual" type="number">
                  Annual revenue.
                </ResponseField>

                <ResponseField name="fundingTotal" type="number">
                  Total funding raised.
                </ResponseField>

                <ResponseField name="fundingLatestRound" type="object">
                  Most recent funding round.

                  <Expandable title="fundingLatestRound properties">
                    <ResponseField name="name" type="string">
                      Round name, such as `Series C`.
                    </ResponseField>

                    <ResponseField name="date" type="string">
                      Date of the round.
                    </ResponseField>

                    <ResponseField name="amount" type="number">
                      Amount raised in the round.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="webTraffic" type="object">
              Web traffic data.

              <Expandable title="webTraffic properties">
                <ResponseField name="visitsMonthly" type="number">
                  Monthly visits.
                </ResponseField>

                <ResponseField name="countryRank" type="integer">
                  Rank within the country.
                </ResponseField>

                <ResponseField name="avgDurationSeconds" type="number">
                  Average visit duration in seconds.
                </ResponseField>

                <ResponseField name="history" type="object[]">
                  Historical traffic data points.

                  <Expandable title="history item properties">
                    <ResponseField name="value" type="number">
                      Traffic value for the period.
                    </ResponseField>

                    <ResponseField name="dateFrom" type="string">
                      Period start, formatted as `YYYY-MM`.
                    </ResponseField>

                    <ResponseField name="dateTo" type="string">
                      Period end, formatted as `YYYY-MM`.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="research" type="object">
              Research output.

              <Expandable title="research properties">
                <ResponseField name="worksCount" type="integer">
                  Number of published works.
                </ResponseField>

                <ResponseField name="citationCount" type="integer">
                  Total citations.
                </ResponseField>

                <ResponseField name="areas" type="string[]">
                  Research areas.
                </ResponseField>

                <ResponseField name="notableWorks" type="object[]">
                  Notable works.

                  <Expandable title="notableWork item properties">
                    <ResponseField name="title" type="string">
                      Work title.
                    </ResponseField>

                    <ResponseField name="year" type="integer">
                      Publication year.
                    </ResponseField>

                    <ResponseField name="venue" type="string">
                      Publication venue.
                    </ResponseField>

                    <ResponseField name="citationCount" type="integer">
                      Number of citations.
                    </ResponseField>

                    <ResponseField name="doi" type="string">
                      Digital Object Identifier.
                    </ResponseField>

                    <ResponseField name="id" type="string">
                      Work identifier.
                    </ResponseField>

                    <ResponseField name="type" type="string">
                      Work type. One of `article`, `book`, `book-chapter`, `dataset`, `dissertation`, `preprint`, `report`, or `review`.
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="topResearchers" type="object[]">
                  Leading researchers associated with the company.

                  <Expandable title="topResearcher item properties">
                    <ResponseField name="person" type="object">
                      The researcher.

                      <Expandable title="person properties">
                        <ResponseField name="name" type="string">
                          Researcher name.
                        </ResponseField>

                        <ResponseField name="id" type="string">
                          Researcher identifier.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>

                    <ResponseField name="worksCount" type="integer">
                      Number of published works.
                    </ResponseField>

                    <ResponseField name="citationCount" type="integer">
                      Total citations.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>

          <Expandable title="person properties">
            <ResponseField name="name" type="string">
              Full name.
            </ResponseField>

            <ResponseField name="firstName" type="string">
              First name.
            </ResponseField>

            <ResponseField name="lastName" type="string">
              Last name.
            </ResponseField>

            <ResponseField name="location" type="string">
              Location.
            </ResponseField>

            <ResponseField name="workHistory" type="object[]">
              Work history entries.

              <Expandable title="workHistory item properties">
                <ResponseField name="title" type="string">
                  Job title.
                </ResponseField>

                <ResponseField name="location" type="string">
                  Location.
                </ResponseField>

                <ResponseField name="dates" type="object">
                  Date range.

                  <Expandable title="dates properties">
                    <ResponseField name="from" type="string">
                      Start date.
                    </ResponseField>

                    <ResponseField name="to" type="string">
                      End date.
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="company" type="object">
                  Employer.

                  <Expandable title="company properties">
                    <ResponseField name="id" type="string">
                      Company identifier.
                    </ResponseField>

                    <ResponseField name="name" type="string">
                      Company name.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="educationHistory" type="object[]">
              Education entries.

              <Expandable title="educationHistory item properties">
                <ResponseField name="degree" type="string">
                  Degree earned.
                </ResponseField>

                <ResponseField name="dates" type="object">
                  Date range.

                  <Expandable title="dates properties">
                    <ResponseField name="from" type="string">
                      Start date.
                    </ResponseField>

                    <ResponseField name="to" type="string">
                      End date.
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="institution" type="object">
                  Institution.

                  <Expandable title="institution properties">
                    <ResponseField name="id" type="string">
                      Institution identifier.
                    </ResponseField>

                    <ResponseField name="name" type="string">
                      Institution name.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="research" type="object">
              Research output.

              <Expandable title="research properties">
                <ResponseField name="worksCount" type="integer">
                  Number of published works.
                </ResponseField>

                <ResponseField name="citationCount" type="integer">
                  Total citations.
                </ResponseField>

                <ResponseField name="hIndex" type="integer">
                  h-index.
                </ResponseField>

                <ResponseField name="firstPublicationYear" type="integer">
                  Year of first publication.
                </ResponseField>

                <ResponseField name="latestPublicationYear" type="integer">
                  Year of most recent publication.
                </ResponseField>

                <ResponseField name="areas" type="string[]">
                  Research areas.
                </ResponseField>

                <ResponseField name="notableWorks" type="object[]">
                  Notable works.

                  <Expandable title="notableWork item properties">
                    <ResponseField name="title" type="string">
                      Work title.
                    </ResponseField>

                    <ResponseField name="year" type="integer">
                      Publication year.
                    </ResponseField>

                    <ResponseField name="venue" type="string">
                      Publication venue.
                    </ResponseField>

                    <ResponseField name="citationCount" type="integer">
                      Number of citations.
                    </ResponseField>

                    <ResponseField name="doi" type="string">
                      Digital Object Identifier.
                    </ResponseField>

                    <ResponseField name="id" type="string">
                      Work identifier.
                    </ResponseField>

                    <ResponseField name="type" type="string">
                      Work type. One of `article`, `book`, `book-chapter`, `dataset`, `dissertation`, `preprint`, `report`, or `review`.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>

          <Expandable title="publication properties">
            <ResponseField name="title" type="string">
              Publication title.
            </ResponseField>

            <ResponseField name="year" type="integer">
              Publication year.
            </ResponseField>

            <ResponseField name="date" type="string">
              Publication date.
            </ResponseField>

            <ResponseField name="type" type="string">
              Publication type. One of `article`, `book`, `book-chapter`, `dataset`, `dissertation`, `preprint`, `report`, or `review`.
            </ResponseField>

            <ResponseField name="language" type="string">
              Language.
            </ResponseField>

            <ResponseField name="citationCount" type="integer">
              Number of citations.
            </ResponseField>

            <ResponseField name="authors" type="object[]">
              Authors.

              <Expandable title="author properties">
                <ResponseField name="name" type="string">
                  Author name.
                </ResponseField>

                <ResponseField name="id" type="string">
                  Author identifier.
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="referenceCount" type="integer">
              Number of references.
            </ResponseField>

            <ResponseField name="abstract" type="string">
              Abstract text.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="statuses" type="object[]">
  Fetch status for each requested URL or document ID.

  <Expandable title="status properties">
    <ResponseField name="id" type="string">
      Requested URL or document ID.
    </ResponseField>

    <ResponseField name="status" type="string">
      Fetch status, such as `success` or `error`.
    </ResponseField>

    <ResponseField name="source" type="string">
      Content source, such as `cached` or `crawled`.
    </ResponseField>

    <ResponseField name="error" type="object">
      Error details when retrieval fails.

      <Expandable title="error properties">
        <ResponseField name="tag" type="string">
          Machine-readable error tag, such as `CRAWL_NOT_FOUND`, `CRAWL_TIMEOUT`, or `SOURCE_NOT_AVAILABLE`.
        </ResponseField>

        <ResponseField name="httpStatusCode" type="integer">
          HTTP status code encountered while fetching the source, such as `404`.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="searchTime" type="number">
  Time taken to serve the request, in milliseconds. Requests served from Exa's cache (`source: "cached"`) return in a few milliseconds, while pages that must be crawled live (`source: "crawled"`) typically take on the order of one to several seconds.
</ResponseField>

## Errors

Errors are returned as a JSON envelope with `code`, `reason`, `message`, and `metadata`. The platform validates and authenticates the request before forwarding it to Exa, so some errors originate at the platform and others are surfaced from upstream.

| Status | `reason` | Meaning |
| - | - | - |
| `400` | `MISSING_API_KEY` | The `Authorization` header is absent. |
| `400` | `INVALID_REQUEST_BODY` | The body failed validation, such as omitting both `urls` and `ids`. |
| `403` | `INVALID_API_KEY` | The API key is invalid or not authorized. |
| `404` | `PATH_NOT_FOUND` | The route does not exist. |
| `429` | `RATE_LIMIT_EXCEEDED` | Too many requests. Slow down and retry with backoff. |
| `500` | `TASK_FAILED` | Internal or upstream provider failure. Transient failures are safe to retry. |
| `503` | `SERVICE_UNAVAILABLE` | The service is temporarily overloaded or down. Retry with backoff. |

For validation failures, `message` is a JSON-encoded string carrying the detailed `error` and `tag`. The examples below show that inner detail decoded for readability.

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "requestId": "string",
    "results": [
      {
        "id": "string",
        "title": "string",
        "url": "string",
        "publishedDate": "string",
        "author": "string",
        "image": "string",
        "favicon": "string",
        "text": "string",
        "highlights": ["string"],
        "summary": "string",
        "subpages": [
          {
            "title": "string",
            "url": "string",
            "publishedDate": "string",
            "author": "string",
            "id": "string",
            "image": "string",
            "favicon": "string"
          }
        ],
        "extras": {
          "links": ["string"],
          "imageLinks": ["string"],
          "richImageLinks": [{}],
          "richLinks": [{}],
          "codeBlocks": [{"text": "string", "source": "string"}]
        },
        "entities": [
          {
            "id": "string",
            "type": "string",
            "version": 0,
            "properties": {}
          }
        ]
      }
    ],
    "statuses": [
      {
        "id": "string",
        "status": "string",
        "source": "string",
        "error": {
          "tag": "string",
          "httpStatusCode": 0
        }
      }
    ],
    "searchTime": 0
  }
  ```

  ```json 400 theme={"system"}
  {
    "code": 400,
    "reason": "INVALID_REQUEST_BODY",
    "message": "Invalid request body | Validation error: must provide either ids or urls",
    "metadata": {
      "trace_id": "a4d532f86200dc7bb0df7fa6023860ca"
    }
  }
  ```

  ```json 403 theme={"system"}
  {
    "code": 403,
    "reason": "INVALID_API_KEY",
    "message": "invalid api-key",
    "metadata": {}
  }
  ```

  ```json 404 theme={"system"}
  {
    "code": 404,
    "reason": "PATH_NOT_FOUND",
    "message": "path not found for model",
    "metadata": {
      "trace_id": "3c3433f0cb475a04f9a4969b8c6ed903"
    }
  }
  ```

  ```json 429 theme={"system"}
  {
    "code": 429,
    "reason": "RATE_LIMIT_EXCEEDED",
    "message": "rate limit exceeded, please retry later",
    "metadata": {
      "trace_id": "5f2a9c7e41b8d0a36e9f4c2b7d81a0e5"
    }
  }
  ```

  ```json 500 theme={"system"}
  {
    "code": 500,
    "reason": "TASK_FAILED",
    "message": "upstream provider temporarily unavailable, please retry",
    "metadata": {
      "trace_id": "8846379c692d7218173460afe95640f1"
    }
  }
  ```

  ```json 503 theme={"system"}
  {
    "code": 503,
    "reason": "SERVICE_UNAVAILABLE",
    "message": "service temporarily unavailable",
    "metadata": {
      "trace_id": "b7e3f1a9d24c05e8f63a1b9c7e05d2f4"
    }
  }
  ```
</ResponseExample>

## Notes

* All request bodies are JSON.
* Extra Exa parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Exa Contents API reference](https://exa.ai/docs/reference/get-contents).


# Exa Search
Source: https://docs.novita.ai/api-reference/model-apis-exa-search

POST https://api.novita.ai/v3/exa/search

Search the web and optionally extract content from returned results. This is a passthrough to Exa's Search API, exposed through the Novita gateway with a platform route prefix.

To authenticate with your Novita API key, get one from the [Novita dashboard](https://novita.ai/settings/key-management). Base URL: `https://api.novita.ai`.

## Quickstart

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/search' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "what makes a good API design",
    "contents": {"highlights": true}
  }'
```

Running the quickstart above returns the following at the time of publishing. The default request returns up to 10 results; two are shown here.

```json theme={"system"}
{
  "requestId": "de41749e9680ea844f760e0ffacc0246",
  "results": [
    {
      "id": "https://docs.cloud.google.com/apis/design",
      "title": "API design guide  |  Cloud API Design Guide  |  Google Cloud Documentation",
      "url": "https://docs.cloud.google.com/apis/design",
      "highlights": [
        "This is a general design guide for networked APIs. It has been used inside Google since 2014 and is the guide that Google follows when designing Cloud APIs and other Google APIs. This design guide is shared here to inform outside developers and to make it…"
      ],
      "image": "https://docs.cloud.google.com/_static/cloud/images/social-icon-google-cloud-1200-630.png",
      "favicon": "https://www.gstatic.com/devrel-devsite/prod/v544324a32f4e1e28baf5bd35dca83b6157fef32c5ffbe146b7ca395602e11426/clouddocs/images/favicons/onecloud/favicon.ico"
    },
    {
      "id": "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design",
      "title": "Web API Design Best Practices - Azure Architecture Center | Microsoft Learn",
      "url": "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design",
      "author": "claytonsiemens77",
      "highlights": [
        "A RESTful web API implementation is a web API that employs Representational State Transfer (REST) architectural principles to achieve a stateless, loosely coupled interface between a client and service. A web API that is RESTful supports the standard HTTP…"
      ],
      "image": "https://learn.microsoft.com/en-us/media/open-graph-image.png"
    }
  ],
  "searchTime": 936.2
}
```

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer YOUR_API_KEY`.
</ParamField>

## Request Body

<ParamField type="string">
  Search query. Minimum length is 1.
</ParamField>

<ParamField type="string[]">
  Restrict results to these domains. Maximum 1200 entries.
</ParamField>

<ParamField type="string[]">
  Exclude results from these domains. Maximum 1200 entries.
</ParamField>

<ParamField type="string">
  Include links published after this ISO 8601 date-time.
</ParamField>

<ParamField type="string">
  Include links published before this ISO 8601 date-time.
</ParamField>

<ParamField type="integer">
  Number of results to return. Default is `10`; supported public range is `1` to `100`.
</ParamField>

<ParamField type="boolean">
  Enable content moderation to filter unsafe content from search results. Default is `false`.
</ParamField>

<ParamField type="object">
  Content extraction options for search results.

  <Expandable title="contents properties">
    <ParamField type="boolean | object">
      Return page text, or an object to configure text extraction.

      <Expandable title="text properties">
        <ParamField type="integer">
          Maximum number of text characters to return. Range is `1` to `10000`.
        </ParamField>

        <ParamField type="boolean">
          Include lightweight HTML tags instead of plain text.
        </ParamField>

        <ParamField type="string">
          Requested text verbosity. One of `compact`, `standard`, or `full`.
        </ParamField>

        <ParamField type="string[]">
          Semantic page sections to keep. Best-effort classification. Each entry is one of `header`, `navigation`, `banner`, `body`, `sidebar`, `footer`, or `metadata`.
        </ParamField>

        <ParamField type="string[]">
          Semantic page sections to drop. Best-effort classification. Each entry is one of `header`, `navigation`, `banner`, `body`, `sidebar`, `footer`, or `metadata`.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="boolean | object">
      Return selected highlight snippets, or an object to configure highlight extraction.

      <Expandable title="highlights properties">
        <ParamField type="string">
          Custom query used to select highlights.
        </ParamField>

        <ParamField type="integer">
          Maximum characters to return for highlights. Range is `1` to `10000`.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="boolean | object">
      Generate a summary of each result. Pass `true` for a default summary, or an object to configure it.

      <Expandable title="summary properties">
        <ParamField type="string">
          Custom instruction for summary generation.
        </ParamField>

        <ParamField type="object">
          Optional JSON schema for structured summary output.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object">
      Return extra page data such as links, image links, rich links, or code blocks.

      <Expandable title="extras properties">
        <ParamField type="integer">
          Number of links to return. Range is `0` to `1000`.
        </ParamField>

        <ParamField type="integer">
          Number of image links to return. Range is `0` to `1000`.
        </ParamField>

        <ParamField type="integer">
          Number of rich image links to return. Range is `0` to `1000`.
        </ParamField>

        <ParamField type="integer">
          Number of rich links to return. Range is `0` to `1000`.
        </ParamField>

        <ParamField type="integer">
          Number of code blocks to return. Range is `0` to `1000`.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="integer">
      Timeout for live crawling in milliseconds. Must be greater than `0` and at most `90000`. Default is `10000`.
    </ParamField>

    <ParamField type="integer">
      Maximum cache age in hours. `0` requests fresh content; `-1` always uses cache. Range is `-1` to `720`.
    </ParamField>

    <ParamField type="integer">
      Number of subpages to crawl. Range is `0` to `100`.
    </ParamField>

    <ParamField type="string | string[]">
      Term or terms used to find targeted subpages. Each string must be `1` to `100` characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  Additional query variants for deep-search modes. Range is `1` to `10` entries.
</ParamField>

<ParamField type="string">
  Search mode. One of `instant`, `fast`, `auto`, `deep-lite`, `deep`, or `deep-reasoning`.
</ParamField>

<ParamField type="string">
  Data category hint. Known values include `company`, `research paper`, `news`, `personal site`, `financial report`, `publication`, and `people`.

  Some values restrict which filters can be used:

  * `company` does not support `startPublishedDate`, `endPublishedDate`, `startCrawlDate`, `endCrawlDate`, or `excludeDomains`.
  * `people` does not support `startPublishedDate`, `endPublishedDate`, `startCrawlDate`, `endCrawlDate`, or `excludeDomains`, and `includeDomains` is limited to supported profile domains.
</ParamField>

<ParamField type="string">
  Two-letter ISO country code, such as `US`.
</ParamField>

<ParamField type="string">
  Enterprise compliance mode. Only `hipaa` is supported.
</ParamField>

<ParamField type="object">
  JSON Schema object for structured synthesized output. Supported root types include `text` and `object`; object schemas should describe the expected JSON object returned by Exa.

  <Expandable title="outputSchema properties">
    <ParamField type="string">
      JSON Schema root type, commonly `text` or `object`.
    </ParamField>

    <ParamField type="string">
      Description of the structured output to guide generation.
    </ParamField>

    <ParamField type="object">
      Field definitions for the structured output.
    </ParamField>

    <ParamField type="string[]">
      Required property names.
    </ParamField>

    <ParamField type="boolean | object">
      Whether undeclared fields are allowed, or a schema for additional fields.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Extra instructions that guide synthesized output.
</ParamField>

## Examples

### Search with highlights

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/search' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "how do vector databases handle high-dimensional indexing",
    "numResults": 10,
    "type": "auto",
    "contents": {
      "highlights": true
    }
  }'
```

### Restrict to trusted domains

Use `includeDomains` to keep results within sources you trust.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/search' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "postgres logical replication conflict handling",
    "includeDomains": ["postgresql.org", "aws.amazon.com"],
    "numResults": 5
  }'
```

### Recent results only

Combine the `news` category with a published-date floor to filter out stale pages.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/search' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "offshore wind farm capacity additions",
    "category": "news",
    "startPublishedDate": "2026-01-01T00:00:00.000Z"
  }'
```

### Structured output from a deep search

Use `type: deep` with an `outputSchema` to get a synthesized, schema-shaped result.

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/exa/search' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "which company acquired Instagram and in what year",
    "type": "deep",
    "outputSchema": {
      "type": "object",
      "properties": {
        "acquirer": {"type": "string"},
        "amountUsd": {"type": "string"},
        "yearAnnounced": {"type": "integer"}
      },
      "required": ["acquirer"]
    }
  }'
```

## Response

<ResponseField name="requestId" type="string">
  Unique request identifier.
</ResponseField>

<ResponseField name="results" type="object[]">
  Search results.

  <Expandable title="result properties">
    <ResponseField name="title" type="string">
      Result title.
    </ResponseField>

    <ResponseField name="url" type="string">
      Result URL.
    </ResponseField>

    <ResponseField name="publishedDate" type="string">
      Published date when available.
    </ResponseField>

    <ResponseField name="author" type="string">
      Author when available.
    </ResponseField>

    <ResponseField name="id" type="string">
      Exa document ID.
    </ResponseField>

    <ResponseField name="image" type="string">
      Associated image URL.
    </ResponseField>

    <ResponseField name="favicon" type="string">
      Site favicon URL.
    </ResponseField>

    <ResponseField name="text" type="string">
      Extracted page text when requested.
    </ResponseField>

    <ResponseField name="highlights" type="string[]">
      Highlight snippets when requested.
    </ResponseField>

    <ResponseField name="summary" type="string">
      Generated summary when requested.
    </ResponseField>

    <ResponseField name="subpages" type="object[]">
      Subpage results when requested.

      <Expandable title="subpage properties">
        <ResponseField name="title" type="string">
          Subpage title.
        </ResponseField>

        <ResponseField name="url" type="string">
          Subpage URL.
        </ResponseField>

        <ResponseField name="publishedDate" type="string">
          Published date when available.
        </ResponseField>

        <ResponseField name="author" type="string">
          Author when available.
        </ResponseField>

        <ResponseField name="id" type="string">
          Exa document ID.
        </ResponseField>

        <ResponseField name="image" type="string">
          Associated image URL.
        </ResponseField>

        <ResponseField name="favicon" type="string">
          Site favicon URL.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="extras" type="object">
      Container for extra page data. Each child field is present only when its matching `contents.extras` count is requested, and may be an empty array when the page has no such data.

      <Expandable title="extras properties">
        <ResponseField name="links" type="string[]">
          Extracted links when `extras.links` is requested.
        </ResponseField>

        <ResponseField name="imageLinks" type="string[]">
          Image links when `extras.imageLinks` is requested.
        </ResponseField>

        <ResponseField name="richImageLinks" type="object[]">
          Rich image links when `extras.richImageLinks` is requested.
        </ResponseField>

        <ResponseField name="richLinks" type="object[]">
          Rich links when `extras.richLinks` is requested.
        </ResponseField>

        <ResponseField name="codeBlocks" type="object[]">
          Code blocks when `extras.codeBlocks` is requested.

          <Expandable title="codeBlock properties">
            <ResponseField name="text" type="string">
              Code block content.
            </ResponseField>

            <ResponseField name="source" type="string">
              Where the block was extracted from, such as `text`.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="entities" type="object[]">
      Structured entities extracted from the result, most often returned for the `company`, `people`, and `publication` categories. It may be absent or empty depending on the query and page.

      <Expandable title="entity properties">
        <ResponseField name="id" type="string">
          Stable entity identifier.
        </ResponseField>

        <ResponseField name="type" type="string">
          Entity discriminator. One of `company`, `person`, or `publication`.
        </ResponseField>

        <ResponseField name="version" type="integer">
          Entity schema version.
        </ResponseField>

        <ResponseField name="properties" type="object">
          Type-specific fields. The shape depends on `type`.

          <Expandable title="company properties">
            <ResponseField name="name" type="string">
              Company name.
            </ResponseField>

            <ResponseField name="foundedYear" type="integer">
              Year the company was founded.
            </ResponseField>

            <ResponseField name="description" type="string">
              Short company description.
            </ResponseField>

            <ResponseField name="workforce" type="object">
              Workforce data.

              <Expandable title="workforce properties">
                <ResponseField name="total" type="number">
                  Total headcount.
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="headquarters" type="object">
              Headquarters location.

              <Expandable title="headquarters properties">
                <ResponseField name="address" type="string">
                  Street address.
                </ResponseField>

                <ResponseField name="city" type="string">
                  City.
                </ResponseField>

                <ResponseField name="postalCode" type="string">
                  Postal code.
                </ResponseField>

                <ResponseField name="country" type="string">
                  Country.
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="financials" type="object">
              Financial data.

              <Expandable title="financials properties">
                <ResponseField name="revenueAnnual" type="number">
                  Annual revenue.
                </ResponseField>

                <ResponseField name="fundingTotal" type="number">
                  Total funding raised.
                </ResponseField>

                <ResponseField name="fundingLatestRound" type="object">
                  Most recent funding round.

                  <Expandable title="fundingLatestRound properties">
                    <ResponseField name="name" type="string">
                      Round name, such as `Series C`.
                    </ResponseField>

                    <ResponseField name="date" type="string">
                      Date of the round.
                    </ResponseField>

                    <ResponseField name="amount" type="number">
                      Amount raised in the round.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="webTraffic" type="object">
              Web traffic data.

              <Expandable title="webTraffic properties">
                <ResponseField name="visitsMonthly" type="number">
                  Monthly visits.
                </ResponseField>

                <ResponseField name="countryRank" type="integer">
                  Rank within the country.
                </ResponseField>

                <ResponseField name="avgDurationSeconds" type="number">
                  Average visit duration in seconds.
                </ResponseField>

                <ResponseField name="history" type="object[]">
                  Historical traffic data points.

                  <Expandable title="history item properties">
                    <ResponseField name="value" type="number">
                      Traffic value for the period.
                    </ResponseField>

                    <ResponseField name="dateFrom" type="string">
                      Period start, formatted as `YYYY-MM`.
                    </ResponseField>

                    <ResponseField name="dateTo" type="string">
                      Period end, formatted as `YYYY-MM`.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="research" type="object">
              Research output.

              <Expandable title="research properties">
                <ResponseField name="worksCount" type="integer">
                  Number of published works.
                </ResponseField>

                <ResponseField name="citationCount" type="integer">
                  Total citations.
                </ResponseField>

                <ResponseField name="areas" type="string[]">
                  Research areas.
                </ResponseField>

                <ResponseField name="notableWorks" type="object[]">
                  Notable works.

                  <Expandable title="notableWork item properties">
                    <ResponseField name="title" type="string">
                      Work title.
                    </ResponseField>

                    <ResponseField name="year" type="integer">
                      Publication year.
                    </ResponseField>

                    <ResponseField name="venue" type="string">
                      Publication venue.
                    </ResponseField>

                    <ResponseField name="citationCount" type="integer">
                      Number of citations.
                    </ResponseField>

                    <ResponseField name="doi" type="string">
                      Digital Object Identifier.
                    </ResponseField>

                    <ResponseField name="id" type="string">
                      Work identifier.
                    </ResponseField>

                    <ResponseField name="type" type="string">
                      Work type. One of `article`, `book`, `book-chapter`, `dataset`, `dissertation`, `preprint`, `report`, or `review`.
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="topResearchers" type="object[]">
                  Leading researchers associated with the company.

                  <Expandable title="topResearcher item properties">
                    <ResponseField name="person" type="object">
                      The researcher.

                      <Expandable title="person properties">
                        <ResponseField name="name" type="string">
                          Researcher name.
                        </ResponseField>

                        <ResponseField name="id" type="string">
                          Researcher identifier.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>

                    <ResponseField name="worksCount" type="integer">
                      Number of published works.
                    </ResponseField>

                    <ResponseField name="citationCount" type="integer">
                      Total citations.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>

          <Expandable title="person properties">
            <ResponseField name="name" type="string">
              Full name.
            </ResponseField>

            <ResponseField name="firstName" type="string">
              First name.
            </ResponseField>

            <ResponseField name="lastName" type="string">
              Last name.
            </ResponseField>

            <ResponseField name="location" type="string">
              Location.
            </ResponseField>

            <ResponseField name="workHistory" type="object[]">
              Work history entries.

              <Expandable title="workHistory item properties">
                <ResponseField name="title" type="string">
                  Job title.
                </ResponseField>

                <ResponseField name="location" type="string">
                  Location.
                </ResponseField>

                <ResponseField name="dates" type="object">
                  Date range.

                  <Expandable title="dates properties">
                    <ResponseField name="from" type="string">
                      Start date.
                    </ResponseField>

                    <ResponseField name="to" type="string">
                      End date.
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="company" type="object">
                  Employer.

                  <Expandable title="company properties">
                    <ResponseField name="id" type="string">
                      Company identifier.
                    </ResponseField>

                    <ResponseField name="name" type="string">
                      Company name.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="educationHistory" type="object[]">
              Education entries.

              <Expandable title="educationHistory item properties">
                <ResponseField name="degree" type="string">
                  Degree earned.
                </ResponseField>

                <ResponseField name="dates" type="object">
                  Date range.

                  <Expandable title="dates properties">
                    <ResponseField name="from" type="string">
                      Start date.
                    </ResponseField>

                    <ResponseField name="to" type="string">
                      End date.
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="institution" type="object">
                  Institution.

                  <Expandable title="institution properties">
                    <ResponseField name="id" type="string">
                      Institution identifier.
                    </ResponseField>

                    <ResponseField name="name" type="string">
                      Institution name.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="research" type="object">
              Research output.

              <Expandable title="research properties">
                <ResponseField name="worksCount" type="integer">
                  Number of published works.
                </ResponseField>

                <ResponseField name="citationCount" type="integer">
                  Total citations.
                </ResponseField>

                <ResponseField name="hIndex" type="integer">
                  h-index.
                </ResponseField>

                <ResponseField name="firstPublicationYear" type="integer">
                  Year of first publication.
                </ResponseField>

                <ResponseField name="latestPublicationYear" type="integer">
                  Year of most recent publication.
                </ResponseField>

                <ResponseField name="areas" type="string[]">
                  Research areas.
                </ResponseField>

                <ResponseField name="notableWorks" type="object[]">
                  Notable works.

                  <Expandable title="notableWork item properties">
                    <ResponseField name="title" type="string">
                      Work title.
                    </ResponseField>

                    <ResponseField name="year" type="integer">
                      Publication year.
                    </ResponseField>

                    <ResponseField name="venue" type="string">
                      Publication venue.
                    </ResponseField>

                    <ResponseField name="citationCount" type="integer">
                      Number of citations.
                    </ResponseField>

                    <ResponseField name="doi" type="string">
                      Digital Object Identifier.
                    </ResponseField>

                    <ResponseField name="id" type="string">
                      Work identifier.
                    </ResponseField>

                    <ResponseField name="type" type="string">
                      Work type. One of `article`, `book`, `book-chapter`, `dataset`, `dissertation`, `preprint`, `report`, or `review`.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>

          <Expandable title="publication properties">
            <ResponseField name="title" type="string">
              Publication title.
            </ResponseField>

            <ResponseField name="year" type="integer">
              Publication year.
            </ResponseField>

            <ResponseField name="date" type="string">
              Publication date.
            </ResponseField>

            <ResponseField name="type" type="string">
              Publication type. One of `article`, `book`, `book-chapter`, `dataset`, `dissertation`, `preprint`, `report`, or `review`.
            </ResponseField>

            <ResponseField name="language" type="string">
              Language.
            </ResponseField>

            <ResponseField name="citationCount" type="integer">
              Number of citations.
            </ResponseField>

            <ResponseField name="authors" type="object[]">
              Authors.

              <Expandable title="author properties">
                <ResponseField name="name" type="string">
                  Author name.
                </ResponseField>

                <ResponseField name="id" type="string">
                  Author identifier.
                </ResponseField>
              </Expandable>
            </ResponseField>

            <ResponseField name="referenceCount" type="integer">
              Number of references.
            </ResponseField>

            <ResponseField name="abstract" type="string">
              Abstract text.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="output" type="object">
  Structured or synthesized output when requested.

  <Expandable title="output properties">
    <ResponseField name="content" type="string | object">
      Generated content. It can be structured when `outputSchema` is provided.
    </ResponseField>

    <ResponseField name="grounding" type="object[]">
      Field-level grounding and citation metadata when returned.

      <Expandable title="grounding properties">
        <ResponseField name="field" type="string">
          The field path in `output.content` this grounding applies to.
        </ResponseField>

        <ResponseField name="citations" type="object[]">
          Sources supporting the field.

          <Expandable title="citation properties">
            <ResponseField name="url" type="string">
              Source URL.
            </ResponseField>

            <ResponseField name="title" type="string">
              Source title.
            </ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="confidence" type="string">
          Grounding confidence. One of `low`, `medium`, or `high`.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="searchTime" type="number">
  Time taken to search in milliseconds.
</ResponseField>

## Errors

Errors are returned as a JSON envelope with `code`, `reason`, `message`, and `metadata`. The platform validates and authenticates the request before forwarding it to Exa, so some errors originate at the platform and others are surfaced from upstream.

| Status | `reason` | Meaning |
| - | - | - |
| `400` | `MISSING_API_KEY` | The `Authorization` header is absent. |
| `400` | `INVALID_REQUEST_BODY` | The body failed validation, such as a missing or malformed `query`. |
| `403` | `INVALID_API_KEY` | The API key is invalid or not authorized. |
| `404` | `PATH_NOT_FOUND` | The route does not exist. |
| `429` | `RATE_LIMIT_EXCEEDED` | Too many requests. Slow down and retry with backoff. |
| `500` | `TASK_FAILED` | Internal or upstream provider failure. Transient failures are safe to retry. |
| `503` | `SERVICE_UNAVAILABLE` | The service is temporarily overloaded or down. Retry with backoff. |

For validation failures, `message` is a JSON-encoded string carrying the detailed `error` and `tag`. The examples below show that inner detail decoded for readability.

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "requestId": "string",
    "results": [
      {
        "id": "string",
        "title": "string",
        "url": "string",
        "publishedDate": "string",
        "author": "string",
        "image": "string",
        "favicon": "string",
        "text": "string",
        "highlights": ["string"],
        "summary": "string",
        "subpages": [
          {
            "title": "string",
            "url": "string",
            "publishedDate": "string",
            "author": "string",
            "id": "string",
            "image": "string",
            "favicon": "string"
          }
        ],
        "extras": {
          "links": ["string"],
          "imageLinks": ["string"],
          "richImageLinks": [{}],
          "richLinks": [{}],
          "codeBlocks": [{"text": "string", "source": "string"}]
        },
        "entities": [
          {
            "id": "string",
            "type": "string",
            "version": 0,
            "properties": {}
          }
        ]
      }
    ],
    "output": {
      "content": "string",
      "grounding": [
        {
          "field": "string",
          "citations": [
            {
              "url": "string",
              "title": "string"
            }
          ],
          "confidence": "string"
        }
      ]
    },
    "searchTime": 0
  }
  ```

  ```json 400 theme={"system"}
  {
    "code": 400,
    "reason": "INVALID_REQUEST_BODY",
    "message": "Invalid request body | Validation error: expected string, received undefined at \"query\"",
    "metadata": {
      "trace_id": "d1fbd8a046d4eb0d46f93c77c1ac59b4"
    }
  }
  ```

  ```json 403 theme={"system"}
  {
    "code": 403,
    "reason": "INVALID_API_KEY",
    "message": "invalid api-key",
    "metadata": {}
  }
  ```

  ```json 404 theme={"system"}
  {
    "code": 404,
    "reason": "PATH_NOT_FOUND",
    "message": "path not found for model",
    "metadata": {
      "trace_id": "3c3433f0cb475a04f9a4969b8c6ed903"
    }
  }
  ```

  ```json 429 theme={"system"}
  {
    "code": 429,
    "reason": "RATE_LIMIT_EXCEEDED",
    "message": "rate limit exceeded, please retry later",
    "metadata": {
      "trace_id": "5f2a9c7e41b8d0a36e9f4c2b7d81a0e5"
    }
  }
  ```

  ```json 500 theme={"system"}
  {
    "code": 500,
    "reason": "TASK_FAILED",
    "message": "upstream provider temporarily unavailable, please retry",
    "metadata": {
      "trace_id": "8846379c692d7218173460afe95640f1"
    }
  }
  ```

  ```json 503 theme={"system"}
  {
    "code": 503,
    "reason": "SERVICE_UNAVAILABLE",
    "message": "service temporarily unavailable",
    "metadata": {
      "trace_id": "b7e3f1a9d24c05e8f63a1b9c7e05d2f4"
    }
  }
  ```
</ResponseExample>

## Notes

* All request bodies are JSON.
* Extra Exa parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Exa Search API reference](https://exa.ai/docs/reference/search).


# Fish Audio Text to Speech
Source: https://docs.novita.ai/api-reference/model-apis-fish-audio-text-to-speech

POST https://api.novita.ai/v4beta/txt2speech

<Note>
  For best results, upload reference audio using the [create model](/api-reference/model-apis-fish-audio-voice-cloning) before using this one. This improves speech quality and reduces latency.
</Note>

Fish Audio converts text into speech.

Audio formats supported:

* WAV / PCM
  * Sample Rate: 8kHz, 16kHz, 24kHz, 32kHz, 44.1kHz
  * Default Sample Rate: 44.1kHz
  * 16-bit, mono

* MP3
  * Sample Rate: 32kHz, 44.1kHz
  * Default Sample Rate: 44.1kHz
  * mono
  * Bitrate: 64kbps, 128kbps (default), 192kbps

* Opus
  * Sample Rate: 48kHz
  * Default Sample Rate: 48kHz
  * mono
  * Bitrate: -1000 (auto), 24kbps, 32kbps (default), 48kbps, 64kbps

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

<ParamField type="enum<string>">
  Specify which TTS model to use. Only supports model: `s1`.
</ParamField>

## Request Body

<ParamField type="string">
  Text to be converted to speech.
</ParamField>

<ParamField type="number">
  Controls randomness in the speech generation. Higher values (e.g., 1.0) make the output more random, while lower values (e.g., 0.1) make it more deterministic. We recommend `0.9` for `s1` model.

  Required range: `0 <= x <= 1`
</ParamField>

<ParamField type="number">
  Controls diversity via nucleus sampling. Lower values (e.g., 0.1) make the output more focused, while higher values (e.g., 1.0) allow more diversity. We recommend `0.9` for `s1` model.

  Required range: `0 <= x <= 1`
</ParamField>

<ParamField type="ReferenceAudio · object[] | null">
  References to be used for the speech, this requires MessagePack serialization, this will override reference\_voices and reference\_texts.

  <Expandable title="properties">
    <ParamField type="file">
      Reference audio file.
    </ParamField>

    <ParamField type="string">
      Reference text corresponding to the audio.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string | null">
  ID of the reference model to be used for the speech.
</ParamField>

<ParamField type="ProsodyControl · object">
  Prosody to be used for the speech.

  <Expandable title="properties">
    <ParamField type="number">
      Speech speed control.
    </ParamField>

    <ParamField type="number">
      Speech volume control.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="integer">
  Chunk length to be used for the speech.

  Required range: `100 <= x <= 300`
</ParamField>

<ParamField type="boolean">
  Whether to normalize the speech, this will reduce the latency but may reduce performance on numbers and dates.
</ParamField>

<ParamField type="enum<string>">
  Format to be used for the speech.

  Available options: `wav`, `pcm`, `mp3`, `opus`
</ParamField>

<ParamField type="integer | null">
  Sample rate to be used for the speech.
</ParamField>

<ParamField type="enum<integer>">
  MP3 Bitrate to be used for the speech.

  Available options: `64`, `128`, `192`
</ParamField>

<ParamField type="enum<integer>">
  Opus Bitrate to be used for the speech.

  Available options: `-1000`, `24`, `32`, `48`, `64`
</ParamField>

<ParamField type="enum<string>">
  Latency to be used for the speech, balanced will reduce the latency but may lead to performance degradation.

  Available options: `normal`, `balanced`
</ParamField>

## Response

The API will directly return the audio stream in the format specified by the `format` parameter (default: mp3).


# Fish Audio Voice Cloning
Source: https://docs.novita.ai/api-reference/model-apis-fish-audio-voice-cloning

POST https://api.novita.ai/v4beta/model

Fish Audio API for creating a voice model (voice cloning).

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="enum<string>">
  Model type, tts is for text to speech.

  Available options: `tts`

  Allowed value: `"tts"`
</ParamField>

<ParamField type="string">
  Model title or name.
</ParamField>

<ParamField type="enum<string>">
  Model train mode, for TTS model, fast means model instantly available after creation.

  Available options: `fast`

  Allowed value: `"fast"`
</ParamField>

<ParamField type="file[]">
  Upload voices files that will be used to tune the model.
</ParamField>

<ParamField type="enum<string>">
  Model visibility, public will be shown in the discovery page, unlist allows anyone with the link to access, private only be visible to the creator.

  Available options: `public`, `unlist`, `private`
</ParamField>

<ParamField type="string | null">
  Model description.
</ParamField>

<ParamField type="file | null">
  Model cover image, this is required if the model is public.
</ParamField>

<ParamField type="string[]">
  Texts corresponding to the voices, if unspecified, ASR will be performed on the voices.
</ParamField>

<ParamField type="string[]">
  Model tags.
</ParamField>

<ParamField type="boolean">
  Enhance audio quality.
</ParamField>

## Response

<ResponseField name="_id" type="string">
  Unique identifier for the created model.
</ResponseField>

<ResponseField name="type" type="enum<string>">
  Model type.

  Available options: `svc`, `tts`
</ResponseField>

<ResponseField name="title" type="string">
  Model title or name.
</ResponseField>

<ResponseField name="description" type="string">
  Model description.
</ResponseField>

<ResponseField name="cover_image" type="string">
  URL of the model cover image.
</ResponseField>

<ResponseField name="state" type="enum<string>">
  Current state of the model.

  Available options: `created`, `training`, `trained`, `failed`
</ResponseField>

<ResponseField name="tags" type="string[]">
  Model tags.
</ResponseField>

<ResponseField name="created_at" type="string<date-time>">
  Timestamp when the model was created.
</ResponseField>

<ResponseField name="updated_at" type="string<date-time>">
  Timestamp when the model was last updated.
</ResponseField>

<ResponseField name="visibility" type="enum<string>">
  Model visibility setting.

  Available options: `public`, `unlist`, `private`
</ResponseField>

<ResponseField name="like_count" type="integer">
  Number of likes the model has received.
</ResponseField>

<ResponseField name="mark_count" type="integer">
  Number of marks/bookmarks the model has received.
</ResponseField>

<ResponseField name="shared_count" type="integer">
  Number of times the model has been shared.
</ResponseField>

<ResponseField name="task_count" type="integer">
  Number of tasks associated with the model.
</ResponseField>

<ResponseField name="author" type="AuthorEntity · object">
  Information about the model author.

  <Expandable title="properties">
    <ResponseField name="_id" type="string">
      Author's unique identifier.
    </ResponseField>

    <ResponseField name="nickname" type="string">
      Author's nickname.
    </ResponseField>

    <ResponseField name="avatar" type="string">
      URL of the author's avatar image.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="train_mode" type="enum<string>">
  Training mode used for the model.

  Available options: `fast`, `full`
</ResponseField>

<ResponseField name="samples" type="SampleEntity · object[]">
  Sample data associated with the model.

  <Expandable title="properties">
    <ResponseField name="title" type="string">
      Sample title.
    </ResponseField>

    <ResponseField name="text" type="string">
      Text content of the sample.
    </ResponseField>

    <ResponseField name="task_id" type="string">
      Task identifier for the sample.
    </ResponseField>

    <ResponseField name="audio" type="string">
      URL of the sample audio file.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="languages" type="string[]">
  Languages supported by the model.
</ResponseField>

<ResponseField name="lock_visibility" type="boolean">
  Whether the visibility setting is locked.
</ResponseField>

<ResponseField name="unliked" type="boolean">
  Whether the current user has unliked the model.
</ResponseField>

<ResponseField name="liked" type="boolean">
  Whether the current user has liked the model.
</ResponseField>

<ResponseField name="marked" type="boolean">
  Whether the current user has marked/bookmarked the model.
</ResponseField>


# Get Model
Source: https://docs.novita.ai/api-reference/model-apis-get-model

GET https://api.novita.ai/v3/model

**This API endpoint is designed to retrieve information on both public and private models. It allows users to access details such as model specifications, status, and usage guidelines, ensuring comprehensive insights into the available modeling resources.**

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Model types: `public` or `private`. If not set, the interface will query all types of models.
</ParamField>

<ParamField type="string">
  Source of the model.<br />
  Enum: `civitai`, `training`, `uploading`
</ParamField>

<ParamField type="string">
  Specifies the types of models to include in the query.<br />
  Enum: `checkpoint`, `lora`, `vae`, `controlnet`, `upscaler`, `textualinversion`
</ParamField>

<ParamField type="boolean">
  Whether the model is SDXL or not. Setting this parameter to `true` includes only SDXL models in the query results, which are typically large-scale, high-performance models designed for extensive data processing tasks. Conversely, setting it to `false` excludes these models from the results. If left unspecified, the filter will not discriminate based on the SDXL classification, including all model types in the search results.
</ParamField>

<ParamField type="string">
  Searches the content of sd\_name, name, and tags.
</ParamField>

<ParamField type="boolean">
  If set to true, it will filter out the checkpoints used for inpainting. The default is false.
</ParamField>

<ParamField type="string">
  Number of models to query per request, range (0, 100].
</ParamField>

<ParamField type="string">
  pagination.cursor is used to specify which record to start returning from. If it is empty, it means to get it from the beginning. Generally, the content of the next page is obtained by passing in the next\_cursor field value from the response packet.
</ParamField>

## Response

<ResponseField name="models" type="object[]">
  <Expandable title="properties">
    <ResponseField name="id" type="integer">
      ID of the model.
    </ResponseField>

    <ResponseField name="name" type="string">
      Model name.
    </ResponseField>

    <ResponseField name="hash_sha256" type="string">
      Hash of the model file.
    </ResponseField>

    <ResponseField name="sd_name" type="string">
      Model file name.
    </ResponseField>

    <ResponseField name="type" type="object">
      <Expandable title="properties">
        <ResponseField name="name" type="string">
          Model type name.
        </ResponseField>

        <ResponseField name="display_name" type="string" />
      </Expandable>
    </ResponseField>

    <ResponseField name="categories" type="string[]">
      Model categories.
    </ResponseField>

    <ResponseField name="status" type="integer">
      Model status: 0 for unavailable, 1 for available.
    </ResponseField>

    <ResponseField name="download_url" type="string">
      Model download URL.
    </ResponseField>

    <ResponseField name="tags" type="string[]">
      Model tags, such as photorealistic, anatomical, base model, CGI, realistic, semi-realistic.
    </ResponseField>

    <ResponseField name="cover_url" type="string">
      Model cover image URL.
    </ResponseField>

    <ResponseField name="source" type="string">
      The source of the model, such as civitai, training, uploading.
    </ResponseField>

    <ResponseField name="base_model" type="string">
      Base model of the model, such as SD 1.5 or SDXL 1.0.
    </ResponseField>

    <ResponseField name="base_model_type" type="string">
      Base model type of the model, such as Inpainting or Standard.
    </ResponseField>

    <ResponseField name="download_url_ttl" type="integer">
      The expiration time of the download URL in seconds, default is 1 day.
    </ResponseField>

    <ResponseField name="sd_name_in_api" type="string">
      The name users can add in the interface.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="pagination" type="object">
  <Expandable title="properties">
    <ResponseField name="next_cursor" type="string">
      Next request starting cursor.
    </ResponseField>
  </Expandable>
</ResponseField>

## Example

request

```bash theme={"system"}
curl --location 'https://api.novita.ai/v3/model?filter.visibility=public&pagination.limit=2&pagination.cursor=c_0' \
--header 'Authorization: Bearer {{API Key}}'
```

response

```json theme={"system"}
{
  "models": [
    {
      "id": 114600,
      "name": "V4.0-inpainting (VAE)",
      "hash_sha256": "1A805277C8",
      "sd_name": "realisticVisionV40_v40VAE-inpainting_81543.safetensors",
      "type": {
        "name": "checkpoint",
        "display_name": "Checkpoint"
      },
      "categories": [],
      "status": 1,
      "tags": [
        "photorealistic",
        "anatomical",
        "base model",
        "cgi",
        "realistic",
        "semi-realistic"
      ],
      "cover_url": "https://next-app-static.s3.amazonaws.com/images-prod/xG1nkqKTMzGDvpLrqFT7WA/f291a219-4a86-45ab-96eb-c53446b3e4df/width=450/1495044.jpeg",
      "base_model": "SD 1.5",
      "base_model_type": "Inpainting",
      "download_url_ttl": 2592000,
      "sd_name_in_api": "realisticVisionV40_v40VAE-inpainting_81543.safetensors",
      "is_sdxl": false
    },
    {
      "id": 55199,
      "name": "beta2",
      "hash_sha256": "BA43B0EFEE",
      "sd_name": "GoodHands-beta2_39807.safetensors",
      "type": {
        "name": "locon",
        "display_name": "locon"
      },
      "categories": [],
      "status": 1,
      "tags": ["photorealistic", "concept", "hands"],
      "cover_url": "https://next-app-static.s3.amazonaws.com/images-prod/xG1nkqKTMzGDvpLrqFT7WA/031a378c-3d66-45da-5d67-966c47cd4800/width=450/599083.jpeg",
      "base_model": "SD 1.5",
      "base_model_type": "Standard",
      "download_url_ttl": 2592000,
      "sd_name_in_api": "GoodHands-beta2_39807",
      "is_sdxl": false
    }
  ],
  "pagination": {
    "next_cursor": "c_WzgwNDY2NiwiNTUxOTkiXQ=="
  }
}
```


# Introduction
Source: https://docs.novita.ai/api-reference/model-apis-introduction



For LLM API, Novita AI provides compatibility with the OpenAI API standard, allowing you to use official OpenAI SDKs like openai-python, openai-node, and others to interact with our API. This means you can easily migrate your existing OpenAI-based applications to Novita AI with minimal code changes. Additionally, you can develop your own custom SDKs by following our [API reference](/api-reference/model-apis-llm-create-chat-completion), which details all available endpoints, parameters, and response formats. Our API supports key features like chat chat completions, completions, model listings, and model information retrieval, making it a flexible solution for your LLM needs.

<Card title="LLM API" icon="bot" href="/api-reference/model-apis-llm-create-chat-completion">
  Create Chat Completion / Create Completion / List Models / Retrieve Model.
</Card>

For Images, Audio and Video APIs, Novita AI provides official SDKs for Python and JavaScript to help you quickly integrate with our services. You can find detailed SDK guides and examples [here](/guides/model-apis-sdks). Additionally, we provide [API reference documentation](/api-reference/model-apis-introduction) that covers all endpoints, parameters, and response formats if you prefer to implement the integration yourself. Our APIs support a wide range of multimodal AI capabilities including text-to-image, image-to-image, text-to-video, image-to-video, and text-to-speech...

<Card title="Image, Audio and Video API" icon="clapperboard" href="/api-reference/model-apis-llm-create-chat-completion">
  Text-to-Image / Image-to-Image / Text-to-Video / Image-to-Video / Text-to-Speech...
</Card>


# Inworld TTS 2
Source: https://docs.novita.ai/api-reference/model-apis-inworld-tts-2

POST https://api.novita.ai/v3/inworld-tts-2/tts/v1/voice

## 1. Overview

This document describes how to use speech synthesis and voice cloning through the Inworld TTS native protocol. Both endpoints are synchronous: a POST request returns the result directly, with no need to create a task and poll for the result.

### 1.1 Endpoints

| Capability | Method and path |
| - | - |
| Speech synthesis | `POST /v3/inworld-tts-2/tts/v1/voice` |
| Voice cloning | `POST /v3/inworld-tts-2/voices/v1/voices:clone` |

The full address in the examples in this document is `https://api.novita.ai` plus the endpoint path in the table above, where `https://api.novita.ai` is the API domain you use.

### 1.2 Authentication

All requests must carry the platform API Key and specify the JSON content type:

```text theme={"system"}
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json
```

### 1.3 Response Structure

Both endpoints return JSON on success:

* In the JSON returned by a successful speech synthesis request, `audioContent` is Base64-encoded audio data that must be decoded to obtain the raw audio bytes.
* In the JSON returned by a successful voice cloning request, `voice.voiceId` is the ID of the newly cloned voice, which can be used directly as the `voiceId` field of the speech synthesis endpoint.

## 2. Speech Synthesis

Official parameter reference: [Synthesize Speech](https://docs.inworld.ai/api-reference/ttsAPI/texttospeech/synthesize-speech)

### 2.1 Endpoint

```text theme={"system"}
POST https://api.novita.ai/v3/inworld-tts-2/tts/v1/voice
```

Common request fields:

| Field | Type | Required | Description |
| - | - | - | - |
| `text` | string | Yes | Text to synthesize, up to 2000 UTF-16 code units. |
| `voiceId` | string | Yes (either this or `voiceDesign`) | Voice ID to use. You can use a built-in voice (such as `Ashley`) or a `voiceId` produced by the voice cloning endpoint. |
| `modelId` | string | Yes | Model to use, fixed to `inworld-tts-2`. |
| `language` | string | No | BCP-47 language tag (such as `en-US`). If omitted, the language is detected automatically. |
| `deliveryMode` | string | No | Defaults to `DELIVERY_MODE_UNSPECIFIED`. Options are `STABLE`/`BALANCED`/`CREATIVE`. Takes effect only on `inworld-tts-2` and controls how much the output varies. |
| `temperature` | number | No | Defaults to 1, range `(0, 2]`. This field is ignored on the `inworld-tts-2` model. |
| `timestampType` | string | No | Defaults to `TIMESTAMP_TYPE_UNSPECIFIED`. Options are `WORD`/`CHARACTER`, returning timestamp metadata at the corresponding granularity. |
| `applyTextNormalization` | string | No | Defaults to `APPLY_TEXT_NORMALIZATION_UNSPECIFIED`. Options are `ON`/`OFF`, controlling whether numbers, dates, and abbreviations are expanded before synthesis. |
| `enhanceGeneration` | boolean | No | Defaults to `false`. When enabled, noise reduction is applied to the output. |
| `audioConfig.audioEncoding` | string | No | Defaults to `MP3`. Options are `LINEAR16`/`MP3`/`OGG_OPUS`/`ALAW`/`MULAW`/`FLAC`/`PCM`/`WAV`. |
| `audioConfig.bitRate` | integer | No | Takes effect only for compressed formats. Defaults to 128000 (in bit/s). |
| `audioConfig.sampleRateHertz` | integer | No | Range 8000–48000. |
| `audioConfig.speakingRate` | number | No | Defaults to 1.0, range 0.5–1.5. Controls the speaking rate. |

`voiceId` and `voiceDesign` (instant voice design, out of scope of this document) are mutually exclusive; do not provide both.

### 2.2 Example

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/inworld-tts-2/tts/v1/voice' \
  -H 'Authorization: Bearer <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "Hello world",
    "voiceId": "Ashley",
    "modelId": "inworld-tts-2"
  }'
```

### 2.3 Response

On success, the endpoint returns (HTTP 200):

```json theme={"system"}
{
  "audioContent": "<base64-encoded-audio>"
}
```

| Field | Type | Description |
| - | - | - |
| `audioContent` | string | Base64-encoded audio data, up to 16MB after decoding. The default encoding is `MP3` and can be changed with `audioConfig.audioEncoding`. |
| `usage.processedCharactersCount` | integer | Number of characters processed by this request (usage information). |
| `usage.modelId` | string | Model used for this synthesis. |
| `timestampInfo.wordAlignment` | object | Returned when the request sets `timestampType=WORD`, containing the start and end time of each word. |
| `timestampInfo.characterAlignment` | object | Returned when the request sets `timestampType=CHARACTER`, containing the start and end time of each character. |

Verified in practice: this endpoint has been validated with real requests in all three platform environments and returns real `audioContent`.

### 2.4 Billing

```text theme={"system"}
Request cost = unit price (per million characters) × number of characters in text / 1000000
```

The character count is calculated from the `text` field. Chinese characters count as 2 characters, and other characters (including English letters, digits, spaces, and punctuation) count as 1 character. The exact unit price depends on the pricing page of the service you use.

## 3. Voice Cloning

Official parameter reference: [Clone a Voice](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/clone-voice)

### 3.1 Endpoint

```text theme={"system"}
POST https://api.novita.ai/v3/inworld-tts-2/voices/v1/voices:clone
```

The request body is JSON. Common fields:

| Field | Type | Required | Description |
| - | - | - | - |
| `displayName` | string | Yes | Display name of the cloned voice. |
| `voiceSamples` | array | Yes | List of audio samples used for cloning. |
| `voiceSamples[].audioData` | string | Yes | Base64-encoded WAV or MP3 audio. 10–30 seconds is recommended; anything longer than 30 seconds is truncated. |
| `voiceSamples[].transcription` | string | No | Transcription of the sample. Generated automatically if omitted. |
| `langCode` | string | No | Language (legacy uppercase underscore format, such as `EN_US`). Mutually exclusive with `languageCode`; do not provide both. `AUTO` means automatic detection. |
| `languageCode` | string | No | Language (standard format, such as `en-US`). The language is detected automatically when omitted or set to `"auto"`. |
| `description` | string | No | Description of the voice's tone, accent, or purpose, to make it easier to find. |
| `tags` | array | No | Free-form tags used for filtering and grouping. Gender and age cannot be set through this field; values passed in are silently ignored. |
| `audioProcessingConfig.removeBackgroundNoise` | boolean | No | Whether to separate background noise from the sample. If the original audio is already clean, enabling this may reduce quality instead. |

<Note>
  `gender`/`ageGroup`/`categories` cannot be set during cloning. Call UpdateVoice separately after cloning is complete to set them.
</Note>

### 3.2 Example

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/inworld-tts-2/voices/v1/voices:clone' \
  -H 'Authorization: Bearer <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "displayName": "my-cloned-voice",
    "voiceSamples": [
      {
        "audioData": "<base64-encoded-audio>"
      }
    ]
  }'
```

### 3.3 Response

On success, the endpoint returns (HTTP 200):

```json theme={"system"}
{
  "voice": {
    "voiceId": "<workspace_id>__my-cloned-voice",
    "displayName": "my-cloned-voice",
    "source": "IVC"
  },
  "audioSamplesValidated": [
    {
      "langCode": "EN_US",
      "transcription": "...",
      "warnings": [],
      "errors": []
    }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `voice.voiceId` | string | ID of the cloned voice, used directly as the `voiceId` field of the speech synthesis endpoint. The format is `{workspace}__{voice name}`. |
| `voice.displayName` | string | Echoes the display name from the request. |
| `voice.source` | string | Fixed to `IVC` for instant cloning. |
| `audioSamplesValidated[].langCode` | string | Language of the sample, either detected or specified. |
| `audioSamplesValidated[].transcription` | string | Transcription of the sample. |
| `audioSamplesValidated[].warnings`/`.errors` | array | Lists of non-fatal/fatal issues encountered while processing the sample. |

Verified in practice: the full "clone → synthesize with the cloned voiceId" path has been validated with real requests in all three platform environments.

### 3.4 Billing

The voice cloning endpoint is always free of charge and generates no cost.

## 4. Common Errors and Notes

Endpoint errors use the following structure:

```json theme={"system"}
{
  "code": "<error code>",
  "message": "<error message>"
}
```

| HTTP | Common scenario | Suggested handling |
| - | - | - |
| 400 | Invalid request parameters, such as missing fields, wrong format, or `text` too long. | Fix the request based on the error message. |
| 401 | API Key missing or invalid. | Check the `Authorization` request header. |
| 404 | The `voiceId` used does not exist or is not accessible. | Check the value of the `voiceId` field. |
| 429 | Too many requests (voice cloning is rate limited more strictly than speech synthesis). | Reduce the request rate and use exponential backoff. |
| 500 | Temporary service error. | Retry later. If the failure persists, contact technical support. |

Other notes:

* A `voiceId` created by voice cloning is shared among all callers under the current API Key, and the system does not record which caller cloned which voice. Manage voice ownership and visibility in your own business layer, and do not assume the platform isolates voices between callers.
* Supported audio formats for the voice cloning endpoint are `wav`/`mp3`/`webm`, with a maximum of 12MB per file.


# JEV-27B-VL
Source: https://docs.novita.ai/api-reference/model-apis-jev-27b-vl-decide

POST https://api.novita.ai/v3/autotrust/v1/decide

## 1. Overview

This document describes how to use the **Decision Evaluation (Decide)** capability through the AutoTrust JEV-27B-VL native protocol.

Decision evaluation is not a chat, image, or video generation endpoint: a single request submits one piece of content to evaluate, `state`, and one question, `question`, and the endpoint synchronously returns the probability of each option for that question. The model supports mixing images into `state`, and is suited to scenarios that need a stable conclusion rather than free-form text, such as content moderation decisions, ticket routing, product categorization, and image-text consistency judgment.

The call flow is a single request-response; there is no need to create a task or poll:

1. Call the decision evaluation endpoint, specifying `model`, `kind`, `state`, and `question` in the request body (`choice` also requires `options`).
2. Read the probability of each option from the response `probabilities`, or read `choice` directly.
3. Read this request's token usage from the response `usage`.

Only one question can be asked per request. To ask multiple questions about the same content, call the endpoint separately for each.

### 1.1 Endpoint

| Capability | Endpoint |
| - | - |
| Decision Evaluation | `POST /v3/autotrust/v1/decide` |

The full address in the examples in this document is `https://api.novita.ai` plus the endpoint path in the table above, where `https://api.novita.ai` is the API domain you use.

### 1.2 Authentication

All requests must carry an API Key and specify the JSON content type:

```text theme={"system"}
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json
```

### 1.3 Response Structure

On success, the HTTP status code is 200 and the conclusion is returned directly:

| Field | Type | Description |
| - | - | - |
| `kind` | string | Echoes the question type of the request. |
| `effective_kind` | string | The question type actually executed, usually the same as `kind`. |
| `options` | array | The list of options for this evaluation. For `noul` it is fixed to `["false", "true"]`, for `score` it is fixed to `["0", "1", "2", "3", "4", "5"]`, and for `choice` it is the `options` from the request. |
| `probabilities` | array | Probabilities corresponding one-to-one to `options`, in the same order, summing to 1. |
| `choice_index` | integer | The index in `options` of the highest-probability option. |
| `choice` | string | The highest-probability option. |
| `adaptation` | string | How options are read. It is `native` when there are no more than 16 options. |
| `protocol` | string | The upstream protocol version identifier. |
| `model` | string | The model that actually served this request, which is `autotrust/JEV-27B-VL`. |
| `usage.prompt_tokens` | integer | Number of input tokens, including image tokens; the billing basis. |
| `usage.completion_tokens` | integer | Number of output tokens. It is 1 when thinking is disabled. |
| `usage.total_tokens` | integer | The sum of the two items above. |
| `num_model_requests` | integer | The number of model calls the upstream executed to complete this request. It is 1 when thinking is disabled. |
| `elapsed_seconds` | number | Upstream processing time (seconds). |
| `thinking` | object | Returned only when thinking is enabled in the request; see 2.1. |

## 2. Decision Evaluation

Official parameter documentation: [autotrust/jev-27b-vl (inference.sh)](https://app.inference.sh/apps/autotrust/jev-27b-vl) · [autotrust/JEV-27B-VL model card](https://huggingface.co/autotrust/JEV-27B-VL) (the `POST /v1/decide` section)

### 2.1 Call Endpoint

```text theme={"system"}
POST https://api.novita.ai/v3/autotrust/v1/decide
```

Request fields:

| Field | Type | Required | Description |
| - | - | - | - |
| `model` | string | Yes | Fixed to `autotrust/jev-27b-vl`. |
| `kind` | string | Yes | Question type: `noul` (yes/no), `score` (0 to 5 rating), `choice` (single select from `options`). |
| `state` | string / object / array | No | The content to evaluate. It can be plain text, a JSON object, or an array mixing text and images (see below). Defaults to an empty string. |
| `question` | string | Yes | The question to judge. |
| `options` | array | Required for `choice` | 2 to 256 strings, the candidate options. Ignored for other types. |
| `thinking` | string | No | `off` (default, decision head only), `auto` (thinks only when the highest-probability option is below `threshold`), `on` (always thinks). `score` does not support `auto` or `on`. |
| `threshold` | number | No | The trigger threshold when `thinking=auto`, default 0.8. |
| `reasoning_effort` | string | No | Thinking effort: `xhigh` (default), `medium`, `low`. `high` is not accepted. |
| `think_budget` | integer | No | The maximum number of tokens to use for thinking; unbounded by default. |
| `return_reasoning` | boolean | No | When `true`, the response `thinking` object includes the reasoning text. |
| `strategy` | string | No | The reading strategy when a `choice` has more than 16 options: `single` (default), `tournament`, `permute`. |
| `debug` | boolean | No | When `true`, the response also includes the respective probability distributions for the decision head and thinking. |

**Passing images in `state`**: write `state` as an array, with text as strings and images as `{"image": "<URL or data URL>"}`; images are inserted at the position they appear. Images support `https://` public addresses or the `data:image/...;base64,...` form, up to 8 images. The larger the image, the more input tokens it consumes; a long edge of about 448 pixels is enough for most judgments.

```json theme={"system"}
"state": ["Product image: ", {"image": "https://example.com/item.jpg"}, "\nSeller title: Like-new wireless earbuds"]
```

**Thinking mode**: after `thinking` is enabled, the upstream calls the model for a round of reasoning in addition to the decision head, and fuses the two probability distributions with fixed weights. In this case `usage.completion_tokens` increases to the number of tokens used for thinking, `num_model_requests` is greater than 1, and the latency increases noticeably. The response contains an extra `thinking` object, including `mode`, `threshold`, `budget`, `used`, `think_tokens`, `think_seconds`, `finished_within_budget`, and, when `return_reasoning=true`, also the `reasoning` text.

A single request body is at most 64 MB. The endpoint is a synchronous call; without thinking, upstream latency is usually under 0.2 seconds, and with thinking it grows with `think_budget`, so set a sufficiently long read timeout for your client.

### 2.2 Request Examples

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/autotrust/v1/decide' \
  -H 'Authorization: Bearer <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "autotrust/jev-27b-vl",
    "kind": "choice",
    "state": "Customer: I was charged twice for a single coffee purchase.",
    "question": "Which team should handle this issue?",
    "options": ["billing", "shipping", "tech support"]
  }'
```

A yes/no judgment with an image:

```bash theme={"system"}
curl -X POST 'https://api.novita.ai/v3/autotrust/v1/decide' \
  -H 'Authorization: Bearer <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "autotrust/jev-27b-vl",
    "kind": "noul",
    "state": ["Photo: ", {"image": "https://example.com/photo.jpg"}],
    "question": "Is the main content of this image food or cooking?"
  }'
```

### 2.3 Response

On success, the conclusion is returned directly, with no need to query:

```json theme={"system"}
{
  "kind": "choice",
  "effective_kind": "choice",
  "options": ["billing", "shipping", "tech support"],
  "probabilities": [0.9970129415480892, 5.8849388422286644e-06, 0.002981173513068533],
  "choice_index": 0,
  "choice": "billing",
  "adaptation": "native",
  "protocol": "jev27-bare-v1",
  "model": "autotrust/JEV-27B-VL",
  "usage": {
    "prompt_tokens": 49,
    "completion_tokens": 1,
    "total_tokens": 50
  },
  "elapsed_seconds": 0.111,
  "num_model_requests": 1
}
```

How to read the conclusion for the three question types:

| `kind` | `options` | How to read the conclusion |
| - | - | - |
| `noul` | `["false", "true"]` | `probabilities[1]` is the probability of "yes"; close to 1 is a strong affirmative, close to 0 is a strong negative, and close to 0.5 is uncertain. Set a threshold in your code. |
| `score` | `["0", "1", "2", "3", "4", "5"]` | `choice` is the most likely score; you can also use `probabilities` weighted by index to get the expected score. |
| `choice` | the request's `options` | `choice` is the highest-probability option, and `probabilities` is in the same order as `options`. |

Use `probabilities` for threshold decisions; `choice` is only the highest-probability option and does not indicate confidence.

### 2.4 Billing

```text theme={"system"}
Request cost = input unit price × input tokens ÷ 1000000
```

* Only **input tokens** are billed, and the billed quantity is taken from the response `usage.prompt_tokens`, i.e. the number of input tokens actually counted by the upstream.
* Image tokens are counted as input tokens.
* Output tokens (`usage.completion_tokens`) are currently not billed, including tokens produced by enabling thinking mode.
* Failed requests, or responses that do not contain `usage`, are not charged.

The exact unit price is subject to the pricing page of the service you use.

## 3. Common Errors and Notes

Endpoint errors use the following structure:

```json theme={"system"}
{
  "code": 400,
  "reason": "INVALID_REQUEST_BODY",
  "message": "invalid request body",
  "metadata": {
    "trace_id": "0af7651916cd43dd8448eb211c80319c"
  }
}
```

| HTTP | reason | Common scenario | Suggested handling |
| - | - | - | - |
| 400 | `INVALID_REQUEST_BODY` | The request body is not valid JSON, exceeds 64 MB, or is rejected by upstream validation (such as a `choice` with fewer than 2 options, a missing `kind`, `reasoning_effort` set to `high`, or thinking enabled for `score`). | Fix the request based on `message`. |
| 400 | `MISSING_API_KEY` | The API Key is not provided. | Check the `Authorization` request header. |
| 403 | `NOT_ENOUGH_BALANCE` | Insufficient account balance. | Top up and retry. |
| 403 | `NOT_ENOUGH_KEY_BUDGET` | Insufficient API Key budget. | Adjust the budget limit for that key. |
| 403 | `NOT_ENOUGH_MEMBER_BUDGET` | Insufficient member budget. | Contact the team administrator to adjust the budget. |
| 404 | `FUSION_MODEL_NOT_FOUND` | `model` is not `autotrust/jev-27b-vl`. | Set `model` to `autotrust/jev-27b-vl` in the request body. |
| 404 | `PATH_NOT_FOUND` | The endpoint path or method is incorrect. | Use `POST /v3/autotrust/v1/decide`. |
| 429 | — | Too many requests. | Reduce the rate and use exponential backoff. |
| 500 | `INTERNAL` | The service is temporarily abnormal. | Contact technical support with `metadata.trace_id`. |

Other notes:

* The `model` in the request body is fixed to `autotrust/jev-27b-vl`; the `model` in the response is the upstream echo `autotrust/JEV-27B-VL`. Both refer to the same model.
* This endpoint and `POST /v3/decisions` (Jev 1.13) are two independent protocols: this endpoint asks one question per request and returns a probability array, while `/v3/decisions` asks multiple questions at once and returns an `answers` object. Do not mix their request bodies.
* Validation of fields such as `kind`, `options`, and `thinking` is performed by the upstream, and the error information is returned with the response; correct your request based on that information.
* Image URLs must be publicly accessible to the upstream and must not rely on an intranet, cookies, or a temporary login state; otherwise use an embedded data URL.
* The model's image judgment capability is zero-shot, and the official evaluation has not systematically measured its calibration on image scenarios; use image conclusions together with thresholds and business review.
* Errors returned by the upstream are converted to the platform error structure shown in the table above; when troubleshooting, provide `metadata.trace_id`.

## 4. Python Example

The following example uses the standard `requests` library to demonstrate one complete call.

```python theme={"system"}
import os

import requests


api_domain = os.environ["API_DOMAIN"]
api_key = os.environ["PLATFORM_API_KEY"]

payload = {
    "model": "autotrust/jev-27b-vl",
    "kind": "choice",
    "state": "Customer: I was charged twice for a single coffee purchase.",
    "question": "Which team should handle this issue?",
    "options": ["billing", "shipping", "tech support"],
}

response = requests.post(
    f"https://{api_domain}/v3/autotrust/v1/decide",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    json=payload,
    timeout=120,
)
response.raise_for_status()
result = response.json()

print(result["choice"])
print(dict(zip(result["options"], result["probabilities"])))
print("prompt tokens:", result["usage"]["prompt_tokens"])
```


# Cancel batch
Source: https://docs.novita.ai/api-reference/model-apis-llm-cancel-batch

POST https://api.novita.ai/openai/v1/batches/{batch_id}/cancel

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The unique identifier of the batch job to check.
</ParamField>

## Response

<ResponseField name="id" type="string">
  A unique identifier for the batch job.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `batch`.
</ResponseField>

<ResponseField name="endpoint" type="string">
  The API endpoint that the batch job is using for processing requests.
</ResponseField>

<ResponseField name="input_file_id" type="string">
  The ID of the input file containing the batch requests.
</ResponseField>

<ResponseField name="output_file_id" type="string">
  The ID of the output file containing the batch results. Empty until the batch is completed.
</ResponseField>

<ResponseField name="error_file_id" type="string">
  The ID of the error file containing any errors that occurred during batch processing. Empty until errors occur.
</ResponseField>

<ResponseField name="completion_window" type="string">
  The time window for batch completion. Currently fixed at `48h`.
</ResponseField>

<ResponseField name="in_progress_at" type="string | null">
  The timestamp when the batch started processing. Null if not yet started.
</ResponseField>

<ResponseField name="expires_at" type="string | null">
  The timestamp when the batch will expire. Null if not set.
</ResponseField>

<ResponseField name="finalizing_at" type="string | null">
  The timestamp when the batch started finalizing. Null if not yet finalizing.
</ResponseField>

<ResponseField name="completed_at" type="string | null">
  The timestamp when the batch was completed. Null if not yet completed.
</ResponseField>

<ResponseField name="failed_at" type="string | null">
  The timestamp when the batch failed. Null if not failed.
</ResponseField>

<ResponseField name="expired_at" type="string | null">
  The timestamp when the batch expired. Null if not expired.
</ResponseField>

<ResponseField name="cancelling_at" type="string | null">
  The timestamp when the batch started cancelling. Null if not cancelling.
</ResponseField>

<ResponseField name="cancelled_at" type="string | null">
  The timestamp when the batch was cancelled. Null if not cancelled.
</ResponseField>

<ResponseField name="status" type="string">
  The current status of the batch job.

  Available statuses:

  * `VALIDATING` - The input file is being validated before the batch can begin
  * `PROGRESS` - Batch is in progress
  * `COMPLETED` - Batch processing completed successfully
  * `FAILED` - Batch processing failed
  * `EXPIRED` - Batch exceeded deadline
  * `CANCELLING` - Batch is being cancelled
  * `CANCELLED` - Batch was cancelled
</ResponseField>

<ResponseField name="errors" type="string">
  Error messages if any errors occurred during batch processing.
</ResponseField>

<ResponseField name="version" type="integer">
  The version number of the batch job.
</ResponseField>

<ResponseField name="created_at" type="string">
  The timestamp when the batch job was created.
</ResponseField>

<ResponseField name="updated_at" type="string | null">
  The timestamp when the batch job was last updated. Null if never updated.
</ResponseField>

<ResponseField name="created_by" type="string">
  The unique identifier of the user who created the batch job.
</ResponseField>

<ResponseField name="created_by_key_id" type="string">
  The API key ID used to create the batch job.
</ResponseField>

<ResponseField name="remark" type="string">
  Optional remark or note for the batch job.
</ResponseField>

<ResponseField name="total" type="integer">
  The total number of requests in the batch.
</ResponseField>

<ResponseField name="completed" type="integer">
  The number of completed requests in the batch.
</ResponseField>

<ResponseField name="failed" type="integer">
  The number of failed requests in the batch.
</ResponseField>

<ResponseField name="metadata" type="object | null">
  Additional metadata associated with the batch job. Null if no metadata is provided.
</ResponseField>

<ResponseField name="request_counts" type="object">
  Detailed request count information.

  <Expandable title="properties">
    <ResponseField name="total" type="integer">
      The total number of requests in the batch.
    </ResponseField>

    <ResponseField name="completed" type="integer">
      The number of successfully completed requests.
    </ResponseField>

    <ResponseField name="failed" type="integer">
      The number of failed requests.
    </ResponseField>
  </Expandable>
</ResponseField>


# Create batch
Source: https://docs.novita.ai/api-reference/model-apis-llm-create-batch

POST https://api.novita.ai/openai/v1/batches

After successfully [uploading the input file](/api-reference/model-apis-llm-upload-batch-input-file), initiate a batch by using the ID of the uploaded file. Note that the completion window is set to a fixed duration of 48 hours and cannot be modified.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  ID of the uploaded input file containing batch requests
</ParamField>

<ParamField type="string">
  The endpoint to use for batch processing. Available endpoints are `/v1/chat/completions` and `/v1/completions`.

  <Warning>
    Set the `endpoint` parameter to `/v1/chat/completions` or `/v1/completions` for OpenAI API compatibility.
  </Warning>
</ParamField>

<ParamField type="string">
  Time window for batch completion. The completion window is fixed at `48h` and is currently non-adjustable.
</ParamField>

## Response

<ResponseField name="id" type="string">
  A unique identifier for the batch job.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `batch`.
</ResponseField>

<ResponseField name="endpoint" type="string">
  The API endpoint that the batch job is using for processing requests.
</ResponseField>

<ResponseField name="input_file_id" type="string">
  The ID of the input file containing the batch requests.
</ResponseField>

<ResponseField name="output_file_id" type="string">
  The ID of the output file containing the batch results. Empty until the batch is completed.
</ResponseField>

<ResponseField name="error_file_id" type="string">
  The ID of the error file containing any errors that occurred during batch processing. Empty until errors occur.
</ResponseField>

<ResponseField name="completion_window" type="string">
  The time window for batch completion. Currently fixed at `48h`.
</ResponseField>

<ResponseField name="in_progress_at" type="string | null">
  The timestamp when the batch started processing. Null if not yet started.
</ResponseField>

<ResponseField name="expires_at" type="string | null">
  The timestamp when the batch will expire. Null if not set.
</ResponseField>

<ResponseField name="finalizing_at" type="string | null">
  The timestamp when the batch started finalizing. Null if not yet finalizing.
</ResponseField>

<ResponseField name="completed_at" type="string | null">
  The timestamp when the batch was completed. Null if not yet completed.
</ResponseField>

<ResponseField name="failed_at" type="string | null">
  The timestamp when the batch failed. Null if not failed.
</ResponseField>

<ResponseField name="expired_at" type="string | null">
  The timestamp when the batch expired. Null if not expired.
</ResponseField>

<ResponseField name="cancelling_at" type="string | null">
  The timestamp when the batch started cancelling. Null if not cancelling.
</ResponseField>

<ResponseField name="cancelled_at" type="string | null">
  The timestamp when the batch was cancelled. Null if not cancelled.
</ResponseField>

<ResponseField name="status" type="string">
  The current status of the batch job.

  Available statuses:

  * `VALIDATING` - The input file is being validated before the batch can begin
  * `PROGRESS` - Batch is in progress
  * `COMPLETED` - Batch processing completed successfully
  * `FAILED` - Batch processing failed
  * `EXPIRED` - Batch exceeded deadline
  * `CANCELLING` - Batch is being cancelled
  * `CANCELLED` - Batch was cancelled
</ResponseField>

<ResponseField name="errors" type="string">
  Error messages if any errors occurred during batch processing.
</ResponseField>

<ResponseField name="version" type="integer">
  The version number of the batch job.
</ResponseField>

<ResponseField name="created_at" type="string">
  The timestamp when the batch job was created.
</ResponseField>

<ResponseField name="updated_at" type="string | null">
  The timestamp when the batch job was last updated. Null if never updated.
</ResponseField>

<ResponseField name="created_by" type="string">
  The unique identifier of the user who created the batch job.
</ResponseField>

<ResponseField name="created_by_key_id" type="string">
  The API key ID used to create the batch job.
</ResponseField>

<ResponseField name="remark" type="string">
  Optional remark or note for the batch job.
</ResponseField>

<ResponseField name="total" type="integer">
  The total number of requests in the batch.
</ResponseField>

<ResponseField name="completed" type="integer">
  The number of completed requests in the batch.
</ResponseField>

<ResponseField name="failed" type="integer">
  The number of failed requests in the batch.
</ResponseField>

<ResponseField name="metadata" type="object | null">
  Additional metadata associated with the batch job. Null if no metadata is provided.
</ResponseField>

<ResponseField name="request_counts" type="object">
  Detailed request count information.

  <Expandable title="properties">
    <ResponseField name="total" type="integer">
      The total number of requests in the batch.
    </ResponseField>

    <ResponseField name="completed" type="integer">
      The number of successfully completed requests.
    </ResponseField>

    <ResponseField name="failed" type="integer">
      The number of failed requests.
    </ResponseField>
  </Expandable>
</ResponseField>


# Create chat completion
Source: https://docs.novita.ai/api-reference/model-apis-llm-create-chat-completion

POST https://api.novita.ai/openai/v1/chat/completions

Creates a model response for the given chat conversation.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The name of the model to use.
</ParamField>

<ParamField type="object[]">
  A list of messages comprising the conversation so far.

  <Expandable title="properties">
    <ParamField type="string | object[] | null">
      The contents of the message. content is required for all messages, and may be null for assistant messages with function calls.

      You may use the following parameters depending on the modality.

      <Frame>
        <div>
          <Tabs>
            <Tab title="Text content">
              <p>Option 1:</p>
              <p>You can use the string type to represent the text contents of the message. </p>

              <br />

              <p>Option 2:</p>
              <p>Use an array of content parts, object\[]. Detailed fields are as follows:</p>

              <ParamField type="string">
                The type of the content part, in this case `text`.
              </ParamField>

              <ParamField type="string">
                The text content.
              </ParamField>
            </Tab>

            <Tab title="Image content">
              <p>Only vision language models can be used.</p>
              <p>An array of content parts, object\[]. Detailed fields are as follows:</p>

              <ParamField type="string">
                The type of the content part, in this case `image_url`.
              </ParamField>

              <ParamField type="string">
                <Expandable title="properties">
                  <ParamField type="string">
                    Either a URL of the image or the base64 encoded image data.
                  </ParamField>
                </Expandable>
              </ParamField>
            </Tab>

            <Tab title="Video content">
              <p>Only models that support video can be used.</p>
              <p>An array of content parts, object\[]. Detailed fields are as follows:</p>

              <ParamField type="string">
                The type of the content part, in this case `video_url`.
              </ParamField>

              <ParamField type="string">
                <Expandable title="properties">
                  <ParamField type="string">
                    URL of the video.
                  </ParamField>
                </Expandable>
              </ParamField>
            </Tab>

            <Tab title="Audio content">
              <p>Only models that support audio can be used.</p>
              <p>Output modality parameters:</p>

              <ParamField type="string[]">
                Set the modality of the model output. Currently support two types: `["text"]`, `["text","audio"]`.
                If `["text"]` is passed in, the model only returns text content. If `["text","audio"]` is passed in, the model will return both text content and audio content.
                Default is `["text"]`.
              </ParamField>

              <p>An array of content parts, object\[]. Detailed fields are as follows:</p>

              <ParamField type="string">
                The type of the content part, in this case `input_audio`.
              </ParamField>

              <ParamField type="object">
                <Expandable title="properties">
                  <ParamField type="string">
                    The URL or Base64 encoded data of the audio.
                  </ParamField>

                  <ParamField type="string">
                    The format of the audio.
                  </ParamField>
                </Expandable>
              </ParamField>
            </Tab>
          </Tabs>
        </div>
      </Frame>
    </ParamField>

    <ParamField type="string">
      The role of the messages author. One of system, user, or assistant.

      Enum: `system`, `user`, `assistant`
    </ParamField>

    <ParamField type="string">
      The name of the author of this message. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="integer">
  The maximum number of tokens to generate in the completion.

  If the token count of your prompt (previous messages) plus max\_tokens exceed the model's context length, the behavior is depends on context\_length\_exceeded\_behavior. By default, max\_tokens will be lowered to fit in the context window instead of returning an error.

  For reasoning models, this limit can include reasoning tokens as well as visible output tokens. If the value is too low, the model may use the entire budget for reasoning and return empty `content` with `finish_reason: "length"`. Increase `max_tokens` when this occurs.
</ParamField>

<ParamField type="boolean | null">
  Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent events (SSE) as they become available, with the stream terminated by a `data: [DONE]` message.
</ParamField>

<ParamField type="object | null">
  Options for streaming response. Only set this when you set stream: true.

  <Expandable title="properties">
    <ParamField type="boolean">
      If set, an additional chunk will be streamed before the data: \[DONE] message. The usage field on this chunk shows the token usage statistics for the entire request, and the choices field will always be an empty array. All other chunks will also include a usage field, but with a null value.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="integer | null">
  How many completions to generate for each prompt.

  Note: Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for max\_tokens and stop.

  Required range: `1 < x < 128`
</ParamField>

<ParamField type="integer | null">
  If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result.
</ParamField>

<ParamField type="number | null">
  Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.

  Reasonable value is around 0.1 to 1 if the aim is to just reduce repetitive samples somewhat. If the aim is to strongly suppress repetition, then one can increase the coefficients up to 2, but this can noticeably degrade the quality of samples. Negative values can be used to increase the likelihood of repetition.

  See also presence\_penalty for penalizing tokens that have at least one appearance at a fixed rate.

  Required range: `-2 < x < 2`
</ParamField>

<ParamField type="number | null">
  Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.

  Reasonable value is around 0.1 to 1 if the aim is to just reduce repetitive samples somewhat. If the aim is to strongly suppress repetition, then one can increase the coefficients up to 2, but this can noticeably degrade the quality of samples. Negative values can be used to increase the likelihood of repetition.

  See also `frequency_penalty` for penalizing tokens at an increasing rate depending on how often they appear.

  Required range: `-2 < x < 2`
</ParamField>

<ParamField type="number | null">
  Applies a penalty to repeated tokens to discourage or encourage repetition. A value of 1.0 means no penalty, allowing free repetition. Values above 1.0 penalize repetition, reducing the likelihood of repeating tokens. Values between 0.0 and 1.0 reward repetition, increasing the chance of repeated tokens. For a good balance, a value of 1.2 is often recommended. Note that the penalty is applied to both the generated output and the prompt in decoder-only models.

  Required range: `0 < x < 2`
</ParamField>

<ParamField type="string | null">
  Up to 4 sequences where the API will stop generating further tokens. The returned text will contain the stop sequence.
</ParamField>

<ParamField type="number | null">
  What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.

  We generally recommend altering this or `top_p` but not both.

  Required range: `0 < x < 2`
</ParamField>

<ParamField type="number | null">
  An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top\_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both.

  Required range: `0 < x <= 1`
</ParamField>

<ParamField type="integer | null">
  Top-k sampling is another sampling method where the k most probable next tokens are filtered and the probability mass is redistributed among only those k next tokens. The value of k controls the number of candidates for the next token at each step during text generation.

  Required range: `1 < x < 128`
</ParamField>

<ParamField type="number | null">
  float that represents the minimum probability for a token to be considered, relative to the probability of the most likely token.

  Required range: `0 <= x <= 1`
</ParamField>

<ParamField type="map[string, integer] | null">
  Modify the likelihood of specified tokens appearing in the completion.

  Accepts a JSON object that maps tokens to an associated bias value from -100 to 100.
  Mathematically, the bias is added to the logits generated by the model prior to
  sampling. The exact effect will vary per model.

  For example, by setting `"logit_bias":{"1639": 6}` will increase the likelihood of the token with token ID 1639.
</ParamField>

<ParamField type="boolean | null">
  Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the content of message.
</ParamField>

<ParamField type="integer | null">
  An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability.  `logprobs` must be set to true if this parameter is used.

  Required range: `0 <= x <= 20`
</ParamField>

<ParamField type="object[] | null">
  A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for.

  Learn more about function calling in the [function calling guide](/guides/llm-function-calling).

  <Expandable title="properties">
    <ParamField type="string">
      The type of the tool.

      Supported types: `function`
    </ParamField>

    <ParamField type="object">
      <Expandable title="properties">
        <ParamField type="string">
          The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
        </ParamField>

        <ParamField type="string | null">
          A description of what the function does, used by the model to choose when and how to call the function.
        </ParamField>

        <ParamField type="object | null">
          The parameters the functions accepts, described as a JSON Schema object. See the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.
        </ParamField>

        <ParamField type="boolean">
          Whether to enable strict schema adherence when generating the function call. If set to true, the model will follow the exact schema defined in the parameters field.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object | null">
  Allows to force the model to produce specific output format.

  Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema.

  Setting to `{ "type": "json_object" }` enables the older JSON mode, which ensures the message the model generates is valid JSON. Using `json_schema` is preferred for models that support it.

  <Expandable title="properties">
    <ParamField type="string">
      Enum: `text`, `json_object`, `json_schema`
    </ParamField>

    <ParamField type="object | null">
      JSON Schema response format. Used to generate structured JSON responses.

      Only supported when `type` is set to `json_schema`, and also required when `type` is set to `json_schema`.

      Please learn more in the [Structured Outputs guide](/guides/llm-structured-outputs).

      <Expandable title="properties">
        <ParamField type="string">
          The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
        </ParamField>

        <ParamField type="string | null">
          A description of what the response format is for, used by the model to determine how to respond in the format.
        </ParamField>

        <ParamField type="object | null">
          The schema for the response format, described as a JSON Schema object. Learn how to build JSON schemas [here](https://json-schema.org/specification).

          Supported types: `string`, `number`, `integer`, `boolean`, `array`, `object`, `enum`, `anyOf`.
        </ParamField>

        <ParamField type="boolean">
          Whether to enable strict schema adherence when generating the output. If set to true, the model will always follow the exact schema defined in the schema field. Only a subset of JSON Schema is supported when strict is true.

          If you turn on Structured Outputs by supplying `strict: true` and call the API with an unsupported JSON Schema, you will receive an error.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean | null">
  For reasoning models, return the chain of thought in a dedicated `reasoning_content` field instead of inside `content`. The final answer stays in `content`.

  Only applies to models with the `reasoning` feature. Reasoning tokens are billed as output tokens and count toward `max_tokens` either way.
</ParamField>

<ParamField type="boolean | null">
  Turns thinking (reasoning) on or off for this request. Set to `false` to make the model answer directly without producing reasoning tokens.

  Only applies to hybrid reasoning models that support switching thinking off, for example `moonshotai/kimi-k2.5` and `qwen/qwen3.8-flash`. Models whose reasoning is always on, such as `zai-org/glm-5.3`, ignore this parameter. Check the model page on [novita.ai/models](https://novita.ai/models) for the model's reasoning behavior.
</ParamField>

## Response

<ResponseField name="choices" type="object[]">
  The list of chat completion choices.

  <Expandable title="properties">
    <ResponseField name="finish_reason" type="string">
      The reason the model stopped generating tokens. This will be "stop" if the model hit a natural stop point or a provided stop sequence, or "length" if the maximum number of tokens specified in the request was reached.

      Available options: `stop`, `length`
    </ResponseField>

    <ResponseField name="index" type="integer">
      The index of the chat completion choice.
    </ResponseField>

    <ResponseField name="message" type="object">
      <Expandable title="properties">
        <ResponseField name="role" type="string">
          The role of the author of this message.

          Available options: `system`, `user`, `assistant`
        </ResponseField>

        <ResponseField name="content" type="string | null">
          The contents of the message.
        </ResponseField>

        <ResponseField name="reasoning_content" type="string | null">
          The model's chain of thought, returned separately from `content`. Present only for reasoning models, and only when the request asks for reasoning to be returned in this field (see `separate_reasoning`). It is absent when thinking is turned off with `enable_thinking: false`.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="created" type="integer">
  The Unix time in seconds when the response was generated.
</ResponseField>

<ResponseField name="id" type="string">
  A unique identifier of the response.
</ResponseField>

<ResponseField name="model" type="string">
  The model used for the chat completion.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `chat.completion`.
</ResponseField>

<ResponseField name="usage" type="object">
  Usage statistics.

  For streaming responses, usage field is included in the very last response chunk returned.

  <Expandable title="properties">
    <ResponseField name="completion_tokens" type="integer">
      The number of tokens in the generated completion.
    </ResponseField>

    <ResponseField name="prompt_tokens" type="integer">
      The number of tokens in the prompt.
    </ResponseField>

    <ResponseField name="total_tokens" type="integer">
      The total number of tokens used in the request (prompt + completion).
    </ResponseField>
  </Expandable>
</ResponseField>


# Create completion
Source: https://docs.novita.ai/api-reference/model-apis-llm-create-completion

POST https://api.novita.ai/openai/v1/completions

Creates a completion for the provided prompt and parameters.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The name of the model to use.
</ParamField>

<ParamField type="string">
  The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays.
</ParamField>

<ParamField type="integer">
  The maximum number of tokens that can be generated in the completion.

  The token count of your prompt plus max\_tokens cannot exceed the model's context length.
</ParamField>

<ParamField type="boolean | null">
  Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent events (SSE) as they become available, with the stream terminated by a `data: [DONE]` message.
</ParamField>

<ParamField type="object | null">
  Options for streaming response. Only set this when you set stream: true.

  <Expandable title="properties">
    <ParamField type="boolean">
      If set, an additional chunk will be streamed before the data: \[DONE] message. The usage field on this chunk shows the token usage statistics for the entire request, and the choices field will always be an empty array. All other chunks will also include a usage field, but with a null value.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="integer | null">
  How many completions to generate for each prompt.

  Note: Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for max\_tokens and stop.

  Required range: `1 < x < 128`
</ParamField>

<ParamField type="integer | null">
  If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result.
</ParamField>

<ParamField type="number | null">
  Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.

  Reasonable value is around 0.1 to 1 if the aim is to just reduce repetitive samples somewhat. If the aim is to strongly suppress repetition, then one can increase the coefficients up to 2, but this can noticeably degrade the quality of samples. Negative values can be used to increase the likelihood of repetition.

  See also presence\_penalty for penalizing tokens that have at least one appearance at a fixed rate.

  Required range: `-2 < x < 2`
</ParamField>

<ParamField type="number | null">
  Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.

  Reasonable value is around 0.1 to 1 if the aim is to just reduce repetitive samples somewhat. If the aim is to strongly suppress repetition, then one can increase the coefficients up to 2, but this can noticeably degrade the quality of samples. Negative values can be used to increase the likelihood of repetition.

  See also `frequency_penalty` for penalizing tokens at an increasing rate depending on how often they appear.

  Required range: `-2 < x < 2`
</ParamField>

<ParamField type="number | null">
  Applies a penalty to repeated tokens to discourage or encourage repetition. A value of 1.0 means no penalty, allowing free repetition. Values above 1.0 penalize repetition, reducing the likelihood of repeating tokens. Values between 0.0 and 1.0 reward repetition, increasing the chance of repeated tokens. For a good balance, a value of 1.2 is often recommended. Note that the penalty is applied to both the generated output and the prompt in decoder-only models.

  Required range: `0 < x < 2`
</ParamField>

<ParamField type="string | null">
  Up to 4 sequences where the API will stop generating further tokens. The returned text will contain the stop sequence.
</ParamField>

<ParamField type="number | null">
  What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.

  We generally recommend altering this or `top_p` but not both.

  Required range: `0 < x < 2`
</ParamField>

<ParamField type="number | null">
  An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top\_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both.

  Required range: `0 < x <= 1`
</ParamField>

<ParamField type="integer | null">
  Top-k sampling is another sampling method where the k most probable next tokens are filtered and the probability mass is redistributed among only those k next tokens. The value of k controls the number of candidates for the next token at each step during text generation.

  Required range: `1 < x < 128`
</ParamField>

<ParamField type="number | null">
  float that represents the minimum probability for a token to be considered, relative to the probability of the most likely token.

  Required range: `0 <= x <= 1`
</ParamField>

<ParamField type="map[string, integer] | null">
  Modify the likelihood of specified tokens appearing in the completion.

  Accepts a JSON object that maps tokens to an associated bias value from -100 to 100.
  Mathematically, the bias is added to the logits generated by the model prior to
  sampling. The exact effect will vary per model.

  For example, by setting `"logit_bias":{"1639": 6}` will increase the likelihood of the token with token ID 1639.
</ParamField>

<ParamField type="integer | null">
  Include the log probabilities on the logprobs most likely output tokens, as well the chosen tokens. For example, if logprobs is 5, the API will return a list of the 5 most likely tokens.

  The maximum value for logprobs is 5.
</ParamField>

<ParamField type="integer">
  Defaults to 1. Generates best\_of completions server-side and returns the "best" (the one with the highest log probability per token). Results cannot be streamed.

  When used with n, best\_of controls the number of candidate completions and n specifies how many to return – best\_of must be greater than n.

  Note: Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for max\_tokens and stop.
</ParamField>

## Response

<ResponseField name="choices" type="object[]">
  The list of generated completion choices.

  <Expandable title="properties">
    <ResponseField name="finish_reason" type="string">
      The reason the model stopped generating tokens. This will be "stop" if the model hit a natural stop point or a provided stop sequence, or "length" if the maximum number of tokens specified in the request was reached.

      Enum: `stop`, `length`
    </ResponseField>

    <ResponseField name="index" type="integer">
      The index of the completion choice.
    </ResponseField>

    <ResponseField name="logprobs" type="object">
      The log probabilities of the most likely tokens.

      <Expandable title="properties">
        <ResponseField name="text_offset" type="integer[]">
          Array of integers representing text offsets.
        </ResponseField>

        <ResponseField name="token_logprobs" type="number[]">
          Array of numbers representing token log probabilities.
        </ResponseField>

        <ResponseField name="tokens" type="string[]">
          Array of strings representing tokens.
        </ResponseField>

        <ResponseField name="top_logprobs" type="object[]">
          Array of objects containing top log probabilities.

          <Expandable title="properties">
            <ResponseField name="{key}" type="integer">
              Log probability value for the given key.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="text" type="string">
      The completion response.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="created" type="integer">
  The Unix time in seconds when the response was generated.
</ResponseField>

<ResponseField name="id" type="string">
  A unique identifier of the response.
</ResponseField>

<ResponseField name="model" type="string">
  The model used for the completion.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `text_completion`.
</ResponseField>

<ResponseField name="usage" type="object">
  Usage statistics.

  For streaming responses, usage field is included in the very last response chunk returned.

  <Expandable title="properties">
    <ResponseField name="completion_tokens" type="integer">
      The number of tokens in the generated completion.
    </ResponseField>

    <ResponseField name="prompt_tokens" type="integer">
      The number of tokens in the prompt.
    </ResponseField>

    <ResponseField name="total_tokens" type="integer">
      The total number of tokens used in the request (prompt + completion).
    </ResponseField>
  </Expandable>
</ResponseField>


# Create embeddings
Source: https://docs.novita.ai/api-reference/model-apis-llm-create-embeddings

POST https://api.novita.ai/openai/v1/embeddings

Creates an embedding vector representing the input text.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string | arrary">
  Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for text-embedding-ada-002), cannot be an empty string, and any array must be 2048 dimensions or less.
</ParamField>

<ParamField type="enum<string>">
  ID of the model to use. Enum: `baai/bge-m3`.
</ParamField>

<ParamField type="string">
  The format to return the embeddings in. Can be either float or base64.
</ParamField>

## Response

<ResponseField name="object" type="string">
  Fixed as list
</ResponseField>

<ResponseField name="data" type="object[]">
  List of embeddings vectors generated by the model.

  <Expandable title="properties">
    <ResponseField name="index" type="integer">
      The index of the embedding vector.
    </ResponseField>

    <ResponseField name="embedding" type="number[]">
      The embedding vector.
    </ResponseField>

    <ResponseField name="object" type="string">
      Fixed as embedding
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="model" type="string">
  The ID of the model used.
</ResponseField>

<ResponseField name="usage" type="object">
  Usage information.

  <Expandable title="properties">
    <ResponseField name="prompt_tokens" type="integer">
      The number of prompt tokens.
    </ResponseField>

    <ResponseField name="total_tokens" type="integer">
      The number of total tokens.
    </ResponseField>
  </Expandable>
</ResponseField>


# Create rerank
Source: https://docs.novita.ai/api-reference/model-apis-llm-create-rerank

POST https://api.novita.ai/openai/v1/rerank

Creates a rerank request.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The name of the model to use.
</ParamField>

<ParamField type="string">
  The search query.
</ParamField>

<ParamField type="string[]">
  Document List.
</ParamField>

<ParamField type="integer">
  Number of most relevant documents or indices to return.
</ParamField>

## Response

<ResponseField name="id" type="string">
  ID
</ResponseField>

<ResponseField name="results" type="object[]">
  <Expandable title="properties">
    <ResponseField name="document" type="object">
      Original document content.

      <Expandable title="properties">
        <ResponseField name="text" type="string" />
      </Expandable>
    </ResponseField>

    <ResponseField name="index" type="integer">
      The index value of the position in the input candidate doc array.
    </ResponseField>

    <ResponseField name="relevance_score" type="number">
      Similarity score.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="usage" type="object">
  Token usage statistics.

  <Expandable title="properties">
    <ResponseField name="completion_tokens" type="integer">
      The number of tokens in the generated completion.
    </ResponseField>

    <ResponseField name="prompt_tokens" type="integer">
      The number of tokens in the prompt.
    </ResponseField>

    <ResponseField name="total_tokens" type="integer">
      The total number of tokens used in the request (prompt + completion).
    </ResponseField>
  </Expandable>
</ResponseField>


# Delete file
Source: https://docs.novita.ai/api-reference/model-apis-llm-delete-file

DELETE https://api.novita.ai/openai/v1/files/{file_id}

Deletes a specific file using its file ID.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The unique identifier of the file to delete.
</ParamField>

## Response

<ResponseField name="deleted" type="boolean">
  Indicates whether the file was successfully deleted. Returns `true` if the deletion was successful.
</ResponseField>

<ResponseField name="id" type="string">
  The unique identifier of the deleted file.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `file`.
</ResponseField>


# List batch
Source: https://docs.novita.ai/api-reference/model-apis-llm-list-batches

GET https://api.novita.ai/openai/v1/batches

Displays a list of all available batch jobs.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseField name="data" type="object[]">
  An array of batch job objects.

  <Expandable title="Batch Object Properties">
    <ResponseField name="id" type="string">
      A unique identifier for the batch job.
    </ResponseField>

    <ResponseField name="object" type="string">
      The object type, which is always `batch`.
    </ResponseField>

    <ResponseField name="endpoint" type="string">
      The API endpoint that the batch job is using for processing requests.
    </ResponseField>

    <ResponseField name="input_file_id" type="string">
      The ID of the input file containing the batch requests.
    </ResponseField>

    <ResponseField name="output_file_id" type="string">
      The ID of the output file containing the batch results. Empty until the batch is completed.
    </ResponseField>

    <ResponseField name="error_file_id" type="string">
      The ID of the error file containing any errors that occurred during batch processing. Empty until errors occur.
    </ResponseField>

    <ResponseField name="completion_window" type="string">
      The time window for batch completion. Currently fixed at `48h`.
    </ResponseField>

    <ResponseField name="in_progress_at" type="string | null">
      The timestamp when the batch started processing. Null if not yet started.
    </ResponseField>

    <ResponseField name="expires_at" type="string | null">
      The timestamp when the batch will expire. Null if not set.
    </ResponseField>

    <ResponseField name="finalizing_at" type="string | null">
      The timestamp when the batch started finalizing. Null if not yet finalizing.
    </ResponseField>

    <ResponseField name="completed_at" type="string | null">
      The timestamp when the batch was completed. Null if not yet completed.
    </ResponseField>

    <ResponseField name="failed_at" type="string | null">
      The timestamp when the batch failed. Null if not failed.
    </ResponseField>

    <ResponseField name="expired_at" type="string | null">
      The timestamp when the batch expired. Null if not expired.
    </ResponseField>

    <ResponseField name="cancelling_at" type="string | null">
      The timestamp when the batch started cancelling. Null if not cancelling.
    </ResponseField>

    <ResponseField name="cancelled_at" type="string | null">
      The timestamp when the batch was cancelled. Null if not cancelled.
    </ResponseField>

    <ResponseField name="status" type="string">
      The current status of the batch job.

      Available statuses:

      * `VALIDATING` - The input file is being validated before the batch can begin
      * `PROGRESS` - Batch is in progress
      * `COMPLETED` - Batch processing completed successfully
      * `FAILED` - Batch processing failed
      * `EXPIRED` - Batch exceeded deadline
      * `CANCELLING` - Batch is being cancelled
      * `CANCELLED` - Batch was cancelled
    </ResponseField>

    <ResponseField name="errors" type="string">
      Error messages if any errors occurred during batch processing.
    </ResponseField>

    <ResponseField name="version" type="integer">
      The version number of the batch job.
    </ResponseField>

    <ResponseField name="created_at" type="string">
      The timestamp when the batch job was created.
    </ResponseField>

    <ResponseField name="updated_at" type="string | null">
      The timestamp when the batch job was last updated. Null if never updated.
    </ResponseField>

    <ResponseField name="created_by" type="string">
      The unique identifier of the user who created the batch job.
    </ResponseField>

    <ResponseField name="created_by_key_id" type="string">
      The API key ID used to create the batch job.
    </ResponseField>

    <ResponseField name="remark" type="string">
      Optional remark or note for the batch job.
    </ResponseField>

    <ResponseField name="total" type="integer">
      The total number of requests in the batch.
    </ResponseField>

    <ResponseField name="completed" type="integer">
      The number of completed requests in the batch.
    </ResponseField>

    <ResponseField name="failed" type="integer">
      The number of failed requests in the batch.
    </ResponseField>

    <ResponseField name="metadata" type="object | null">
      Additional metadata associated with the batch job. Null if no metadata is provided.
    </ResponseField>

    <ResponseField name="request_counts" type="object">
      Detailed request count information.

      <Expandable title="properties">
        <ResponseField name="total" type="integer">
          The total number of requests in the batch.
        </ResponseField>

        <ResponseField name="completed" type="integer">
          The number of successfully completed requests.
        </ResponseField>

        <ResponseField name="failed" type="integer">
          The number of failed requests.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="first_id" type="string">
  The ID of the first batch in the list.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  Indicates whether there are more batches available beyond this list.
</ResponseField>

<ResponseField name="last_id" type="string">
  The ID of the last batch in the list.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `list`.
</ResponseField>


# List files
Source: https://docs.novita.ai/api-reference/model-apis-llm-list-files

GET https://api.novita.ai/openai/v1/files

Displays a list of all available files.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseField name="code" type="string">
  HTTP status code indicating the result of the request.
</ResponseField>

<ResponseField name="message" type="string">
  A descriptive message about the request result.
</ResponseField>

<ResponseField name="data" type="object[]">
  An array of file objects.

  <Expandable title="File Object Properties">
    <ResponseField name="id" type="string">
      The unique identifier of the file.
    </ResponseField>

    <ResponseField name="object" type="string">
      The object type, which is always `file`.
    </ResponseField>

    <ResponseField name="bytes" type="integer">
      The size of the file in bytes.
    </ResponseField>

    <ResponseField name="filename" type="string">
      The name of the file.
    </ResponseField>

    <ResponseField name="created_at" type="integer">
      The Unix timestamp (in seconds) when the file was created.
    </ResponseField>

    <ResponseField name="expires_at" type="integer">
      The Unix timestamp (in seconds) when the file will expire. Only present for output files.
    </ResponseField>

    <ResponseField name="purpose" type="string">
      The purpose of the file.

      Available purposes:

      * `batch` - Input file for batch processing
      * `batch_output` - Output file from batch processing
    </ResponseField>

    <ResponseField name="metadata" type="object">
      Additional metadata about the file.

      <Expandable title="properties">
        <ResponseField name="total_requests" type="integer">
          The total number of requests in the batch input file. Only present for batch input files.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>


# List models
Source: https://docs.novita.ai/api-reference/model-apis-llm-list-models

GET https://api.novita.ai/openai/v1/models

Lists the currently available models for the LLM API, and provides basic information about each one. This endpoint is compatible with OpenAI API.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseExample>
  ```json theme={"system"}
  {
    "data": [
      {
        "created": 1733560109,
        "id": "meta-llama/llama-3.3-70b-instruct",
        "object": "model",
        "input_token_price_per_m": 3900,
        "output_token_price_per_m": 3900,
        "title": "meta-llama/llama-3.3-70b-instruct",
        "description": "The Meta Llama 3.3 multilingual large language model (LLM) is a pretrained and instruction tuned generative model in 70B (text in/text out). The Llama 3.3 instruction tuned text only model is optimized for multilingual dialogue use cases and outperforms many of the available open source and closed chat models on common industry benchmarks.\n\nSupported languages: English, German, French, Italian, Portuguese, Hindi, Spanish, and Thai.",
        "context_size": 131072
      },
      // more models here...
    ]
  }
  ```
</ResponseExample>

<ResponseField name="data" type="object[]">
  Array of model objects with the following properties:

  <Expandable title="Model Object Properties">
    <ResponseField name="id" type="string">
      The model identifier, which can be referenced in the API endpoints.
    </ResponseField>

    <ResponseField name="created" type="integer">
      The Unix timestamp (in seconds) when the model was created.
    </ResponseField>

    <ResponseField name="object" type="string">
      The object type, which is always "model".
    </ResponseField>

    <ResponseField name="input_token_price_per_m" type="integer">
      The price per million input tokens.
    </ResponseField>

    <ResponseField name="output_token_price_per_m" type="integer">
      The price per million output tokens.
    </ResponseField>

    <ResponseField name="title" type="string">
      The title of the model.
    </ResponseField>

    <ResponseField name="description" type="string">
      The description of the model.
    </ResponseField>

    <ResponseField name="context_size" type="integer">
      The maximum context size of the model.
    </ResponseField>
  </Expandable>
</ResponseField>


# Retrieve file
Source: https://docs.novita.ai/api-reference/model-apis-llm-query-file

GET https://api.novita.ai/openai/v1/files/{file_id}

Retrieves information about a specific file using its file ID.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The unique identifier of the file to retrieve information for.
</ParamField>

## Response

<ResponseField name="id" type="string">
  The unique identifier of the file.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `file`.
</ResponseField>

<ResponseField name="bytes" type="integer">
  The size of the file in bytes.
</ResponseField>

<ResponseField name="filename" type="string">
  The name of the file.
</ResponseField>

<ResponseField name="created_at" type="integer">
  The Unix timestamp (in seconds) when the file was created.
</ResponseField>

<ResponseField name="expires_at" type="integer">
  The Unix timestamp (in seconds) when the file will expire. Only present for output files.
</ResponseField>

<ResponseField name="purpose" type="string">
  The purpose of the file.

  Available purposes:

  * `batch` - Input file for batch processing
  * `batch_output` - Output file from batch processing
</ResponseField>

<ResponseField name="metadata" type="object">
  Additional metadata about the file.

  <Expandable title="properties">
    <ResponseField name="total_requests" type="integer">
      The total number of requests in the batch input file. Only present for batch input files.
    </ResponseField>
  </Expandable>
</ResponseField>


# Retrieve batch
Source: https://docs.novita.ai/api-reference/model-apis-llm-retrieve-batch

GET https://api.novita.ai/openai/v1/batches/{batch_id}

Retrieve batch using its batch ID.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The unique identifier of the batch job to check.
</ParamField>

## Response

<ResponseField name="id" type="string">
  A unique identifier for the batch job.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `batch`.
</ResponseField>

<ResponseField name="endpoint" type="string">
  The API endpoint that the batch job is using for processing requests.
</ResponseField>

<ResponseField name="input_file_id" type="string">
  The ID of the input file containing the batch requests.
</ResponseField>

<ResponseField name="output_file_id" type="string">
  The ID of the output file containing the batch results. Empty until the batch is completed.
</ResponseField>

<ResponseField name="error_file_id" type="string">
  The ID of the error file containing any errors that occurred during batch processing. Empty until errors occur.
</ResponseField>

<ResponseField name="completion_window" type="string">
  The time window for batch completion. Currently fixed at `48h`.
</ResponseField>

<ResponseField name="in_progress_at" type="string | null">
  The timestamp when the batch started processing. Null if not yet started.
</ResponseField>

<ResponseField name="expires_at" type="string | null">
  The timestamp when the batch will expire. Null if not set.
</ResponseField>

<ResponseField name="finalizing_at" type="string | null">
  The timestamp when the batch started finalizing. Null if not yet finalizing.
</ResponseField>

<ResponseField name="completed_at" type="string | null">
  The timestamp when the batch was completed. Null if not yet completed.
</ResponseField>

<ResponseField name="failed_at" type="string | null">
  The timestamp when the batch failed. Null if not failed.
</ResponseField>

<ResponseField name="expired_at" type="string | null">
  The timestamp when the batch expired. Null if not expired.
</ResponseField>

<ResponseField name="cancelling_at" type="string | null">
  The timestamp when the batch started cancelling. Null if not cancelling.
</ResponseField>

<ResponseField name="cancelled_at" type="string | null">
  The timestamp when the batch was cancelled. Null if not cancelled.
</ResponseField>

<ResponseField name="status" type="string">
  The current status of the batch job.

  Available statuses:

  * `VALIDATING` - The input file is being validated before the batch can begin
  * `PROGRESS` - Batch is in progress
  * `COMPLETED` - Batch processing completed successfully
  * `FAILED` - Batch processing failed
  * `EXPIRED` - Batch exceeded deadline
  * `CANCELLING` - Batch is being cancelled
  * `CANCELLED` - Batch was cancelled
</ResponseField>

<ResponseField name="errors" type="string">
  Error messages if any errors occurred during batch processing.
</ResponseField>

<ResponseField name="version" type="integer">
  The version number of the batch job.
</ResponseField>

<ResponseField name="created_at" type="string">
  The timestamp when the batch job was created.
</ResponseField>

<ResponseField name="updated_at" type="string | null">
  The timestamp when the batch job was last updated. Null if never updated.
</ResponseField>

<ResponseField name="created_by" type="string">
  The unique identifier of the user who created the batch job.
</ResponseField>

<ResponseField name="created_by_key_id" type="string">
  The API key ID used to create the batch job.
</ResponseField>

<ResponseField name="remark" type="string">
  Optional remark or note for the batch job.
</ResponseField>

<ResponseField name="total" type="integer">
  The total number of requests in the batch.
</ResponseField>

<ResponseField name="completed" type="integer">
  The number of completed requests in the batch.
</ResponseField>

<ResponseField name="failed" type="integer">
  The number of failed requests in the batch.
</ResponseField>

<ResponseField name="metadata" type="object | null">
  Additional metadata associated with the batch job. Null if no metadata is provided.
</ResponseField>

<ResponseField name="request_counts" type="object">
  Detailed request count information.

  <Expandable title="properties">
    <ResponseField name="total" type="integer">
      The total number of requests in the batch.
    </ResponseField>

    <ResponseField name="completed" type="integer">
      The number of successfully completed requests.
    </ResponseField>

    <ResponseField name="failed" type="integer">
      The number of failed requests.
    </ResponseField>
  </Expandable>
</ResponseField>


# Retrieve file content
Source: https://docs.novita.ai/api-reference/model-apis-llm-retrieve-file-content

GET https://api.novita.ai/openai/v1/files/{file_id}/content

Retrieves the content of a specific file using its file ID. This is commonly used to download batch output files or error files.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The unique identifier of the file to retrieve content for.
</ParamField>

## Response

The response returns the raw file content. For batch output files, this will be in JSONL format where each line contains a batch request result.

**Example Response**

For batch output files, each line contains a response like this:

```json theme={"system"}
{
  "custom_id": "request-2589",
  "error": null,
  "id": "batch_req_task_d2c",
  "response": {
    "body": {
      "id": "29e1432c-edfb-44a4-b531-c23c600abfae",
      "object": "chat.completion",
      "created": 1754902266,
      "model": "deepseek-test",
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": "Hello! 👋 How can I assist you today? 😊"
          },
          "finish_reason": "stop"
        }
      ],
      "usage": {
        "prompt_tokens": 5,
        "completion_tokens": 13,
        "total_tokens": 18
      }
    },
    "request_id": "request-2589",
    "status_code": 200
  }
}
```


# Retrieve model
Source: https://docs.novita.ai/api-reference/model-apis-llm-retrieve-model

GET https://api.novita.ai/openai/v1/models/{model}

Retrieves a model instance, providing basic information about the model. This endpoint is compatible with OpenAI API.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Path Parameters

<ParamField type="string">
  The ID of the model to use for this request.
</ParamField>

## Response

<ResponseExample>
  ```json theme={"system"}
  {
    "created": 1733560109,
    "id": "meta-llama/llama-3.3-70b-instruct",
    "object": "model",
    "input_token_price_per_m": 3900,
    "output_token_price_per_m": 3900,
    "title": "meta-llama/llama-3.3-70b-instruct",
    "description": "The Meta Llama 3.3 multilingual large language model (LLM) is a pretrained and instruction tuned generative model in 70B (text in/text out). The Llama 3.3 instruction tuned text only model is optimized for multilingual dialogue use cases and outperforms many of the available open source and closed chat models on common industry benchmarks.\n\nSupported languages: English, German, French, Italian, Portuguese, Hindi, Spanish, and Thai.",
    "context_size": 131072,
  }
  ```
</ResponseExample>

<ResponseField name="id" type="string">
  The model identifier, which can be referenced in the API endpoints.
</ResponseField>

<ResponseField name="created" type="integer">
  The Unix timestamp (in seconds) when the model was created.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always "model".
</ResponseField>

<ResponseField name="input_token_price_per_m" type="integer">
  The price per million input tokens.
</ResponseField>

<ResponseField name="output_token_price_per_m" type="integer">
  The price per million output tokens.
</ResponseField>

<ResponseField name="title" type="string">
  The title of the model.
</ResponseField>

<ResponseField name="description" type="string">
  The description of the model.
</ResponseField>

<ResponseField name="context_size" type="integer">
  The maximum context size of the model.
</ResponseField>


# Upload file
Source: https://docs.novita.ai/api-reference/model-apis-llm-upload-batch-input-file

POST https://api.novita.ai/openai/v1/files

Upload the batch input file so that it can be correctly referenced when creating a batch.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="file">
  The batch input file to be uploaded should be in `.jsonl` format, with each line detailing an API inference request.

  Each request must have a unique `custom_id` to identify inference results in the output file after batch processing. The parameters within the `body` field of each line are used as the actual inference request parameters for the endpoint.

  <Warning>
    All requests within a single batch JSONL file must target the same model. Do not mix requests for different models in one batch.
  </Warning>

  Here is an example of an input file containing two requests:

  ```JSON theme={"system"}
  {"custom_id": "request-1", "body": {"model": "deepseek/deepseek-r1-0528", "messages": [{"role": "user", "content": "Hello, world!"}], "max_tokens": 400}}
  {"custom_id": "request-2", "body": {"model": "deepseek/deepseek-r1-0528", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}
  ```
</ParamField>

<ParamField type="string">
  The purpose of the uploaded file. For batch processing, this should be set to `batch`.

  Enum: `batch`
</ParamField>

## Response

<ResponseField name="id" type="string">
  The unique identifier of the uploaded file.
</ResponseField>

<ResponseField name="object" type="string">
  The object type, which is always `file`.
</ResponseField>

<ResponseField name="bytes" type="integer">
  The size of the uploaded file in bytes.
</ResponseField>

<ResponseField name="created_at" type="integer">
  The Unix timestamp (in seconds) when the file was created.
</ResponseField>

<ResponseField name="filename" type="string">
  The name of the uploaded file.
</ResponseField>

<ResponseField name="purpose" type="string">
  The purpose of the uploaded file.
</ResponseField>

<ResponseField name="metadata" type="object">
  Additional metadata about the uploaded file.

  <Expandable title="properties">
    <ResponseField name="total_requests" type="integer">
      The total number of requests contained in the batch input file.
    </ResponseField>
  </Expandable>
</ResponseField>


# Ming Image Layer Decoupling
Source: https://docs.novita.ai/api-reference/model-apis-ming-image-layer

POST https://api.novita.ai/openai/v1/images/edits

`ming-image-0.1-design-layer` only supports layer decoupling, using the OpenAI Image edits protocol.

## Request Headers

<ParamField type="string">
  Supports: `multipart/form-data`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="enum">
  The model name. Available values: `ming-image-0.1-design-layer`
</ParamField>

<ParamField type="Files">
  The input image, 1 image. It is `image[]` in the form.

  `ming-image-0.1-design-layer` only supports 1 image input.
</ParamField>

<ParamField type="string">
  The text description of the image.
</ParamField>

<ParamField type="string">
  The output image format. Must be one of `png` or `webp`.
</ParamField>

<ParamField type="string">
  The form in which the image is returned, default is `b64_json`. Available values: `url`, `b64_json`. Note: `url` is valid for 24 hours.
</ParamField>

<ParamField type="string">
  The output image size, default `auto` selects automatically based on the model. Format `"{w}x{h}"`, e.g. `"1024x1024"`.
</ParamField>

<ParamField type="boolean">
  Controls whether a watermark is added to the AI-generated image. `true`: enables the explicit watermark and implicit digital watermark on AI-generated images, complying with policy requirements. `false`: disables all watermarks.
</ParamField>

## Response

<ResponseField name="created" type="integer">
  The Unix timestamp (seconds) when the response was created.
</ResponseField>

<ResponseField name="data" type="array">
  The array of returned images, containing multiple layers (e.g. foreground layer, background layer, etc.), one element per layer.

  <Expandable title="properties">
    <ResponseField name="b64_json" type="string">
      The base64-encoded layer image data.
    </ResponseField>

    <ResponseField name="data[0].revised_prompt" type="string">
      A JSON string describing the layer structure, indicating the canvas size and the information of each region.

      <Expandable title="properties">
        <ResponseField name="revised_prompt.canvas" type="array">
          The canvas size `[width, height]`.
        </ResponseField>

        <ResponseField name="revised_prompt.regions" type="array">
          The list of layer regions.

          <Expandable title="properties">
            <ResponseField name="revised_prompt.regions[].category" type="string">
              The layer category, e.g. `Image` (foreground layer), `BackgroundImage` (background layer).
            </ResponseField>

            <ResponseField name="revised_prompt.regions[].bbox" type="array">
              The bounding box of the layer on the canvas `[x1, y1, x2, y2]`.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="output_format" type="string">
  The output image format (layers are always output as png).
</ResponseField>

<ResponseField name="size" type="string">
  `null`
</ResponseField>

<ResponseField name="usage" type="object">
  Token usage statistics.

  <Expandable title="properties">
    <ResponseField name="input_tokens" type="integer">
      Total input tokens (including the tokens of the uploaded image).
    </ResponseField>

    <ResponseField name="input_tokens_details" type="object">
      Input token details.

      <Expandable title="properties">
        <ResponseField name="image_tokens" type="integer">
          The number of tokens consumed by the input image.
        </ResponseField>

        <ResponseField name="text_tokens" type="integer">
          The number of tokens consumed by the input text.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="output_tokens" type="integer">
      Total output tokens (the sum across multiple layers, e.g. 3 layers × 4096 = 12288).
    </ResponseField>

    <ResponseField name="total_tokens" type="integer">
      The total of input + output tokens.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="model" type="string">
  The name of the model that actually processed the request.
</ResponseField>

<ResponseField name="id" type="string">
  The unique identifier ID of this request.
</ResponseField>

## Example

`Request:`

```bash theme={"system"}
curl --location --request POST 'https://api.novita.ai/openai/v1/images/edits' \
--header 'Authorization: Bearer {{API Key}}' \
--form 'model="ming-image-0.1-design-layer"' \
--form 'prompt="Split into three layers"' \
--form 'image[]=@"{The Path of Your Image File}"' \
--form 'size="1024x1024"'
```

`Response:`

```js theme={"system"}
{
  "created": 1789642431,
  "data": [
    {
      "b64_json": "...",
      "revised_prompt": "{\"canvas\": [1024, 1024], \"regions\": [{\"category\": \"Image\", \"bbox\": [1.4, 3.3, 1023.0, 1022.0]}, {\"category\": \"BackgroundImage\", \"bbox\": [1.4, 3.3, 1023.0, 1022.0]}]}"
    },
    {
      "b64_json": "..."
    },
    {
      "b64_json": "..."
    }
  ],
  "output_format": "png",
  "size": null,
  "usage": {
    "input_tokens": 4096,
    "input_tokens_details": {
      "image_tokens": 4096,
      "text_tokens": 0
    },
    "output_tokens": 12288,
    "total_tokens": 16384
  },
  "model": "Ming-Image-0.1-Design-Layer-StressTest",
  "id": "218edba617896423893976485e4f35"
}
```


# Ming Image Text to Image
Source: https://docs.novita.ai/api-reference/model-apis-ming-image-txt2img

POST https://api.novita.ai/openai/v1/images/generations

Ming Image — an image generation model family. `ming-image-0.1-design` supports text-to-image generation using the OpenAI Image generations protocol.

* `ming-image-0.1-design`: Supports text-to-image, using the OpenAI Image generations protocol.
* `ming-image-0.1-design-layer`: Only supports layer decoupling, using the OpenAI Image edits protocol (see the [Ming Image Layer Decoupling](/api-reference/model-apis-ming-image-layer) API).

## Request Headers

<ParamField type="string">
  Supports: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="enum">
  The model name. Available values: `ming-image-0.1-design`
</ParamField>

<ParamField type="string">
  The text description of the image.
</ParamField>

<ParamField type="string">
  The output image format. Must be one of `png` or `jpeg`.
</ParamField>

<ParamField type="string">
  The form in which the image is returned, default is `b64_json`. Available values: `url`, `b64_json`. Note: `url` is valid for 24 hours.
</ParamField>

<ParamField type="string">
  The output image size, default `auto` selects automatically based on the model. Format `"{w}x{h}"`, e.g. `"1024x1024"`.

  `ming-image-0.1-design` size only supports 1k and above, e.g. `"1024x1024"`, `"2048x2048"`.
</ParamField>

<ParamField type="boolean">
  Controls whether a watermark is added to the AI-generated image. `true`: enables the explicit watermark and implicit digital watermark on AI-generated images, complying with policy requirements. `false`: disables all watermarks.
</ParamField>

## Response

<ResponseField name="created" type="integer">
  The Unix timestamp (seconds) when the response was created.
</ResponseField>

<ResponseField name="data" type="array">
  The array of returned images, each element contains the information of one generated image.

  <Expandable title="properties">
    <ResponseField name="b64_json" type="string">
      The base64-encoded generated image data.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="output_format" type="string">
  The actual output image format, e.g. `png`, `jpeg`.
</ResponseField>

<ResponseField name="size" type="string">
  `null`
</ResponseField>

<ResponseField name="usage" type="object">
  Token usage statistics.

  <Expandable title="properties">
    <ResponseField name="input_tokens" type="integer">
      Total input tokens (0 for text-to-image).
    </ResponseField>

    <ResponseField name="input_tokens_details" type="object">
      Input token details.

      <Expandable title="properties">
        <ResponseField name="image_tokens" type="integer">
          The number of image tokens in the input (0 for text-to-image).
        </ResponseField>

        <ResponseField name="text_tokens" type="integer">
          The number of text tokens in the input.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="output_tokens" type="integer">
      Total output tokens (i.e. the tokens consumed by the generated image).
    </ResponseField>

    <ResponseField name="total_tokens" type="integer">
      The total of input + output tokens.
    </ResponseField>

    <ResponseField name="output_tokens_details" type="object">
      Output token details.

      <Expandable title="properties">
        <ResponseField name="image_tokens" type="integer">
          The number of image tokens in the output.
        </ResponseField>

        <ResponseField name="text_tokens" type="integer">
          The number of text tokens in the output.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="model" type="string">
  The name of the model that actually processed the request.
</ResponseField>

<ResponseField name="id" type="string">
  The unique identifier ID of this request.
</ResponseField>

## Example

`Request:`

```bash theme={"system"}
curl --location --request POST 'https://api.novita.ai/openai/v1/images/generations' \
--header 'Authorization: Bearer {{API Key}}' \
--header 'content-type: application/json' \
--data-raw '{
  "model": "ming-image-0.1-design",
  "prompt": "Generate a banana"
}'
```

`Response:`

```js theme={"system"}
{
  "created": 1789642354,
  "data": [
    {
      "b64_json": "..."
    }
  ],
  "output_format": "png",
  "size": null,
  "usage": {
    "input_tokens": 0,
    "input_tokens_details": {
      "image_tokens": 0,
      "text_tokens": 0
    },
    "output_tokens": 16384,
    "total_tokens": 16384,
    "output_tokens_details": {
      "image_tokens": 16384,
      "text_tokens": 0
    }
  },
  "model": "Ming-Image-0.1-Design-StressTest",
  "id": "2180534417896423312485570e8523"
}
```


# MiniMax Speech 2.8 HD Sync Text-to-Speech
Source: https://docs.novita.ai/api-reference/model-apis-minimax-speech-2.8-hd

POST https://api.novita.ai/v3/minimax-speech-2.8-hd

MiniMax synchronous text-to-speech API using HTTP protocol. Supports various voice, emotion, speed and other parameter settings.

## Request Headers

<ParamField type="string">
  Supports: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Text to synthesize into speech, length limit is less than 10000 characters. If text length is greater than 3000 characters, streaming output is recommended. Supports paragraph breaks (newline), pause control (`&lt;#x#&gt;` tag), and interjection tags (such as (laughs), (coughs), etc., only supported by speech-2.8-hd/turbo)
</ParamField>

<ParamField type="boolean">
  Controls whether to enable streaming output. Default is false
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="integer">
      Pitch adjustment (deep/bright), range \[-100, 100]. Values closer to -100 produce deeper voice; closer to 100 produce brighter voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Timbre adjustment (rich/crisp), range \[-100, 100]. Values closer to -100 produce richer voice; closer to 100 produce crisper voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Intensity adjustment (powerful/soft), range \[-100, 100]. Values closer to -100 produce more powerful voice; closer to 100 produce softer voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="string">
      Sound effect setting, only one can be selected at a time. Options: spacious\_echo (spacious echo), auditorium\_echo (auditorium broadcast), lofi\_telephone (telephone distortion), robotic (electronic)

      Optional values: `spacious_echo`, `auditorium_echo`, `lofi_telephone`, `robotic`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string">
      Audio output format. wav is only supported in non-streaming output

      Optional values: `mp3`, `pcm`, `flac`, `wav`
    </ParamField>

    <ParamField type="integer">
      Audio bitrate. Options: \[32000, 64000, 128000, 256000], default is 128000. This parameter only applies to mp3 format

      Optional values: `32000`, `64000`, `128000`, `256000`
    </ParamField>

    <ParamField type="integer">
      Number of audio channels. Options: \[1, 2], where 1 is mono and 2 is stereo. Default is 1

      Optional values: `1`, `2`
    </ParamField>

    <ParamField type="boolean">
      Controls constant bitrate (CBR) encoding. When set to true, audio will be encoded with constant bitrate. Note: This parameter only works when streaming output is enabled and audio format is mp3
    </ParamField>

    <ParamField type="integer">
      Audio sample rate. Options: \[8000, 16000, 22050, 24000, 32000, 44100], default is 32000

      Optional values: `8000`, `16000`, `22050`, `24000`, `32000`, `44100`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Controls output format, options are url or hex, default is hex. This parameter is only valid in non-streaming scenarios. URL is valid for 24 hours

  Optional values: `url`, `hex`
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="number">
      Audio volume, higher value means louder. Range (0, 10], default is 1.0

      Value range: \[0, 10]
    </ParamField>

    <ParamField type="integer">
      Audio pitch, range \[-12, 12], default is 0, where 0 is original voice output

      Value range: \[-12, 12]
    </ParamField>

    <ParamField type="number">
      Speech speed, higher value means faster. Range \[0.5, 2], default is 1.0

      Value range: \[0.5, 2]
    </ParamField>

    <ParamField type="string">
      Controls the emotion of synthesized speech. Options correspond to 8 emotions: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper. The model will automatically match appropriate emotion based on input text

      Optional values: `happy`, `sad`, `angry`, `fearful`, `disgusted`, `surprised`, `calm`, `fluent`, `whisper`
    </ParamField>

    <ParamField type="string">
      Voice ID for audio synthesis. If mixed voice is needed, set timber\_weights parameter and leave this empty. Supports system voice, cloned voice, and text-generated voice
    </ParamField>

    <ParamField type="boolean">
      Controls whether to read LaTeX formulas, default is false. Only supports Chinese. When enabled, language\_boost will be set to Chinese
    </ParamField>

    <ParamField type="boolean">
      Whether to enable Chinese/English text normalization, which can improve performance in number reading scenarios but slightly increases latency. Default is false
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean">
  Controls whether to add audio rhythm identifier at the end of synthesized audio, default is false. This parameter is only valid for non-streaming synthesis
</ParamField>

<ParamField type="string">
  Whether to enhance recognition ability for specified minor languages and dialects. Default is null, can be set to auto to let the model decide automatically

  Optional values: `Chinese`, `Chinese,Yue`, `English`, `Arabic`, `Russian`, `Spanish`, `French`, `Portuguese`, `German`, `Turkish`, `Dutch`, `Ukrainian`, `Vietnamese`, `Indonesian`, `Japanese`, `Italian`, `Korean`, `Thai`, `Polish`, `Romanian`, `Greek`, `Czech`, `Finnish`, `Hindi`, `Bulgarian`, `Danish`, `Hebrew`, `Malay`, `Persian`, `Slovak`, `Swedish`, `Croatian`, `Filipino`, `Hungarian`, `Norwegian`, `Slovenian`, `Catalan`, `Nynorsk`, `Tamil`, `Afrikaans`, `auto`
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="boolean">
      Sets whether the last chunk contains the concatenated audio hex data. Default is false, meaning the last chunk contains the complete concatenated audio hex data
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Mixed voice settings, supports up to 4 voice mixtures

  <Expandable title="properties">
    <ParamField type="integer">
      Weight of each voice in the mix, must be set together with voice\_id. Range \[1, 100], supports up to 4 voice mixtures. Higher weight means more similarity to that voice

      Value range: \[1, 100]
    </ParamField>

    <ParamField type="string">
      Voice ID for audio synthesis, must be set together with weight parameter. Supports system voice, cloned voice, and text-generated voice
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean">
  Controls whether to enable subtitle service, default is false. This parameter is only valid in non-streaming output scenarios, and only valid for speech-2.6-hd, speech-2.6-turbo, speech-01-turbo, speech-01-hd models
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string[]">
      Defines pronunciation or replacement rules for special characters or symbols. For Chinese text, tones are represented by numbers: 1st tone = 1, 2nd tone = 2, 3rd tone = 3, 4th tone = 4, neutral tone = 5. Example: \["omg/oh my god"]
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="data" type="object">
  Returned synthesis data object, may be null and needs null check

  <Expandable title="properties">
    <ResponseField name="audio" type="string">
      Synthesized audio data in hex encoding, format matches the output format specified in request
    </ResponseField>

    <ResponseField name="status" type="integer">
      Current audio stream status: 1 means synthesizing, 2 means synthesis completed
    </ResponseField>

    <ResponseField name="subtitle_file" type="string">
      Subtitle download link. Subtitles for the audio file, accurate to sentence (no more than 50 characters), in milliseconds, JSON format
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="trace_id" type="string">
  Session ID for this request, used for troubleshooting
</ResponseField>

<ResponseField name="base_resp" type="object">
  Status code and details for this request

  <Expandable title="properties">
    <ResponseField name="status_msg" type="string">
      Status details
    </ResponseField>

    <ResponseField name="status_code" type="integer">
      Status code. 0: success, 1000: unknown error, 1001: timeout, 1002: rate limit triggered, 1004: authentication failed, 1039: TPM rate limit triggered, 1042: invalid characters exceed 10%, 2013: invalid input parameters
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="extra_info" type="object">
  Additional audio information

  <Expandable title="properties">
    <ResponseField name="bitrate" type="integer">
      Audio bitrate
    </ResponseField>

    <ResponseField name="audio_size" type="integer">
      Audio file size in bytes
    </ResponseField>

    <ResponseField name="word_count" type="integer">
      Word count of pronounced text, includes Chinese characters, numbers, letters, excludes punctuation
    </ResponseField>

    <ResponseField name="audio_format" type="string">
      Generated audio file format. Options: \[mp3, pcm, flac]

      Optional values: `mp3`, `pcm`, `flac`
    </ResponseField>

    <ResponseField name="audio_length" type="integer">
      Audio duration in milliseconds
    </ResponseField>

    <ResponseField name="audio_channel" type="integer">
      Generated audio channel count, 1: mono, 2: stereo
    </ResponseField>

    <ResponseField name="usage_characters" type="integer">
      Billable character count
    </ResponseField>

    <ResponseField name="audio_sample_rate" type="integer">
      Audio sample rate
    </ResponseField>

    <ResponseField name="invisible_character_ratio" type="number">
      Invalid character ratio. If invalid characters do not exceed 10% (inclusive), audio will be generated normally with this ratio data returned; if exceeds 10%, an error will be returned
    </ResponseField>
  </Expandable>
</ResponseField>


# MiniMax Speech 2.8 HD Async Text-to-Speech
Source: https://docs.novita.ai/api-reference/model-apis-minimax-speech-2.8-hd-async

POST https://api.novita.ai/v3/async/minimax-speech-2.8-hd

MiniMax asynchronous text-to-speech API, supports various voice, emotion, speed and other parameter settings, text length limit up to 50,000 characters, supports file input (up to 100,000 characters)

<Tip>
  This is an **asynchronous** API. The response contains the **task\_id** and billing details. Use the **task\_id** to request the [**Task Result API**](/api-reference/model-apis-task-result) to retrieve the generated output.
</Tip>

## Request Headers

<ParamField type="string">
  Supports: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Text to synthesize into audio, maximum length is 50,000 characters. Either `text` or `text_file_id` is required.

  * Interjection tags: Only supported when model is `speech-2.8-hd` or `speech-2.8-turbo`. Supported interjections: `(laughs)` (laughter), `(chuckle)` (light laugh), `(coughs)` (cough), `(clear-throat)` (clear throat), `(groans)` (groan), `(breath)` (normal breathing), `(pant)` (panting), `(inhale)` (inhale), `(exhale)` (exhale), `(gasps)` (gasp), `(sniffs)` (sniff), `(sighs)` (sigh), `(snorts)` (snort), `(burps)` (burp), `(lip-smacking)` (lip smacking), `(humming)` (humming), `(hissing)` (hissing), `(emm)` (um), `(whistles)` (whistle), `(sneezes)` (sneeze), `(crying)` (crying), `(applause)` (applause)
</ParamField>

<ParamField type="integer">
  Text file ID for audio synthesis, single file length limit is less than 100,000 characters, supported file formats: txt, zip. Either `text` or `text_file_id` is required, format will be automatically validated.

  * **txt file**: Length limit \<100000 characters. Supports custom pause using `&lt;#x#&gt;` tag. x is pause duration (in seconds), range \[0.01, 99.99], up to 2 decimal places. Pause must be set between two pronounceable text segments, cannot use multiple pause tags consecutively
  * **zip file**:
    * Compressed package must contain txt or json files of the same format.
    * json file format: Supports \[`title`, `content`, `extra`] three fields, representing title, body, and additional information. If all three fields exist, 3 groups of results will be produced, 9 files in total, stored in one folder. If a field does not exist or is empty, no corresponding result will be generated
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="integer">
      Pitch adjustment (deep/bright), range \[-100, 100], values closer to -100 produce deeper voice; closer to 100 produce brighter voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Timbre adjustment (rich/crisp), range \[-100, 100], values closer to -100 produce richer voice; closer to 100 produce crisper voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Intensity adjustment (powerful/soft), range \[-100, 100], values closer to -100 produce more powerful voice; closer to 100 produce softer voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="string">
      Sound effect setting, only one can be selected at a time. Options:

      1. spacious\_echo (spacious echo)
      2. auditorium\_echo (auditorium broadcast)
      3. lofi\_telephone (telephone distortion)
      4. robotic (electronic)

      Optional values: `spacious_echo`, `auditorium_echo`, `lofi_telephone`, `robotic`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string">
      Audio output format. Options `[mp3, pcm, flac, wav, pcmu_raw, pcmu_wav, opus]`, default is `mp3`. `pcmu_raw` and `pcmu_wav` use G.711 μ-law encoding (sample rate 8 kHz; `pcmu_raw` is headerless raw data, `pcmu_wav` is wrapped in a WAV container). `opus` uses Ogg/Opus encoding, only supports sample rates `[8000, 12000, 16000, 24000, 48000]`; using other sample rates will cause task errors.

      Optional values: `mp3`, `pcm`, `flac`, `wav`, `pcmu_raw`, `pcmu_wav`, `opus`
    </ParamField>

    <ParamField type="integer">
      Audio bitrate. Options `[32000, 64000, 128000, 256000]`, default is `128000`. This parameter only applies to `mp3` format
    </ParamField>

    <ParamField type="integer">
      Number of audio channels. Options: `[1, 2]`, where `1` is mono and `2` is stereo, default is 1
    </ParamField>

    <ParamField type="integer">
      Audio sample rate. Options `[8000, 16000, 22050, 24000, 32000, 44100]`, default is `32000`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="number">
      Audio volume, higher value means louder. Range (0, 10], default is 1.0

      Value range: \[0, 10]
    </ParamField>

    <ParamField type="integer">
      Audio pitch, range `[-12, 12]`, default is 0, where 0 is original voice output

      Value range: \[-12, 12]
    </ParamField>

    <ParamField type="number">
      Speech speed, higher value means faster. Range `[0.5, 2]`, default is 1.0

      Value range: \[0.5, 2]
    </ParamField>

    <ParamField type="string">
      Controls the emotion of synthesized speech. Options `["happy", "sad", "angry", "fearful", "disgusted", "surprised", "calm", "fluent", "whisper"]` correspond to 8 emotions: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper

      * The model will automatically match appropriate emotion based on input text, usually no need to specify manually
      * This parameter only works for `speech-2.6-hd`, `speech-2.6-turbo`, `speech-01-hd`, `speech-01-turbo` models
      * Options `fluent`, `whisper` only work for `speech-2.6-turbo`, `speech-2.6-hd` models

      Optional values: `happy`, `sad`, `angry`, `fearful`, `disgusted`, `surprised`, `calm`, `fluent`, `whisper`
    </ParamField>

    <ParamField type="string">
      Voice ID for audio synthesis. If mixed voice is needed, set timber\_weights parameter and leave this empty. Supports system voice, cloned voice, and text-generated voice. Below are some of the latest system voices (ID)

      <ul>
        <li><strong>Chinese</strong>: moss\_audio\_ce44fc67-7ce3-11f0-8de5-96e35d26fb85, moss\_audio\_aaa1346a-7ce7-11f0-8e61-2e6e3c7ee85d, Chinese (Mandarin)\_Lyrical\_Voice, Chinese (Mandarin)\_HK\_Flight\_Attendant</li>
        <li><strong>English</strong>: English\_Graceful\_Lady, English\_Insightful\_Speaker, English\_radiant\_girl, English\_Persuasive\_Man, moss\_audio\_6dc281eb-713c-11f0-a447-9613c873494c, moss\_audio\_570551b1-735c-11f0-b236-0adeeecad052, moss\_audio\_ad5baf92-735f-11f0-8263-fe5a2fe98ec8, English\_Lucky\_Robot</li>
        <li><strong>Japanese</strong>: Japanese\_Whisper\_Belle, moss\_audio\_24875c4a-7be4-11f0-9359-4e72c55db738, moss\_audio\_7f4ee608-78ea-11f0-bb73-1e2a4cfcd245, moss\_audio\_c1a6a3ac-7be6-11f0-8e8e-36b92fbb4f95</li>
      </ul>
    </ParamField>

    <ParamField type="boolean">
      Supports English text normalization, which can improve performance in number reading scenarios but slightly increases latency, default false
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean">
  Controls whether to add audio rhythm identifier at the end of synthesized audio, default is False. This parameter is only valid for non-streaming synthesis
</ParamField>

<ParamField type="string">
  Whether to enhance recognition ability for specified minor languages and dialects. Default is `null`, can be set to `auto` to let the model decide automatically.

  Optional values: `Chinese`, `Chinese,Yue`, `English`, `Arabic`, `Russian`, `Spanish`, `French`, `Portuguese`, `German`, `Turkish`, `Dutch`, `Ukrainian`, `Vietnamese`, `Indonesian`, `Japanese`, `Italian`, `Korean`, `Thai`, `Polish`, `Romanian`, `Greek`, `Czech`, `Finnish`, `Hindi`, `Bulgarian`, `Danish`, `Hebrew`, `Malay`, `Persian`, `Slovak`, `Swedish`, `Croatian`, `Filipino`, `Hungarian`, `Norwegian`, `Slovenian`, `Catalan`, `Nynorsk`, `Tamil`, `Afrikaans`, `auto`
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string[]">
      Defines pronunciation or replacement rules for special characters or symbols. For Chinese text, tones are represented by numbers:
      1st tone = 1, 2nd tone = 2, 3rd tone = 3, 4th tone = 4, neutral tone = 5
      Example:
      `["omg/oh my god"]`
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="task_id" type="string">
  Use the task\_id to request the [Task Result API](/api-reference/model-apis-task-result) and retrieve the generated output.
</ResponseField>

<ResponseField name="details" type="object">
  Additional task details.

  <Expandable title="properties">
    <ResponseField name="usage_characters" type="integer">
      Number of billable characters.
    </ResponseField>
  </Expandable>
</ResponseField>


# MiniMax Speech 2.8 Turbo Sync Text-to-Speech
Source: https://docs.novita.ai/api-reference/model-apis-minimax-speech-2.8-turbo

POST https://api.novita.ai/v3/minimax-speech-2.8-turbo

MiniMax synchronous text-to-speech API using HTTP protocol. Supports various voice, emotion, speed and other parameter settings.

## Request Headers

<ParamField type="string">
  Supports: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Text to synthesize into speech, length limit is less than 10000 characters. If text length is greater than 3000 characters, streaming output is recommended. Supports paragraph breaks (newline), pause control (`&lt;#x#&gt;` tag), and interjection tags (such as (laughs), (coughs), etc., only supported by speech-2.8-hd/turbo)
</ParamField>

<ParamField type="boolean">
  Controls whether to enable streaming output. Default is false
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="integer">
      Pitch adjustment (deep/bright), range \[-100, 100]. Values closer to -100 produce deeper voice; closer to 100 produce brighter voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Timbre adjustment (rich/crisp), range \[-100, 100]. Values closer to -100 produce richer voice; closer to 100 produce crisper voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Intensity adjustment (powerful/soft), range \[-100, 100]. Values closer to -100 produce more powerful voice; closer to 100 produce softer voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="string">
      Sound effect setting, only one can be selected at a time. Options: spacious\_echo (spacious echo), auditorium\_echo (auditorium broadcast), lofi\_telephone (telephone distortion), robotic (electronic)

      Optional values: `spacious_echo`, `auditorium_echo`, `lofi_telephone`, `robotic`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string">
      Audio output format. wav is only supported in non-streaming output

      Optional values: `mp3`, `pcm`, `flac`, `wav`
    </ParamField>

    <ParamField type="integer">
      Audio bitrate. Options: \[32000, 64000, 128000, 256000], default is 128000. This parameter only applies to mp3 format

      Optional values: `32000`, `64000`, `128000`, `256000`
    </ParamField>

    <ParamField type="integer">
      Number of audio channels. Options: \[1, 2], where 1 is mono and 2 is stereo. Default is 1

      Optional values: `1`, `2`
    </ParamField>

    <ParamField type="boolean">
      Controls constant bitrate (CBR) encoding. When set to true, audio will be encoded with constant bitrate. Note: This parameter only works when streaming output is enabled and audio format is mp3
    </ParamField>

    <ParamField type="integer">
      Audio sample rate. Options: \[8000, 16000, 22050, 24000, 32000, 44100], default is 32000

      Optional values: `8000`, `16000`, `22050`, `24000`, `32000`, `44100`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Controls output format, options are url or hex, default is hex. This parameter is only valid in non-streaming scenarios. URL is valid for 24 hours

  Optional values: `url`, `hex`
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="number">
      Audio volume, higher value means louder. Range (0, 10], default is 1.0

      Value range: \[0, 10]
    </ParamField>

    <ParamField type="integer">
      Audio pitch, range \[-12, 12], default is 0, where 0 is original voice output

      Value range: \[-12, 12]
    </ParamField>

    <ParamField type="number">
      Speech speed, higher value means faster. Range \[0.5, 2], default is 1.0

      Value range: \[0.5, 2]
    </ParamField>

    <ParamField type="string">
      Controls the emotion of synthesized speech. Options correspond to 8 emotions: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper. The model will automatically match appropriate emotion based on input text

      Optional values: `happy`, `sad`, `angry`, `fearful`, `disgusted`, `surprised`, `calm`, `fluent`, `whisper`
    </ParamField>

    <ParamField type="string">
      Voice ID for audio synthesis. If mixed voice is needed, set timber\_weights parameter and leave this empty. Supports system voice, cloned voice, and text-generated voice
    </ParamField>

    <ParamField type="boolean">
      Controls whether to read LaTeX formulas, default is false. Only supports Chinese. When enabled, language\_boost will be set to Chinese
    </ParamField>

    <ParamField type="boolean">
      Whether to enable Chinese/English text normalization, which can improve performance in number reading scenarios but slightly increases latency. Default is false
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean">
  Controls whether to add audio rhythm identifier at the end of synthesized audio, default is false. This parameter is only valid for non-streaming synthesis
</ParamField>

<ParamField type="string">
  Whether to enhance recognition ability for specified minor languages and dialects. Default is null, can be set to auto to let the model decide automatically

  Optional values: `Chinese`, `Chinese,Yue`, `English`, `Arabic`, `Russian`, `Spanish`, `French`, `Portuguese`, `German`, `Turkish`, `Dutch`, `Ukrainian`, `Vietnamese`, `Indonesian`, `Japanese`, `Italian`, `Korean`, `Thai`, `Polish`, `Romanian`, `Greek`, `Czech`, `Finnish`, `Hindi`, `Bulgarian`, `Danish`, `Hebrew`, `Malay`, `Persian`, `Slovak`, `Swedish`, `Croatian`, `Filipino`, `Hungarian`, `Norwegian`, `Slovenian`, `Catalan`, `Nynorsk`, `Tamil`, `Afrikaans`, `auto`
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="boolean">
      Sets whether the last chunk contains the concatenated audio hex data. Default is false, meaning the last chunk contains the complete concatenated audio hex data
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Mixed voice settings, supports up to 4 voice mixtures

  <Expandable title="properties">
    <ParamField type="integer">
      Weight of each voice in the mix, must be set together with voice\_id. Range \[1, 100], supports up to 4 voice mixtures. Higher weight means more similarity to that voice

      Value range: \[1, 100]
    </ParamField>

    <ParamField type="string">
      Voice ID for audio synthesis, must be set together with weight parameter. Supports system voice, cloned voice, and text-generated voice
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean">
  Controls whether to enable subtitle service, default is false. This parameter is only valid in non-streaming output scenarios, and only valid for speech-2.6-hd, speech-2.6-turbo, speech-01-turbo, speech-01-hd models
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string[]">
      Defines pronunciation or replacement rules for special characters or symbols. For Chinese text, tones are represented by numbers: 1st tone = 1, 2nd tone = 2, 3rd tone = 3, 4th tone = 4, neutral tone = 5. Example: \["omg/oh my god"]
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="data" type="object">
  Returned synthesis data object, may be null and needs null check

  <Expandable title="properties">
    <ResponseField name="audio" type="string">
      Synthesized audio data in hex encoding, format matches the output format specified in request
    </ResponseField>

    <ResponseField name="status" type="integer">
      Current audio stream status: 1 means synthesizing, 2 means synthesis completed
    </ResponseField>

    <ResponseField name="subtitle_file" type="string">
      Subtitle download link. Subtitles for the audio file, accurate to sentence (no more than 50 characters), in milliseconds, JSON format
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="trace_id" type="string">
  Session ID for this request, used for troubleshooting
</ResponseField>

<ResponseField name="base_resp" type="object">
  Status code and details for this request

  <Expandable title="properties">
    <ResponseField name="status_msg" type="string">
      Status details
    </ResponseField>

    <ResponseField name="status_code" type="integer">
      Status code. `0`: success, `1000`: unknown error, `1001`: timeout, `1002`: rate limit triggered, `1004`: authentication failed, `1039`: TPM rate limit triggered, `1042`: invalid characters exceed 10%, `2013`: invalid input parameters
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="extra_info" type="object">
  Additional audio information

  <Expandable title="properties">
    <ResponseField name="bitrate" type="integer">
      Audio bitrate
    </ResponseField>

    <ResponseField name="audio_size" type="integer">
      Audio file size in bytes
    </ResponseField>

    <ResponseField name="word_count" type="integer">
      Word count of pronounced text, includes Chinese characters, numbers, letters, excludes punctuation
    </ResponseField>

    <ResponseField name="audio_format" type="string">
      Generated audio file format. Options: \[mp3, pcm, flac]

      Optional values: `mp3`, `pcm`, `flac`
    </ResponseField>

    <ResponseField name="audio_length" type="integer">
      Audio duration in milliseconds
    </ResponseField>

    <ResponseField name="audio_channel" type="integer">
      Generated audio channel count, `1`: mono, `2`: stereo
    </ResponseField>

    <ResponseField name="usage_characters" type="integer">
      Billable character count
    </ResponseField>

    <ResponseField name="audio_sample_rate" type="integer">
      Audio sample rate
    </ResponseField>

    <ResponseField name="invisible_character_ratio" type="number">
      Invalid character ratio. If invalid characters do not exceed 10% (inclusive), audio will be generated normally with this ratio data returned; if exceeds 10%, an error will be returned
    </ResponseField>
  </Expandable>
</ResponseField>


# MiniMax Speech 2.8 Turbo Async Text-to-Speech
Source: https://docs.novita.ai/api-reference/model-apis-minimax-speech-2.8-turbo-async

POST https://api.novita.ai/v3/async/minimax-speech-2.8-turbo

MiniMax asynchronous text-to-speech API, supports various voice, emotion, speed and other parameter settings, text length limit up to 50,000 characters, supports file input (up to 100,000 characters)

<Tip>
  This is an **asynchronous** API; only the **task\_id** will be returned. You should use the **task\_id** to request the [**Task Result API**](/api-reference/model-apis-task-result) to retrieve the video generation results.
</Tip>

## Request Headers

<ParamField type="string">
  Supports: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Text to synthesize into audio, maximum length is 50,000 characters. Either `text` or `text_file_id` is required.<br /><br />• Interjection tags: Only supported when model is `speech-2.8-hd` or `speech-2.8-turbo`. Supported interjections: `(laughs)` (laughter), `(chuckle)` (light laugh), `(coughs)` (cough), `(clear-throat)` (clear throat), `(groans)` (groan), `(breath)` (normal breathing), `(pant)` (panting), `(inhale)` (inhale), `(exhale)` (exhale), `(gasps)` (gasp), `(sniffs)` (sniff), `(sighs)` (sigh), `(snorts)` (snort), `(burps)` (burp), `(lip-smacking)` (lip smacking), `(humming)` (humming), `(hissing)` (hissing), `(emm)` (um), `(whistles)` (whistle), `(sneezes)` (sneeze), `(crying)` (crying), `(applause)` (applause)
</ParamField>

<ParamField type="integer">
  Text file ID for audio synthesis, single file length limit is less than 100,000 characters, supported file formats: txt, zip. Either `text` or `text_file_id` is required, format will be automatically validated.<br />• txt file: Length limit \<100000 characters. Supports custom pause using `&lt;#x#&gt;` tag. x is pause duration (in seconds), range \[0.01, 99.99], up to 2 decimal places. Pause must be set between two pronounceable text segments, cannot use multiple pause tags consecutively<br />• zip file:<br />• Compressed package must contain txt or json files of the same format.<br />• json file format: Supports \[`title`, `content`, `extra`] three fields, representing title, body, and additional information. If all three fields exist, 3 groups of results will be produced, 9 files in total, stored in one folder. If a field does not exist or is empty, no corresponding result will be generated
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="integer">
      Pitch adjustment (deep/bright), range \[-100, 100], values closer to -100 produce deeper voice; closer to 100 produce brighter voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Timbre adjustment (rich/crisp), range \[-100, 100], values closer to -100 produce richer voice; closer to 100 produce crisper voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="integer">
      Intensity adjustment (powerful/soft), range \[-100, 100], values closer to -100 produce more powerful voice; closer to 100 produce softer voice

      Value range: \[-100, 100]
    </ParamField>

    <ParamField type="string">
      Sound effect setting, only one can be selected at a time. Options:

      1. spacious\_echo (spacious echo)
      2. auditorium\_echo (auditorium broadcast)
      3. lofi\_telephone (telephone distortion)
      4. robotic (electronic)

      Optional values: `spacious_echo`, `auditorium_echo`, `lofi_telephone`, `robotic`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string">
      Audio output format. Options `[mp3, pcm, flac]`, default is `mp3`

      Optional values: `mp3`, `pcm`, `flac`
    </ParamField>

    <ParamField type="integer">
      Audio bitrate. Options `[32000, 64000, 128000, 256000]`, default is `128000`. This parameter only applies to `mp3` format
    </ParamField>

    <ParamField type="integer">
      Number of audio channels. Options: `[1, 2]`, where `1` is mono and `2` is stereo, default is 1
    </ParamField>

    <ParamField type="integer">
      Audio sample rate. Options `[8000, 16000, 22050, 24000, 32000, 44100]`, default is `32000`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="number">
      Audio volume, higher value means louder. Range (0, 10], default is 1.0

      Value range: \[0, 10]
    </ParamField>

    <ParamField type="integer">
      Audio pitch, range `[-12, 12]`, default is 0, where 0 is original voice output

      Value range: \[-12, 12]
    </ParamField>

    <ParamField type="number">
      Speech speed, higher value means faster. Range `[0.5, 2]`, default is 1.0

      Value range: \[0.5, 2]
    </ParamField>

    <ParamField type="string">
      Controls the emotion of synthesized speech. Options `["happy", "sad", "angry", "fearful", "disgusted", "surprised", "calm", "fluent", "whisper"]` correspond to 8 emotions: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper
      <br />• The model will automatically match appropriate emotion based on input text, usually no need to specify manually\
      <br />• This parameter only works for `speech-2.6-hd`, `speech-2.6-turbo`, `speech-01-hd`, `speech-01-turbo` models
      <br />• Options `fluent`, `whisper` only work for `speech-2.6-turbo`, `speech-2.6-hd` models

      Optional values: `happy`, `sad`, `angry`, `fearful`, `disgusted`, `surprised`, `calm`, `fluent`, `whisper`
    </ParamField>

    <ParamField type="string">
      Voice ID for audio synthesis. If mixed voice is needed, set timber\_weights parameter and leave this empty. Supports system voice, cloned voice, and text-generated voice. Below are some of the latest system voices (ID)
      <br />• **Chinese**:<br />• moss\_audio\_ce44fc67-7ce3-11f0-8de5-96e35d26fb85<br />• moss\_audio\_aaa1346a-7ce7-11f0-8e61-2e6e3c7ee85d<br />• Chinese (Mandarin)\_Lyrical\_Voice<br />• Chinese (Mandarin)\_HK\_Flight\_Attendant<br />• English:<br />• English\_Graceful\_Lady<br />• English\_Insightful\_Speaker<br />• English\_radiant\_girl<br />• English\_Persuasive\_Man<br />• moss\_audio\_6dc281eb-713c-11f0-a447-9613c873494c<br />• moss\_audio\_570551b1-735c-11f0-b236-0adeeecad052<br />• moss\_audio\_ad5baf92-735f-11f0-8263-fe5a2fe98ec8<br />• English\_Lucky\_Robot<br />• Japanese:<br />• Japanese\_Whisper\_Belle<br />• moss\_audio\_24875c4a-7be4-11f0-9359-4e72c55db738<br />• moss\_audio\_7f4ee608-78ea-11f0-bb73-1e2a4cfcd245<br />• moss\_audio\_c1a6a3ac-7be6-11f0-8e8e-36b92fbb4f95
    </ParamField>

    <ParamField type="boolean">
      Supports English text normalization, which can improve performance in number reading scenarios but slightly increases latency, default false
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="boolean">
  Controls whether to add audio rhythm identifier at the end of synthesized audio, default is False. This parameter is only valid for non-streaming synthesis
</ParamField>

<ParamField type="string">
  Whether to enhance recognition ability for specified minor languages and dialects. Default is `null`, can be set to `auto` to let the model decide automatically.

  Optional values: `Chinese`, `Chinese,Yue`, `English`, `Arabic`, `Russian`, `Spanish`, `French`, `Portuguese`, `German`, `Turkish`, `Dutch`, `Ukrainian`, `Vietnamese`, `Indonesian`, `Japanese`, `Italian`, `Korean`, `Thai`, `Polish`, `Romanian`, `Greek`, `Czech`, `Finnish`, `Hindi`, `Bulgarian`, `Danish`, `Hebrew`, `Malay`, `Persian`, `Slovak`, `Swedish`, `Croatian`, `Filipino`, `Hungarian`, `Norwegian`, `Slovenian`, `Catalan`, `Nynorsk`, `Tamil`, `Afrikaans`, `auto`
</ParamField>

<ParamField type="object">
  <Expandable title="properties">
    <ParamField type="string[]">
      Defines pronunciation or replacement rules for special characters or symbols. For Chinese text, tones are represented by numbers:
      1st tone = 1, 2nd tone = 2, 3rd tone = 3, 4th tone = 4, neutral tone = 5
      Example:
      `["omg/oh my god"]`
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="task_id" type="string">
  Use the task\_id to request the [Task Result API](/api-reference/model-apis-task-result) and retrieve the generated output.
</ResponseField>

<ResponseField name="details" type="object">
  Additional task details.

  <Expandable title="properties">
    <ResponseField name="usage_characters" type="integer">
      Number of billable characters.
    </ResponseField>
  </Expandable>
</ResponseField>


# MiniMax Quick Voice Cloning
Source: https://docs.novita.ai/api-reference/model-apis-minimax-voice-cloning

POST https://api.novita.ai/v3/minimax-voice-cloning

This interface will allow users to efficiently clone a voice, as specified in the audio file. Potential use cases include: IP voice cloning, voice cloning, and other scenarios where quick voice cloning is required.
The quick voice clone generated by this API is temporary. If you wish to permanently retain a cloned voice, you must use this voice in any T2A speech synthesis API within 168 hours (7 days) (excluding preview actions within this API). Otherwise, the cloned voice will be deleted.

Applicable scenarios for this API include: IP voice cloning, timbre cloning, and other use cases requiring rapid voice cloning.

Notes:

* The uploaded audio file must be in mp3, m4a, or wav format.
* The duration of the uploaded audio must be at least 10 seconds and no more than 5 minutes.
* The uploaded audio file size must not exceed 20 MB.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The URL of the audio file to be cloned. Supported formats: mp3, m4a, wav.
</ParamField>

<ParamField>
  Voice cloning parameters. Providing this parameter can help improve the similarity and stability of the synthesized voice.

  If this parameter is used, you must also upload a short sample audio (duration less than 8 seconds) and the corresponding transcript. Supported audio formats: mp3, m4a, wav.

  <Expandable title="properties">
    <ParamField type="string">
      Audio prompt parameter. The URL of the sample audio. Duration must be less than 8 seconds.
    </ParamField>

    <ParamField type="string">
      Audio prompt parameter. The transcript corresponding to the sample audio. The text must match the audio content, and end with punctuation.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Voice cloning preview parameter. The model will use the cloned voice to synthesize this text and return the result as an audio URL for preview. Maximum 2000 characters. Note: Preview will be charged according to the number of characters, at the same rate as T2A APIs.
</ParamField>

<ParamField type="string">
  Voice cloning preview parameter. Specifies the speech model to use for preview. Required if the "text" field is provided.<br />
  Options: `speech-2.6-hd`, `speech-2.6-turbo`, `speech-2.8-hd`, `speech-2.8-turbo`
</ParamField>

<ParamField type="float">
  Voice cloning parameter. Value range: \[0, 1]. If provided, sets the text validation accuracy threshold. Default is 0.7 if not specified.
</ParamField>

<ParamField type="bool">
  Voice cloning parameter. Whether to enable noise reduction. Defaults to false if not specified.
</ParamField>

<ParamField type="bool">
  Voice cloning parameter. Whether to enable volume normalization. Defaults to false if not specified.
</ParamField>

## Response

<ResponseField name="demo_audio_url" type="string">
  If both the preview text (<code>text</code>) and preview model (<code>model</code>) are provided in the request body, this parameter returns the preview audio as a URL.
</ResponseField>

<ResponseField name="voice_id" type="string">
  The generated <code>voice\_id</code>.
</ResponseField>

## Example

Below is an example of how to use the Minimax Voice Cloning API to clone a voice.

`Request:`

```bash theme={"system"}
curl \
-X POST https://api.novita.ai/v3/minimax-voice-cloning \
-H "Authorization: Bearer $your_api_key" \
-H "Content-Type: application/json" \
-d '{
  "audio_url": "https://example.com/voice.mp3",
  "text": "Audio generation technology is evolving rapidly, enabling the creation of speech, music, and sound effects from text or data inputs. It supports applications in media, accessibility, customer service, and content creation. With improved quality and customization, these tools are increasingly integrated into digital platforms across various industries.",
  "model": "speech-01-hd",
  "need_noise_reduction": true,
  "need_volume_normalization": true
}'
```

`Response:`

```js theme={"system"}
{
  "demo_audio_url": "https://demo.com/audio.mp3", // Audio sample
  "voice_id": "xxxxxxx" // Generated voice_id
}
```


# MiniMax Voice Design
Source: https://docs.novita.ai/api-reference/model-apis-minimax-voice-design

POST https://api.novita.ai/v3/minimax-voice-design

Generate personalized custom voices from text descriptions. Returns a voice\_id for use in T2A speech synthesis APIs, along with a hex-encoded preview audio sample.

## Request Headers

<ParamField type="string">
  Supports: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Voice description text that defines the characteristics of the voice to generate.
</ParamField>

<ParamField type="string">
  Custom voice ID for the generated voice. If not provided, a unique voice\_id will be automatically created.
</ParamField>

<ParamField type="string">
  Text used for generating a preview audio sample. Max 500 characters.

  Length limit: 0 - 500
</ParamField>

## Response

<ResponseField name="voice_id" type="string">
  The generated voice ID, usable in T2A speech synthesis APIs.
</ResponseField>


# Task Result
Source: https://docs.novita.ai/api-reference/model-apis-task-result

GET https://api.novita.ai/v3/async/task-result

**This API is used to retrieve images, audio, or video results from v3 asynchronous tasks using the `task_id`.**

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Returned in the 200 response of v3 asynchronous APIs.
</ParamField>

## Response

<ResponseField name="extra" type="object">
  <Expandable title="properties">
    <ResponseField name="seed" type="string">
      A seed is a number from which Stable Diffusion generates noise, making generation deterministic. Using the same seed and set of parameters will produce identical images each time, minimum -1.
    </ResponseField>

    <ResponseField name="enable_nsfw_detection" type="boolean">
      When set to true, NSFW detection will be enabled, incurring an additional cost of \$0.0015 for each generated image.
    </ResponseField>

    <ResponseField name="debug_info" type="object">
      <Expandable title="properties">
        <ResponseField name="request_info" type="string">
          Records request parameters for debugging purposes.
        </ResponseField>

        <ResponseField name="submit_time_ms" type="string">
          The timestamp (in milliseconds) of when the task was submitted.
        </ResponseField>

        <ResponseField name="execute_time_ms" type="string">
          The timestamp (in milliseconds) of when the task started execution.
        </ResponseField>

        <ResponseField name="complete_time_ms" type="string">
          The timestamp (in milliseconds) of when the task was completed.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="task" type="object">
  Task information.

  <Expandable title="properties">
    <ResponseField name="task_id" type="string">
      Task ID.
    </ResponseField>

    <ResponseField name="status" type="string">
      Indicates the current status of a task. This parameter allows you to filter results based on the following status categories: `TASK_STATUS_QUEUED`: The task is queued and awaiting processing. `TASK_STATUS_SUCCEED`: The task has been processed successfully. `TASK_STATUS_FAILED`: The task failed during processing. `TASK_STATUS_PROCESSING`: The task is currently being processed.<br />
      Enum: `TASK_STATUS_QUEUED`, `TASK_STATUS_SUCCEED`, `TASK_STATUS_FAILED`, `TASK_STATUS_PROCESSING`
    </ResponseField>

    <ResponseField name="reason" type="string">
      Provides the reason for a task's failure. This parameter specifies the explanation or error message associated with tasks that did not succeed. Utilizing this filter can aid in diagnosing issues and implementing corrective actions for failed processes.
    </ResponseField>

    <ResponseField name="task_type" type="string">
      Specifies the type of task.
    </ResponseField>

    <ResponseField name="eta" type="number">
      Specifies the estimated time of completion for tasks, measured in seconds. This parameter is particularly relevant for video-type tasks, providing a forecast of how long it will take to complete the task based on current processing parameters and system load. This information can be vital for planning and resource management in environments with time-sensitive video processing requirements.
    </ResponseField>

    <ResponseField name="progress_percent" type="number">
      Progress of task completion in percentage. This feature is currently available only for: <br /> 1. Video Generator APIs; <br /> 2. Text-to-Image API and Image-to-Image API.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="images" type="object[]">
  Contains information about images associated with image-type tasks. This parameter provides detailed data on each image processed or generated during the task, such as file paths, metadata, or any image-specific attributes. It is returned only for tasks that involve image operations, facilitating enhanced tracking and management of image data.

  <Expandable title="properties">
    <ResponseField name="image_url" type="string">
      Image URL.
    </ResponseField>

    <ResponseField name="image_url_ttl" type="integer">
      Image expiration time in seconds.
    </ResponseField>

    <ResponseField name="image_type" type="string">
      Image type.<br />
      Enum: `jpeg`, `png`, `webp`
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="videos" type="object[]">
  Contains information about videos associated with video-type tasks. This parameter provides detailed data on each video processed or generated during the task, such as file paths, metadata, or any video-specific attributes. It is returned only for tasks that involve video operations, facilitating enhanced tracking and management of video data.

  <Expandable title="properties">
    <ResponseField name="video_url" type="string">
      Video URL.
    </ResponseField>

    <ResponseField name="video_url_ttl" type="string">
      Video expiration time in seconds.
    </ResponseField>

    <ResponseField name="video_type" type="string">
      Video type.<br />
      Enum: `mp4`, `gif`
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="audios" type="object[]">
  Contains information about audios associated with audio type tasks. This parameter provides detailed data on each audio processed or generated during the task, such as file paths, metadata, or any image-specific attributes. It is returned only for tasks that involve audio operations, facilitating enhanced tracking and management of audio data.

  <Expandable title="properties">
    <ResponseField name="audio_url" type="string">
      Audio url.
    </ResponseField>

    <ResponseField name="audio_url_ttl" type="string">
      Audio url expire time in seconds.
    </ResponseField>

    <ResponseField name="audio_type" type="string">
      Audio type.<br />
      Enum: `wav`
    </ResponseField>

    <ResponseField name="audio_metadata" type="object">
      Contains detailed metadata about audio files processed or generated during audio-related tasks.

      <Expandable title="properties">
        <ResponseField name="text" type="string" />

        <ResponseField name="start_time" type="float32">
          the start\_time of the text in seconds.
        </ResponseField>

        <ResponseField name="end_time" type="float32">
          the end\_time of the text in seconds.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Example

request

```bash theme={"system"}
curl --location --globoff 'https://api.novita.ai/v3/async/task-result?task_id={{Novita-TaskID}}' \
--header 'Authorization: Bearer {{API Key}}'
```

response

```json theme={"system"}
{
  "extra": {
    "seed": "158881667",
    "enable_nsfw_detection": false,
    "debug_info": {
      "request_info": "",
      "submit_time_ms": "",
      "execute_time_ms": "",
      "complete_time_ms": ""
    }
  },
  "task": {
    "task_id": "830d9c90-d53f-4c5a-8009-xxx",
    "status": "TASK_STATUS_SUCCEED",
    "reason": "",
    "task_type": "TXT_TO_IMG",
    "eta": 0,
    "progress_percent": 0
  },
  "images": [
    {
      "image_url": "https://faas-output-image.s3.ap-southeast-1.amazonaws.com/prod/xxx",
      "image_url_ttl": 3600,
      "image_type": "jpeg",
      "nsfw_detection_result": {}
    }
  ],
  "videos": [],
  "audios": []
}
```


# Tavily Crawl
Source: https://docs.novita.ai/api-reference/model-apis-tavily-crawl

POST https://api.novita.ai/v3/tavily/crawl

Tavily is a web data retrieval API designed for developers and AI applications. This document describes the Tavily passthrough endpoints exposed by the platform gateway. It is based on Tavily's Crawl API with platform route prefixes applied.

Base URL example: `https://api.novita.ai`

Crawl a site starting from a root URL, following links to retrieve content from multiple pages.

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer <api_key>`.
</ParamField>

## Request Body

<ParamField type="string">
  Crawl root URL.
</ParamField>

<ParamField type="string">
  Crawler instructions in natural language.
</ParamField>

<ParamField type="integer">
  Max number of content snippets per source to return. Default is `3`; range is `1` to `5`. Available only when `instructions` are set.
</ParamField>

<ParamField type="integer">
  Crawler depth limit. Default is `1`; range is `1` to `5`.
</ParamField>

<ParamField type="integer">
  Max links per page. Default is `20`; range is `1` to `500`.
</ParamField>

<ParamField type="integer">
  Link processing limit for crawler. Default is `50`; minimum is `1`.
</ParamField>

<ParamField type="string[]">
  Restrict URL paths matching these regex patterns.
</ParamField>

<ParamField type="string[]">
  Restrict domains matching these regex patterns.
</ParamField>

<ParamField type="string[]">
  Exclude URL paths matching these regex patterns.
</ParamField>

<ParamField type="string[]">
  Exclude domains matching these regex patterns.
</ParamField>

<ParamField type="boolean">
  Include external domain links.
</ParamField>

<ParamField type="boolean">
  Returns a list of images along with the response.
</ParamField>

<ParamField type="string">
  Extraction mode. Known values include `basic` and `advanced`. `basic` is set as the default value.
</ParamField>

<ParamField type="string">
  Output format of extracted content. Common values include `markdown` and `text`. `markdown` is set as the default value.
</ParamField>

<ParamField type="boolean">
  Include favicon URL from result.
</ParamField>

<ParamField type="float">
  Crawl operation time limit in seconds. Default is `150`; range is `10` to `150`.
</ParamField>

## Request Example

```bash theme={"system"}
curl -s -X POST 'https://api.novita.ai/v3/tavily/crawl' \
  -H 'Authorization: Bearer sk-123' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://docs.tavily.com",
    "chunks_per_source": 3,
    "extract_depth": "basic",
    "format": "markdown",
    "max_depth": 1,
    "max_breadth": 5,
    "limit": 5,
    "allow_external": false,
    "include_images": false,
    "timeout": 120
  }'
```

## Response

<ResponseField name="base_url" type="string">
  Crawl base URL.
</ResponseField>

<ResponseField name="results" type="object[]">
  Extracted content from crawled URLs. See [Result Object](#result-object).
</ResponseField>

<ResponseField name="response_time" type="float">
  Request duration in seconds.
</ResponseField>

<ResponseField name="request_id" type="string">
  Unique request identifier.
</ResponseField>

### Result Object

<ResponseField name="url" type="string">
  Crawled URL.
</ResponseField>

<ResponseField name="raw_content" type="string">
  Full page content.
</ResponseField>

<ResponseField name="favicon" type="string">
  Site favicon URL. Returned if `include_favicon` is `true`.
</ResponseField>

## Errors

The platform may return standard HTTP errors before forwarding the request, and Tavily may return upstream errors after forwarding.

<ResponseField name="400" type="status">
  Invalid request body or unsupported parameter value.
</ResponseField>

<ResponseField name="401" type="status">
  Missing or invalid API key.
</ResponseField>

<ResponseField name="403" type="status">
  Access denied by platform or upstream provider, or the URL is not supported.
</ResponseField>

<ResponseField name="404" type="status">
  Route or requested resource not found.
</ResponseField>

<ResponseField name="429" type="status">
  Rate limit exceeded.
</ResponseField>

<ResponseField name="500" type="status">
  Internal server error.
</ResponseField>

<ResponseField name="502" type="status">
  Upstream provider error.
</ResponseField>

<ResponseField name="503" type="status">
  Service unavailable.
</ResponseField>

## Notes

* All request bodies are JSON.
* Extra Tavily parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Tavily Crawl API reference](https://docs.tavily.com/documentation/api-reference/endpoint/crawl).


# Tavily Extract
Source: https://docs.novita.ai/api-reference/model-apis-tavily-extract

POST https://api.novita.ai/v3/tavily/extract

Tavily is a web data retrieval API designed for developers and AI applications. This document describes the Tavily passthrough endpoints exposed by the platform gateway. It is based on Tavily's Extract API with platform route prefixes applied.

Base URL example: `https://api.novita.ai`

Extract web page content from one or more specified URLs.

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer <api_key>`.
</ParamField>

## Request Body

<ParamField type="string | string[]">
  URL(s) to extract.
</ParamField>

<ParamField type="string">
  Query for snippet reranking.
</ParamField>

<ParamField type="integer">
  Max number of content snippets per source to return. Default is `3`; range is `1` to `5`. Available only when `query` is provided.
</ParamField>

<ParamField type="string">
  Extraction mode. Common values include `basic` and `advanced`. `basic` is set as the default value.
</ParamField>

<ParamField type="boolean">
  Returns a list of images from the response URLs.
</ParamField>

<ParamField type="boolean">
  Include favicon URL from result.
</ParamField>

<ParamField type="string">
  Extracted Content Format. Common values include `markdown` and `text`.
</ParamField>

<ParamField type="float">
  URL extraction time limit in seconds. Range is `1.0` to `60.0`. `10` for `basic`, `30` for `advanced`.
</ParamField>

## Request Example

```bash theme={"system"}
curl -s -X POST 'https://api.novita.ai/v3/tavily/extract' \
  -H 'Authorization: Bearer sk-123' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://en.wikipedia.org/wiki/Lionel_Messi"],
    "query": "career highlights and achievements",
    "chunks_per_source": 3,
    "extract_depth": "basic",
    "include_images": false,
    "format": "markdown",
    "timeout": 15
  }'
```

## Response

<ResponseField name="results" type="object[]">
  Extracted results from URL(s). See [Result Object](#result-object).
</ResponseField>

<ResponseField name="failed_results" type="object[]">
  Unprocessable URL(s). See [Failed Result Object](#failed-result-object).
</ResponseField>

<ResponseField name="response_time" type="float">
  Request duration in seconds.
</ResponseField>

<ResponseField name="request_id" type="string">
  Unique request identifier.
</ResponseField>

### Result Object

<ResponseField name="url" type="string">
  Response content URL.
</ResponseField>

<ResponseField name="raw_content" type="string">
  Full page content.
</ResponseField>

<ResponseField name="images" type="string[]">
  Image URLs from page URL. Returned if `include_images` is `true`.
</ResponseField>

<ResponseField name="favicon" type="string">
  Site favicon URL. Returned if `include_favicon` is `true`.
</ResponseField>

### Failed Result Object

<ResponseField name="url" type="string">
  Unprocessed URL(s)
</ResponseField>

<ResponseField name="error" type="string">
  URL processing error message.
</ResponseField>

## Errors

The platform may return standard HTTP errors before forwarding the request, and Tavily may return upstream errors after forwarding.

<ResponseField name="400" type="status">
  Invalid request body or unsupported parameter value. May occur if over 20 URLs are provided.
</ResponseField>

<ResponseField name="401" type="status">
  Missing or invalid API key.
</ResponseField>

<ResponseField name="403" type="status">
  Access denied by platform or upstream provider.
</ResponseField>

<ResponseField name="404" type="status">
  Route or requested resource not found.
</ResponseField>

<ResponseField name="429" type="status">
  Rate limit exceeded.
</ResponseField>

<ResponseField name="500" type="status">
  Internal server error.
</ResponseField>

<ResponseField name="502" type="status">
  Upstream provider error.
</ResponseField>

<ResponseField name="503" type="status">
  Service unavailable.
</ResponseField>

## Notes

* All request bodies are JSON.
* Extra Tavily parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Tavily Extract API reference](https://docs.tavily.com/documentation/api-reference/endpoint/extract).


# Tavily Map
Source: https://docs.novita.ai/api-reference/model-apis-tavily-map

POST https://api.novita.ai/v3/tavily/map

Tavily is a web data retrieval API designed for developers and AI applications. This document describes the Tavily passthrough endpoints exposed by the platform gateway. It is based on Tavily's Map API with platform route prefixes applied.

Base URL example: `https://api.novita.ai`

Map a site starting from a root URL, following links to discover the structure of reachable pages and return their URLs.

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer <api_key>`.
</ParamField>

## Request Body

<ParamField type="string">
  Crawl root URL.
</ParamField>

<ParamField type="string">
  Crawler instructions in natural language.
</ParamField>

<ParamField type="integer">
  Crawler depth limit. Default is `1`; range is `1` to `5`.
</ParamField>

<ParamField type="integer">
  Max links per page. Default is `20`; range is `1` to `500`.
</ParamField>

<ParamField type="integer">
  Link processing limit for crawler. Default is `50`; minimum is `1`.
</ParamField>

<ParamField type="string[]">
  Restrict URL paths matching these regex patterns.
</ParamField>

<ParamField type="string[]">
  Restrict domains matching these regex patterns.
</ParamField>

<ParamField type="string[]">
  Exclude URL paths matching these regex patterns.
</ParamField>

<ParamField type="string[]">
  Exclude domains matching these regex patterns.
</ParamField>

<ParamField type="boolean">
  Include external domain links.
</ParamField>

<ParamField type="float">
  Crawl operation time limit in seconds. Default is `150`; range is `10` to `150`.
</ParamField>

## Request Example

```bash theme={"system"}
curl -s -X POST 'https://api.novita.ai/v3/tavily/map' \
  -H 'Authorization: Bearer sk-123' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "docs.tavily.com",
    "instructions": "Find all pages about the Python SDK",
    "max_depth": 2,
    "max_breadth": 20,
    "limit": 50,
    "select_paths": ["/docs/.*"],
    "exclude_paths": ["/admin/.*"],
    "allow_external": false,
    "timeout": 60
  }'
```

## Response

<ResponseField name="base_url" type="string">
  Crawl base URL.
</ResponseField>

<ResponseField name="results" type="string[]">
  URLs discovered during mapping.
</ResponseField>

<ResponseField name="response_time" type="float">
  Request duration in seconds.
</ResponseField>

<ResponseField name="request_id" type="string">
  Unique request identifier.
</ResponseField>

## Errors

The platform may return standard HTTP errors before forwarding the request, and Tavily may return upstream errors after forwarding.

<ResponseField name="400" type="status">
  Invalid request body or unsupported parameter value.
</ResponseField>

<ResponseField name="401" type="status">
  Missing or invalid API key.
</ResponseField>

<ResponseField name="403" type="status">
  Access denied by platform or upstream provider, or the URL is not supported.
</ResponseField>

<ResponseField name="404" type="status">
  Route or requested resource not found.
</ResponseField>

<ResponseField name="429" type="status">
  Rate limit exceeded.
</ResponseField>

<ResponseField name="500" type="status">
  Internal server error.
</ResponseField>

<ResponseField name="502" type="status">
  Upstream provider error.
</ResponseField>

<ResponseField name="503" type="status">
  Service unavailable.
</ResponseField>

## Notes

* All request bodies are JSON.
* Extra Tavily parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Tavily Map API reference](https://docs.tavily.com/documentation/api-reference/endpoint/map).


# Tavily Search
Source: https://docs.novita.ai/api-reference/model-apis-tavily-search

POST https://api.novita.ai/v3/tavily/search

Tavily is a web data retrieval API designed for developers and AI applications. This document describes the Tavily passthrough endpoints exposed by the platform gateway. It is based on Tavily's Search API with platform route prefixes applied.

Base URL example: `https://api.novita.ai`

Search the internet by keyword or natural-language question and retrieve a list of matching results (title, URL, summary/snippet, etc.).

## Request Headers

All endpoints require platform API authentication.

<ParamField type="string">
  Use `application/json`.
</ParamField>

<ParamField type="string">
  Platform API key, formatted as `Bearer <api_key>`.
</ParamField>

## Request Body

<ParamField type="string">
  Search query. Minimum length is 1.
</ParamField>

<ParamField type="string[]">
  Search mode. Common values include `advanced`, `basic`, `fast`, `ultra-fast`. `basic` is set as the default value.
</ParamField>

<ParamField type="integer">
  Max number of content snippets per source to return. Default is `3`; range is `1` to `3`. Available only when `search_depth` is `advanced`.
</ParamField>

<ParamField type="integer">
  Max number of results to return. Default is `5`; supported public range is `1` to `20`.
</ParamField>

<ParamField type="string[]">
  Data category hint. Known values include `news`, `general`, and `finance`.
</ParamField>

<ParamField type="string[]">
  Include links published or updated from the time range to now. Common values include `day`,`d`, `week`, `w`, `month`, `m`, `year`, and `y`.
</ParamField>

<ParamField type="string">
  Include links published after this YYYY-MM-DD date-time.
</ParamField>

<ParamField type="string">
  Include links published before this YYYY-MM-DD date-time.
</ParamField>

<ParamField type="boolean | string[]">
  Returns an LLM-generated answer. `basic` and `true` returns quick answers; `advanced` returns detailed answers.
</ParamField>

<ParamField type="boolean | string[]">
  Returns result in HTML content. `markdown` or `true` returns in markdown; `text` returns in plain text.
</ParamField>

<ParamField type="boolean ">
  Returns a list of images along with the response.
</ParamField>

<ParamField type="boolean ">
  Includes a description to each image. Only works when `include_images` is `true`.
</ParamField>

<ParamField type="boolean ">
  Include favicon URL from result.
</ParamField>

<ParamField type="string[] ">
  Restrict results to these domains. Maximum 300 domains.
</ParamField>

<ParamField type="string[] ">
  Exclude results from these domains. Maximum 150 domains.
</ParamField>

<ParamField type="string[]">
  Prioritize results from specified country. Only works when topic is `general`. Known values include `afghanistan`, `albania`, `algeria`, `andorra`, `angola`, `argentina`, `armenia`, `australia`, `austria`, `azerbaijan`, `bahamas`, `bahrain`, `bangladesh`, `barbados`, `belarus`, `belgium`, `belize`, `benin`, `bhutan`, `bolivia`, `bosnia and herzegovina`, `botswana`, `brazil`, `brunei`, `bulgaria`, `burkina faso`, `burundi`, `cambodia`, `cameroon`, `canada`, `cape verde`, `central african republic`, `chad`, `chile`, `china`, `colombia`, `comoros`, `congo`, `costa rica`, `croatia`, `cuba`, `cyprus`, `czech republic`, `denmark`, `djibouti`, `dominican republic`, `ecuador`, `egypt`, `el salvador`, `equatorial guinea`, `eritrea`, `estonia`, `ethiopia`, `fiji`, `finland`, `france`, `gabon`, `gambia`, `georgia`, `germany`, `ghana`, `greece`, `guatemala`, `guinea`, `haiti`, `honduras`, `hungary`, `iceland`, `india`, `indonesia`, `iran`, `iraq`, `ireland`, `israel`, `italy`, `jamaica`, `japan`, `jordan`, `kazakhstan`, `kenya`, `kuwait`, `kyrgyzstan`, `latvia`, `lebanon`, `lesotho`, `liberia`, `libya`, `liechtenstein`, `lithuania`, `luxembourg`, `madagascar`, `malawi`, `malaysia`, `maldives`, `mali`, `malta`, `mauritania`, `mauritius`, `mexico`, `moldova`, `monaco`, `mongolia`, `montenegro`, `morocco`, `mozambique`, `myanmar`, `namibia`, `nepal`, `netherlands`, `new zealand`, `nicaragua`, `niger`, `nigeria`, `north korea`, `north macedonia`, `norway`, `oman`, `pakistan`, `panama`, `papua new guinea`, `paraguay`, `peru`, `philippines`, `poland`, `portugal`, `qatar`, `romania`, `russia`, `rwanda`, `saudi arabia`, `senegal`, `serbia`, `singapore`, `south africa`, `south korea`, `south sudan`, `spain`, `sri lanka`, `sudan`, `sweden`, `slovakia`, `slovenia`, `somalia`, `south africa`, `south korea`, `south sudan`, `spain`, `sri lanka`, `sudan`, `sweden`, `switzerland`, `syria`, `taiwan`, `tajikista`, `tanzania`, `thailand`, `togo`, `trinidad and tobago`, `tunisia`, `turkey`, `turkmenistan`, `uganda`, `ukraine`, `united arab emirates`, `united kingdom`, `united states`, `uruguay`, `uzbekistan`, `venezuela`, `vietnam`, `yemen`, `zambia`, and `zimbabwe`.
</ParamField>

<ParamField type="boolean">
  Automatically sets parameters for search. `include_answer`, `include_raw_content`,and `max_results` must be set manually if needed. `search_depth` may be set to `advanced` if `search_depth` is not explicitly set.
</ParamField>

<ParamField type="boolean">
  Returns results containing the exact quoted phrase(s) from the query. Punctuation is ignored.
</ParamField>

## Request Example

```bash theme={"system"}
curl -s -X POST 'https://api.novita.ai/v3/tavily/search' \
  -H 'Authorization: Bearer sk-123' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "Who is Leo Messi?",
    "auto_parameters": false,
    "topic": "general",
    "search_depth": "basic",
    "chunks_per_source": 3,
    "max_results": 5,
    "include_answer": false,
    "include_raw_content": false,
    "include_images": false
  }'
```

## Response

<ResponseField name="query" type="string">
  Executed search query.
</ResponseField>

<ResponseField name="answer" type="string">
  LLM-generated answer only when `include_answer`is requested.
</ResponseField>

<ResponseField name="images" type="object[]">
  List of images from image search. Returned if `include_images` is `true`. Image `url` and `description` are included if `include_image_description` is `true`. See [Images Output Object](#images-output-object).
</ResponseField>

<ResponseField name="results" type="object[]">
  Search results.
</ResponseField>

<ResponseField name="response_time" type="float">
  Request duration in seconds.
</ResponseField>

<ResponseField name="auto_parameters" type="object">
  Dictionary of automatically set parameters. Included if `auto_parameters` is `true`.
</ResponseField>

<ResponseField name="request_id" type="string">
  Unique request identifier.
</ResponseField>

### Images Output Object

<ResponseField name="url" type="string">
  Image URL.
</ResponseField>

<ResponseField name="description" type="string">
  Image Description.
</ResponseField>

### Result Object

<ResponseField name="title" type="string">
  Result title.
</ResponseField>

<ResponseField name="url" type="string">
  Result URL.
</ResponseField>

<ResponseField name="content" type="string">
  Result short snippet.
</ResponseField>

<ResponseField name="score" type="float">
  Result relevance score.
</ResponseField>

<ResponseField name="raw_content" type="string">
  Result HTML content. Returned if `include_raw_content` is `true`.
</ResponseField>

<ResponseField name="favicon" type="string">
  Site favicon URL.
</ResponseField>

<ResponseField name="images" type="object[]">
  List of images from search results. Returned if `include_images` is `true`. Image `url` and `description` are included if `include_image_description` is `true`. See [Images Output Object](#images-output-object).
</ResponseField>

## Errors

The platform may return standard HTTP errors before forwarding the request, and Tavily may return upstream errors after forwarding.

<ResponseField name="400" type="status">
  Invalid request body or unsupported parameter value.
</ResponseField>

<ResponseField name="401" type="status">
  Missing or invalid API key.
</ResponseField>

<ResponseField name="403" type="status">
  Access denied by platform or upstream provider.
</ResponseField>

<ResponseField name="404" type="status">
  Route or requested resource not found.
</ResponseField>

<ResponseField name="429" type="status">
  Rate limit exceeded.
</ResponseField>

<ResponseField name="500" type="status">
  Internal server error.
</ResponseField>

<ResponseField name="502" type="status">
  Upstream provider error.
</ResponseField>

<ResponseField name="503" type="status">
  Service unavailable.
</ResponseField>

## Notes

* All request bodies are JSON.
* Extra Tavily parameters not listed here may be passed through.
* Response shapes can vary depending on request options.
* This document intentionally omits billing-related fields.

## References

For more details, see the [Tavily Search API reference](https://docs.tavily.com/documentation/api-reference/endpoint/search).


# Webhook
Source: https://docs.novita.ai/api-reference/model-apis-webhook



**For the asynchronous V3 APIs, in addition to polling the [Task Result API](/api-reference/model-apis-task-result), you can now also listen for the `ASYNC_TASK_RESULT` event on your webhook endpoint.**

## Get Started

### Enable Webhook in Request

For the asynchronous V3 APIs that support webhooks, the webhook parameters are provided in the `extra.webhook` field. (For more details, please refer to the individual documentation pages for each API.) For example:

```js theme={"system"}
{
    "extra": {
        "webhook": {
            "url": "https://your-webhook-endpoint-url"
        }
        //...
    },
    // ...
}
```

When the task is finished, Novita AI will send the `ASYNC_TASK_RESULT` event to your webhook endpoint. For example:

```js theme={"system"}
// The webhook event content with API /v3/async/txt2image.
{
  "event_type": "ASYNC_TASK_RESULT",
  "payload": {
    "task": {
      "eta": 0,
      "progress_percent": 0,
      "reason": "",
      "status": "TASK_STATUS_SUCCEED",
      "task_id": "f6aec953-2e81-4e8b-938e-2ceab54d6df9",
      "task_type": "TXT_TO_IMG"
    },
    "extra": {
      "enable_nsfw_detection": false,
      "webhook": {
        "url": "https://your-webhook-endpoint-url"
      }
    },
    "images": [
      {
        "image_type": "jpeg",
        "image_url": "https://faas-output-image.s3.ap-southeast-1.amazonaws.com/test_data/image_01.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASVPYCN6LRCW3SOUV%2F20240602%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=20240602T150041Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&x-id=GetObject&X-Amz-Signature=f22b3a2f93587e8f7e80ba88842ddf7d80d6481bbec6f94eedd241bb15718629",
        "image_url_ttl": "3600",
        "nsfw_detection_result": null
      }
    ],
    "videos": [],
    "audios": []
  }
}
```

**The schema of the `payload` fields is exactly the same as the response from the [Task Result API](/api-reference/model-apis-task-result).**

### Use Test Mode to Test Your Integration

You can test your integration without making actual API calls by enabling `Test Mode`. For example:

```js theme={"system"}
{
    "extra": {
        "webhook": {
            "url": "https://your-webhook-endpoint-url",
            "test_mode": {
                "enabled": true,
                // Set return_task_status to TASK_STATUS_SUCCEED to test the success response.
                // Set return_task_status to TASK_STATUS_FAILED to test the error response.
                "return_task_status": 'TASK_STATUS_SUCCEED'
            }
        },
        //...
    },
    // ...
}
```

Your webhook endpoint will receive **a simulated mock event**, which you can use to ensure that your integration works correctly.

### Retry Policy

Novita AI will retry up to five times if it fails to send the event to your webhook endpoint. This includes the following situations:

* The request to your endpoint times out (currently set to 5 seconds).
* Your endpoint returns a non-2xx status code.

## Best Practices

1. Your webhook endpoint must quickly return a successful status code **2xx** before executing any complex logic that could cause a timeout.
2. After receiving a webhook event, filter out any events other than `ASYNC_TASK_RESULT` to continue with your logic.
3. Before releasing features that rely on the webhook event, use `Test Mode` to thoroughly test your logic.
4. Use HTTPS for your webhook endpoint.


# Query Quota List
Source: https://docs.novita.ai/api-reference/quota-list

GET https://api.novita.ai/openapi/v1/user/quota/list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Quota modal. Common values: `llm`, `image`.
</ParamField>

<ParamField type="string">
  Quota type. Common values: `RPM`, `TPM`, `IPM`.
</ParamField>

<ParamField type="string">
  Product type. Common values: `Public Endpoint`.
</ParamField>

<ParamField type="string">
  Quota object. For model scenarios, pass a model ID such as `deepseek/deepseek-v4.1-flash`. The server supports fuzzy matching. If empty, all quotas visible to the current account are returned.
</ParamField>

## Response Parameters

<ResponseField name="data" type="object[]">
  Quota list.

  <Expandable title="properties">
    <ResponseField name="modal" type="string">
      Quota modal.
    </ResponseField>

    <ResponseField name="quotaObject" type="string">
      Quota object display name. For model scenarios, this is usually the model name or model ID.
    </ResponseField>

    <ResponseField name="quotaType" type="string">
      Quota type.
    </ResponseField>

    <ResponseField name="productType" type="string">
      Product type.
    </ResponseField>

    <ResponseField name="currentQuota" type="string">
      Current account quota limit. A 64-bit integer returned as a JSON string (for example, `"2200000000"`). Values may exceed the 32-bit integer range, so parse it as a 64-bit integer.
    </ResponseField>

    <ResponseField name="defaultQuota" type="string">
      Default quota limit. A 64-bit integer returned as a JSON string (for example, `"2200000000"`). Values may exceed the 32-bit integer range, so parse it as a 64-bit integer.
    </ResponseField>

    <ResponseField name="adjustable" type="bool">
      Whether quota adjustment can be requested.
    </ResponseField>

    <ResponseField name="quotaItems" type="object[]">
      Quota limits for different account tiers.

      <Expandable title="properties">
        <ResponseField name="tier" type="string">
          Account tier. Possible values: `T1`, `T2`, `T3`, `T4`, `T5`.
        </ResponseField>

        <ResponseField name="quota" type="string">
          Quota limit for the account tier. A 64-bit integer returned as a JSON string (for example, `"2200000000"`). Values may exceed the 32-bit integer range, so parse it as a 64-bit integer.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="int32">
  Number of returned quotas.
</ResponseField>

<RequestExample>
  ```bash theme={"system"}
  curl --request GET \
    --url 'https://api.novita.ai/openapi/v1/user/quota/list?modal=llm&quotaType=RPM&productType=Public%20Endpoint&quotaObject=deepseek%2Fdeepseek-v4.1-flash' \
    --header 'Authorization: Bearer <API_KEY>'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"system"}
  {
    "data": [
      {
        "modal": "llm",
        "quotaObject": "deepseek/deepseek-v4.1-flash",
        "quotaType": "RPM",
        "productType": "Public Endpoint",
        "currentQuota": "60",
        "defaultQuota": "60",
        "adjustable": true,
        "quotaItems": [
          {
            "tier": "T1",
            "quota": "60"
          },
          {
            "tier": "T2",
            "quota": "120"
          }
        ]
      }
    ],
    "total": 1
  }
  ```
</ResponseExample>


# Break Job
Source: https://docs.novita.ai/api-reference/gpu-instance-break-job

POST https://api.novita.ai/gpu-instance/openapi/v1/job/break

## API Description

Forcefully interrupt a running job.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The job ID to be interrupted. String, length 0–255.
</ParamField>

## cURL Example

```bash theme={"system"}
curl --location --request POST 'https://api.novita.ai/gpu-instance/openapi/v1/job/break' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{API_KEY}}' \
--data-raw '{
  "jobId": "10wi9visv0rqt57w"
}'
```

## Response

<ResponseField name="jobId" type="string">
  The job ID.
</ResponseField>


# Convert Pay-As-You-Go Instance to Subscription
Source: https://docs.novita.ai/api-reference/gpu-instance-change-billing-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/transToMonthlyInstance

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The ID of the pay-as-you-go instance to be converted to a subscription instance.
</ParamField>

<ParamField type="integer">
  Subscription period after conversion, in months.
</ParamField>


# Create Container Registry Authentication
Source: https://docs.novita.ai/api-reference/gpu-instance-create-container-registry-auth

POST https://api.novita.ai/gpu-instance/openapi/v1/repository/auth/save

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string" />

<ParamField type="string" />

<ParamField type="string" />

## Example

Request

```bash theme={"system"}
curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/repository/auth/save' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "<string>",
    "username": "<string>",
    "password": "<string>"
  }'
```

Response

```json theme={"system"}
{}
```


# Create CPU Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-create-cpu-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/create

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  CPU instance name. String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  Product ID for deploying the instance. You can query this via the [List CPU Products API](/api-reference/gpu-instance-list-cpu-products). String, length limit: 1-255 characters.
</ParamField>

<ParamField type="string">
  Container image URL. String, length limit: 1-500 characters.
</ParamField>

<ParamField type="string">
  Image repository authentication. Format: username:password. Required for private images; not required for public images or platform images. String, length limit: 0-10239 characters.
</ParamField>

<ParamField type="string">
  Image repository authentication ID.
</ParamField>

<ParamField type="string">
  Ports exposed by the instance. String, e.g.: 80/http, 3306/tcp. Supported port range: 1-65535, except for 2222, 2223, 2224 which are reserved for internal use. Supported port types: tcp, http. The total number of ports used by ports + tools must not exceed 15.
</ParamField>

<ParamField type="object[]">
  Instance environment variables. Array, up to 100 environment variables.

  <Expandable title="properties">
    <ParamField type="string">
      Environment variable name. String, length limit: 0-511 characters.
    </ParamField>

    <ParamField type="string">
      Environment variable value. String, length limit: 0-4095 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Enable official image support tools. Array. Currently, some official images only include Jupyter. The total number of ports used by ports + tools must not exceed 15.

  <Expandable title="properties">
    <ParamField type="string">
      Tool name. Valid value: Jupyter.
    </ParamField>

    <ParamField type="string">
      Port used by the tool. Supported port range: 1-65535, except for 2222, 2223, 2224 which are reserved for internal use.
    </ParamField>

    <ParamField type="string">
      Port type used by the tool. Valid values: tcp, http.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Container startup command. This setting will override the Docker image's CMD. String, length limit: 0-2047 characters.
</ParamField>

<ParamField type="string">
  Container startup entrypoint. This setting will override the Docker image's ENTRYPOINT. String, length limit: 0-2047 characters.
</ParamField>

<ParamField type="string">
  Specify the cluster ID to create the instance in. If left empty, the instance will be created in a random cluster. String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  Mount point for local storage. Default: "/workspace". String, length limit: 1-4095 characters.
</ParamField>

<ParamField type="object[]">
  Cloud storage mount configuration. Array, up to 30 cloud storages can be mounted.

  <Expandable title="properties">
    <ParamField type="string">
      Cloud storage ID.
    </ParamField>

    <ParamField type="string">
      Mount point for cloud storage. Default: "/network". String, length limit: 1-4095 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  VPC network ID. Leave empty if not using a VPC network.
</ParamField>

<ParamField type="string">
  Instance type. Valid values: cpu, gpu. Must be set to cpu.
</ParamField>

## Response

<ResponseField name="id" type="string">
  Created instance ID.
</ResponseField>


# Create Image Prewarm Task
Source: https://docs.novita.ai/api-reference/gpu-instance-create-image-prewarm

POST https://api.novita.ai/gpu-instance/openapi/v1/image/prewarm

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Image address to prewarm.
</ParamField>

<ParamField type="string">
  Image registry authentication ID. Required only for private registries.
</ParamField>

<ParamField type="string">
  Cluster ID where the image should be prewarmed.
</ParamField>

<ParamField type="string[]">
  Product IDs to prewarm on. Leave empty to prewarm globally in the cluster.
</ParamField>

<ParamField type="string">
  Task note or description.
</ParamField>

## Response

<ResponseField name="id" type="string">
  Created prewarm task ID.
</ResponseField>


# Create GPU Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-create-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/create

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Instance name. String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  GPU product ID. String, length limit: 1-255 characters. You can query this via the [List GPU Products API](/api-reference/gpu-instance-list-products).
</ParamField>

<ParamField type="integer">
  Number of GPUs. Integer, valid range: \[1, 8].
</ParamField>

<ParamField type="integer">
  Root filesystem size. Integer, valid range: \[10, 6144]. If the minimum value is less than 10 GB, the system will default to 10 GB. The maximum value is dynamically limited by available storage resources and can be queried via the product list API.
</ParamField>

<ParamField type="string">
  Image URL. String, length limit: 1-500 characters.
</ParamField>

<ParamField type="string">
  Image authentication (username:password). String, length limit: 0-10239 characters.
</ParamField>

<ParamField type="string">
  Image repository authentication ID.
</ParamField>

<ParamField type="string">
  Instance open ports, e.g.: 80/http, 3306/tcp. Supported port range: \[1-65535], except for 2222, 2223, 2224 which are reserved for internal use. Supported port types: \[tcp, http]. The total number of ports used by ports + tools must not exceed 15.
</ParamField>

<ParamField type="object[]">
  Instance environment variables. Up to 100 environment variables can be created.

  <Expandable title="properties">
    <ParamField type="string">
      Environment variable name. String, length limit: 0-511 characters.
    </ParamField>

    <ParamField type="string">
      Environment variable value. String, length limit: 0-4095 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Enable official image support tools. Currently, some official images only include Jupyter. The total number of ports used by ports + tools must not exceed 15.

  <Expandable title="properties">
    <ParamField type="string">
      Tool name. Valid values: \[Jupyter].
    </ParamField>

    <ParamField type="string">
      Port used by the tool. Supported port range: \[1-65535], except for 2222, 2223, 2224 which are reserved for internal use.
    </ParamField>

    <ParamField type="string">
      Port type used by the tool. Supported types: \[tcp, http].
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Instance startup command. String, length limit: 0-2047 characters.
</ParamField>

<ParamField type="string">
  Instance startup entrypoint. This setting will override the Docker image's ENTRYPOINT. String, length limit: 0-2047 characters.
</ParamField>

<ParamField type="string">
  Specify the cluster ID to create the instance in. If left empty, the instance will be created in a random cluster.
</ParamField>

<ParamField type="object[]">
  Cloud storage mount configuration. Up to 30 cloud storages can be mounted.

  <Expandable title="properties">
    <ParamField type="string">
      Network storage ID.
    </ParamField>

    <ParamField type="string">
      Network storage mount path. Default: "/network". String, length limit: 1-4095 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  VPC network ID. Leave empty if not using a VPC network.
</ParamField>

<ParamField type="string">
  Instance type. Valid values: \[gpu, cpu].
</ParamField>

<ParamField type="integer">
  Number of months for subscription instance. Set to 0 to create a pay-as-you-go instance. Integer, value must be greater than or equal to 0.
</ParamField>

<ParamField type="string">
  Instance billing mode. Valid values: `onDemand`, `monthly`, `spot`. <br />
  Default: `onDemand`.<br />
  Note: If month > 0 is set, `monthly` billing mode will be enforced.

  * `onDemand`: Pay-as-you-go billing
  * `monthly`: Monthly subscription billing
  * `spot`: Spot instance billing
</ParamField>

<ParamField type="boolean">
  Whether to automatically renew. Boolean, default value: false.<br />
  Note: This parameter is only effective when monthly billing is enabled.
</ParamField>

<ParamField type="integer">
  Number of months to automatically renew. Integer, valid range: \[1, 12].<br />
  Note: This parameter is only effective when monthly billing is enabled and auto renew is enabled.
</ParamField>

<ParamField type="string">
  Minimum CUDA version supported for instance creation. Must be specified in the format: 11.8, 12.4, etc. String, length limit: 0-255 characters.
</ParamField>

## Response

<ResponseField name="id" type="string">
  Created instance ID.
</ResponseField>


# Create Network Storage
Source: https://docs.novita.ai/api-reference/gpu-instance-create-network-storage

POST https://api.novita.ai/gpu-instance/openapi/v1/networkstorage/create

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string" />

<ParamField type="string" />

<ParamField type="integer" />

## Example

Request

```bash theme={"system"}
curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/networkstorage/create' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "clusterId": "string",
    "storageName": "string",
    "storageSize": 0
  }'
```

Response

```json theme={"system"}
"<string>"
```


# Create Template
Source: https://docs.novita.ai/api-reference/gpu-instance-create-template

POST https://api.novita.ai/gpu-instance/openapi/v1/template/create

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="object">
  Template settings.

  <Expandable title="properties">
    <ParamField type="string">
      Template name. String, length limit: 1-255 characters.
    </ParamField>

    <ParamField type="string">
      Template README content (in Markdown format). String, length limit: 0-102400 characters.
    </ParamField>

    <ParamField type="string">
      Template type.<br />
      Enum: `instance`
    </ParamField>

    <ParamField type="string">
      Template channel.<br />
      Enum: `private`
    </ParamField>

    <ParamField type="string">
      Docker image address for instance startup. String, length limit: 1-500 characters.
    </ParamField>

    <ParamField type="string">
      Image repository authentication ID for private images. String, length limit: 0-255 characters.
    </ParamField>

    <ParamField type="string">
      Startup command for the instance. String, length limit: 0-2047 characters.
    </ParamField>

    <ParamField type="string">
      Instance startup entrypoint. This setting will override the Docker image's ENTRYPOINT. String, length limit: 0-2047 characters.
    </ParamField>

    <ParamField type="integer">
      Root filesystem storage (GB). Integer, value must be greater than 0.
    </ParamField>

    <ParamField type="object[]">
      Exposed port settings.

      <Expandable title="properties">
        <ParamField type="string">
          Exposed port types.<br />
          Enum: `http`, `tcp`
        </ParamField>

        <ParamField type="integer[]">
          Exposed ports (maximum of 10). Supported port range: 1-65535, except for 2222, 2223, 2224 which are reserved for internal use.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object[]">
      Environment variables injected into the instance. Up to 100 environment variable pairs can be created.

      <Expandable title="properties">
        <ParamField type="string">
          Environment variable key. String, length limit: 0-511 characters.
        </ParamField>

        <ParamField type="string">
          Environment variable value. String, length limit: 0-4095 characters.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="string">
      Minimum supported CUDA version. String, length limit: 0-255 characters. Format example: 11.8, 12.4.
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="templateId" type="string">
  ID of the created template.
</ResponseField>

## Example

Request

```bash theme={"system"}

curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/template/create' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "template": {
      "name": "test",
      "readme": "readme",
      "type": "instance",
      "channel": "private",
      "readme": "test create template",
      "image": "nginx",
      "imageAuth": "",
      "startCommand": "echo test",
      "entrypoint": "",
      "rootfsSize": 60,
      "ports": [
        {
          "type": "http",
          "ports": [80, 443]
        },
        {
          "type": "tcp",
          "ports": [90, 95]
        }
      ],
      "envs": [
        {
          "key": "test1",
          "value": "template1"
        },
        {
          "key": "test2",
          "value": "test2"
        }
      ],
      "minCudaVersion": "11.8"
    }
  }'
```

Response

```json theme={"system"}
{
  "templateId": "1"
}
```


# Create VPC Network
Source: https://docs.novita.ai/api-reference/gpu-instance-create-vpc

POST https://api.novita.ai/gpu-instance/openapi/v1/network/create

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Cluster ID. You can obtain this via the [List Clusters](/api-reference/gpu-instance-list-clusters) API. String, length limit: 1-255 characters.
</ParamField>

<ParamField type="string">
  Name of the VPC network. Customizable. String, length limit: 0-30 characters.
</ParamField>

## Response

<ResponseField name="network" type="object[]">
  VPC network information.

  <Expandable title="properties">
    <ResponseField name="Id" type="string">
      VPC network ID.
    </ResponseField>

    <ResponseField name="user" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      VPC network name.
    </ResponseField>

    <ResponseField name="state" type="object[]">
      VPC network status.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          VPC network status. Possible values:

          * creating: Being created.
          * ready: Successfully created.
        </ResponseField>

        <ResponseField name="error" type="string">
          Error message if VPC network creation fails or the network is unavailable.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="segment" type="string">
      VPC network segment.
    </ResponseField>

    <ResponseField name="clusterId" type="string">
      Cluster ID.
    </ResponseField>

    <ResponseField name="Addresses" type="object[]">
      Instance information under the VPC network.

      <Expandable title="properties">
        <ResponseField name="Id" type="string">
          Instance ID.
        </ResponseField>

        <ResponseField name="Ip" type="string">
          Instance IP address.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="createTime" type="string">
      VPC network creation time.
    </ResponseField>
  </Expandable>
</ResponseField>


# Delete Container Registry Auth
Source: https://docs.novita.ai/api-reference/gpu-instance-delete-container-registry-auth

POST https://api.novita.ai/gpu-instance/openapi/v1/repository/auth/delete

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string" />

## Example

Request

```bash theme={"system"}
curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/repository/auth/delete' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "id": "<string>"
  }'
```

Response

```json theme={"system"}
{}
```


# Delete Image Prewarm Tasks
Source: https://docs.novita.ai/api-reference/gpu-instance-delete-image-prewarm

POST https://api.novita.ai/gpu-instance/openapi/v1/image/prewarm/delete

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string[]">
  IDs of prewarm tasks to delete.
</ParamField>


# Delete Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-delete-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/delete

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  ID of the instance to be deleted.
</ParamField>


# Delete Network Storage
Source: https://docs.novita.ai/api-reference/gpu-instance-delete-network-storage

POST https://api.novita.ai/gpu-instance/openapi/v1/networkstorage/delete

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string" />

## Example

Request

```bash theme={"system"}
curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/networkstorage/delete' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "storageId": "string"
  }'

```

Response

```json theme={"system"}
{}
```


# Delete Template
Source: https://docs.novita.ai/api-reference/gpu-instance-delete-template

POST https://api.novita.ai/gpu-instance/openapi/v1/template/delete

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The ID of the template to be deleted.
</ParamField>

## Response

<ResponseField name="templateId" type="string">
  The ID of the deleted template.
</ResponseField>

## Example

Request

```bash theme={"system"}
curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/template/delete' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "templateId": "<string>"
  }'
```

Response

```json theme={"system"}
{
  "templateId": "<string>"
}
```


# Delete VPC Network
Source: https://docs.novita.ai/api-reference/gpu-instance-delete-vpc

POST https://api.novita.ai/gpu-instance/openapi/v1/network/delete

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Cluster ID. String, length limit: 1-255 characters. Please ensure there are no instances under this VPC network before deletion.
</ParamField>


# Edit Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-edit-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/edit

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  ID of the instance to edit.
</ParamField>

<ParamField type="object[]">
  Ports to be exposed by the instance. The total number of ports used by ports + tools must not exceed 15.

  <Expandable title="properties">
    <ParamField type="integer">
      Port number. Supported port range: 1-65535, except for 2222, 2223, and 2224 which are reserved for internal use.
    </ParamField>

    <ParamField type="string">
      Protocol type. Supported values: `tcp`, `http`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="integer">
  Size to expand the root disk, in GB. Integer, value must be >= 0. Set to 0 if no expansion is needed.
</ParamField>


# Edit Network Storage
Source: https://docs.novita.ai/api-reference/gpu-instance-edit-network-storage

POST https://api.novita.ai/gpu-instance/openapi/v1/networkstorage/update

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The unique identifier for the storage.
</ParamField>

<ParamField type="string">
  The name of the storage.
</ParamField>

<ParamField type="string">
  The size of the storage in appropriate units.
</ParamField>

## Example

Request

```bash theme={"system"}
curl --request POST \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/networkstorage/update' \
  --header 'Authorization: Bearer {{API Key}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "storageId": "string",
    "storageName": "string",
    "storageSize": "string"
  }'
```

Response

```json theme={"system"}
{}
```


# Get Image Prewarm Quota
Source: https://docs.novita.ai/api-reference/gpu-instance-get-image-quota

GET https://api.novita.ai/gpu-instance/openapi/v1/image/prewarm/quota

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseField name="total" type="integer">
  Number of created prewarm tasks.
</ResponseField>

<ResponseField name="limit" type="integer">
  Maximum number of prewarm tasks that can be created.
</ResponseField>

<ResponseField name="perImageSize" type="integer">
  Maximum image size per prewarm task (GB).
</ResponseField>


# Get Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-get-instance

GET https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  ID of the instance being queried.
</ParamField>

## Response

<ResponseField name="id" type="string">
  Instance ID.
</ResponseField>

<ResponseField name="name" type="string">
  Instance name.
</ResponseField>

<ResponseField name="clusterId" type="string">
  Cluster ID.
</ResponseField>

<ResponseField name="clusterName" type="string">
  Name of the cluster.
</ResponseField>

<ResponseField name="imageUrl" type="string">
  Container image URL.
</ResponseField>

<ResponseField name="imageAuthId" type="string">
  Image registry authentication information.
</ResponseField>

<ResponseField name="command" type="string">
  Container startup command.
</ResponseField>

<ResponseField name="entrypoint" type="string">
  Container startup entrypoint.
</ResponseField>

<ResponseField name="cpuNum" type="string">
  Number of CPU cores allocated to the instance.
</ResponseField>

<ResponseField name="memory" type="string">
  Memory size allocated to the instance, in GB.
</ResponseField>

<ResponseField name="gpuNum" type="string">
  Number of GPU cards allocated to the instance.
</ResponseField>

<ResponseField name="status" type="string">
  Instance status. Possible values:

  * `toCreate`: Pending creation
  * `creating`: Creating
  * `pulling`: Pulling image
  * `running`: Running
  * `toStart`: Pending start
  * `starting`: Starting
  * `toStop`: Pending stop
  * `stopping`: Stopping
  * `exited`: Stopped
  * `toRestart`: Pending restart
  * `restarting`: Restarting
  * `toRemove`: Pending deletion
  * `removing`: Deleting
  * `removed`: Deleted
  * `toReset`: Pending reset (upgrade)
  * `resetting`: Resetting
  * `migrating`: Migrating
  * `freezing`: Freezing
</ResponseField>

<ResponseField name="statusError" type="object">
  Error information when instance creation fails or the instance is unavailable.

  <Expandable title="properties">
    <ResponseField name="state" type="string">
      Abnormal instance status.
    </ResponseField>

    <ResponseField name="message" type="string">
      Error message.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="network" type="object">
  VPC network information for the instance.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      VPC network ID.
    </ResponseField>

    <ResponseField name="ip" type="string">
      Instance IP address.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="portMappings" type="object[]">
  Instance port mapping information.

  <Expandable title="properties">
    <ResponseField name="port" type="integer">
      Port number.
    </ResponseField>

    <ResponseField name="endpoint" type="string">
      Endpoint.
    </ResponseField>

    <ResponseField name="type" type="string">
      Protocol type.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="volumeMounts" type="object[]">
  Storage configuration for the instance.

  <Expandable title="properties">
    <ResponseField name="type" type="string">
      Storage type. Possible values:

      * network: Cloud storage.
      * local: Local storage.
    </ResponseField>

    <ResponseField name="size" type="number">
      Storage capacity.
    </ResponseField>

    <ResponseField name="id" type="string">
      Cloud storage ID. Only present when the storage type is "network".
    </ResponseField>

    <ResponseField name="mountPath" type="string">
      Mount path for the storage.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="node" type="object[]">
  Node information.

  <Expandable title="properties">
    <ResponseField name="maxRootfsSize" type="number">
      Maximum available system disk size.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="jobs" type="object[]">
  Tasks currently running on the instance.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Task ID.
    </ResponseField>

    <ResponseField name="type" type="string">
      Task type. Currently, only saveImage is supported, which indicates saving an image.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="envs" type="object[]">
  Instance environment variable information.

  <Expandable title="properties">
    <ResponseField name="key" type="string">
      Environment variable name.
    </ResponseField>

    <ResponseField name="value" type="string">
      Environment variable value.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="connectComponentSSH" type="object">
  Instance SSH connection information.

  <Expandable title="properties">
    <ResponseField name="sshCommand" type="string">
      Command for SSH remote login.
    </ResponseField>

    <ResponseField name="password" type="string">
      Password for SSH remote login.
    </ResponseField>

    <ResponseField name="isRunning" type="boolean">
      Whether SSH remote login is active.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="connectComponentWebTerminal" type="object">
  Instance WebSSH connection information.

  <Expandable title="properties">
    <ResponseField name="address" type="string">
      Address for web-based remote login.
    </ResponseField>

    <ResponseField name="username" type="string">
      Username for web-based remote login.
    </ResponseField>

    <ResponseField name="password" type="string">
      Password for web-based remote login.
    </ResponseField>

    <ResponseField name="isRunning" type="boolean">
      Whether web-based remote login is active.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="connectComponentJupyter" type="object">
  Jupyter connection information for the instance (if available).

  <Expandable title="properties">
    <ResponseField name="port" type="integer">
      Connection port.
    </ResponseField>

    <ResponseField name="address" type="string">
      Connection address.
    </ResponseField>

    <ResponseField name="isRunning" type="boolean">
      Whether Jupyter remote login is active.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="connectComponentLog" type="object">
  Instance log connection information (HTTP Stream).

  <Expandable title="properties">
    <ResponseField name="systemLogAddress" type="string">
      System log address.
    </ResponseField>

    <ResponseField name="instanceLogAddress" type="string">
      Instance log address.
    </ResponseField>

    <ResponseField name="isRunning" type="boolean">
      Whether log connection is active.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="kind" type="string">
  Instance type.
</ResponseField>

<ResponseField name="billingMode" type="string">
  Billing mode for the instance. Values:

  * onDemand: Pay-as-you-go billing.
  * monthly: Subscription (monthly or yearly) billing.
  * spot: Spot instance billing.
</ResponseField>

<ResponseField name="endTime" type="string">
  Expiration time for subscription instances. For pay-as-you-go instances, returns -1.
</ResponseField>

<ResponseField name="freeStorageSize" type="integer">
  Free system disk size (GB).
</ResponseField>

<ResponseField name="keepDataDay" type="integer">
  Number of days data is retained after the instance is shut down.
</ResponseField>


# Fetch Instance Monitor Metrics
Source: https://docs.novita.ai/api-reference/gpu-instance-get-metrics

GET https://api.novita.ai/openapi/v1/metrics/gpu/instance

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<Note>
  If the parameters endTime, startTime, or interval do not meet the requirements, default values will be used. There may be a delay of about one minute when retrieving real-time monitoring data.
</Note>

<ParamField type="string">
  Instance ID.
</ParamField>

<ParamField type="integer">
  End time for querying monitoring data. Value range: (0, current timestamp], default: current timestamp.
</ParamField>

<ParamField type="integer">
  Start time for querying monitoring data. Value range: (0, endTime - 60], default: current timestamp - 60.
</ParamField>

<ParamField type="integer">
  Time granularity for monitoring data, in seconds. Value must be greater than 15. Default: 15.
</ParamField>

## Response

<ResponseField name="cpuUtilization" type="object[]">
  CPU utilization data.

  <Expandable title="properties">
    <ResponseField name="timestamp" type="string">
      Timestamp.
    </ResponseField>

    <ResponseField name="value" type="number">
      CPU utilization.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="memUtilization" type="object[]">
  Memory utilization data.

  <Expandable title="properties">
    <ResponseField name="timestamp" type="string">
      Timestamp.
    </ResponseField>

    <ResponseField name="value" type="number">
      Memory utilization.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="rootDiskUtilization" type="object[]">
  Root disk utilization data.

  <Expandable title="properties">
    <ResponseField name="timestamp" type="string">
      Timestamp.
    </ResponseField>

    <ResponseField name="value" type="number">
      Root disk utilization.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="gpuUtilization" type="object">
  GPU utilization data.

  <Expandable title="properties">
    <ResponseField name="avg" type="object[]">
      Average utilization across all GPUs.

      <Expandable title="properties">
        <ResponseField name="timestamp" type="string">
          Timestamp.
        </ResponseField>

        <ResponseField name="value" type="number">
          Utilization.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="gpuIds" type="object[]">
      Utilization for each GPU.

      <Expandable title="properties">
        <ResponseField name="gpuId" type="string">
          GPU ID in use.
        </ResponseField>

        <ResponseField name="items" type="object[]">
          GPU utilization data.

          <Expandable title="properties">
            <ResponseField name="timestamp" type="string">
              Timestamp.
            </ResponseField>

            <ResponseField name="value" type="number">
              Utilization.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="gpuMemUtilization" type="object">
  GPU memory utilization data.

  <Expandable title="properties">
    <ResponseField name="avg" type="object[]">
      Average GPU memory utilization across all GPUs.

      <Expandable title="properties">
        <ResponseField name="timestamp" type="string">
          Timestamp.
        </ResponseField>

        <ResponseField name="value" type="number">
          Utilization.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="gpuIds" type="object[]">
      GPU memory utilization for each GPU.

      <Expandable title="properties">
        <ResponseField name="gpuId" type="string">
          GPU ID in use.
        </ResponseField>

        <ResponseField name="items" type="object[]">
          GPU memory utilization data.

          <Expandable title="properties">
            <ResponseField name="timestamp" type="string">
              Timestamp.
            </ResponseField>

            <ResponseField name="value" type="number">
              Utilization.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>


# Get Template
Source: https://docs.novita.ai/api-reference/gpu-instance-get-template

GET https://api.novita.ai/gpu-instance/openapi/v1/template

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  ID of the template being queried.
</ParamField>

## Response

<ResponseField name="template" type="object">
  Template settings.

  <Expandable title="properties">
    <ResponseField name="Id" type="string">
      Template ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Template name.
    </ResponseField>

    <ResponseField name="readme" type="string">
      Template README content (in Markdown format).
    </ResponseField>

    <ResponseField name="type" type="string">
      Template type.Enum: `instance`
    </ResponseField>

    <ResponseField name="channel" type="string">
      Template channel.Enum: `private`
    </ResponseField>

    <ResponseField name="image" type="string">
      Docker image address for instance startup.
    </ResponseField>

    <ResponseField name="imageAuth" type="string">
      Image repository authentication ID for private images.
    </ResponseField>

    <ResponseField name="startCommand" type="string">
      Startup command for the instance.
    </ResponseField>

    <ResponseField name="entrypoint" type="string">
      Instance startup entrypoint.
    </ResponseField>

    <ResponseField name="rootfsSize" type="integer">
      Rootfs storage (GB).
    </ResponseField>

    <ResponseField name="ports" type="object[]">
      Exposed ports settings.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Exposed port types.Enum: `http, tcp`
        </ResponseField>

        <ResponseField name="ports" type="[integer]">
          Exposed ports (maximum 10).
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="envs" type="object[]">
      Environment variables injected into instance.

      <Expandable title="properties">
        <ResponseField name="key" type="string">
          Environment variable key.
        </ResponseField>

        <ResponseField name="value" type="string">
          Environment variable value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="tools" type="object[]">
      Template built-in tools (only available for official templates).

      <Expandable title="properties">
        <ResponseField name="name" type="string">
          Tool name.
        </ResponseField>

        <ResponseField name="describe" type="string">
          Tool description.
        </ResponseField>

        <ResponseField name="port" type="integer">
          Tool port.
        </ResponseField>

        <ResponseField name="type" type="string">
          Tool protocol type. Enum: `http`, `tcp`
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      Template creation timestamp (Unix seconds).
    </ResponseField>

    <ResponseField name="recommendCards" type="object[]">
      Recommended GPU card specs (only available for official templates).

      <Expandable title="properties">
        <ResponseField name="gpuSpecId" type="string">
          GPU spec ID.
        </ResponseField>

        <ResponseField name="cardNum" type="string">
          Recommended card number.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="minCudaVersion" type="string">
      Minimum required CUDA version, e.g. 11.8, 12.4.
    </ResponseField>
  </Expandable>
</ResponseField>

## Example

Request

```bash theme={"system"}
curl --request GET \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/template' \
  --header 'Authorization: Bearer {{API Key}}'
```

Response

```json theme={"system"}
{
  "template": {
    "Id": "212",
    "name": "ubuntu",
    "readme": "readme",
    "type": "instance",
    "channel": "private",
    "image": "ubuntu:latest",
    "imageAuth": "",
    "startCommand": "",
    "entrypoint": "",
    "rootfsSize": 10,
    "ports": [
      {
        "type": "tcp",
        "ports": [8080, 8090]
      },
      {
        "type": "http",
        "ports": [6000, 60001]
      }
    ],
    "envs": [
      {
        "key": "testkey",
        "value": "123"
      }
    ],
    "tools": [
      {
        "name": "Jupyter",
        "describe": "Start Jupyter Notebook",
        "port": 8888,
        "type": "http"
      }
    ],
    "createdAt": "1713162260",
    "recommendCards": [
      {
        "gpuSpecId": "4090.18c.60g",
        "cardNum": "2"
      }
    ],
    "minCudaVersion": "11.8"
  }
}
```


# Get VPC Network
Source: https://docs.novita.ai/api-reference/gpu-instance-get-vpc

GET https://api.novita.ai/gpu-instance/openapi/v1/network

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Cluster ID. String, length limit: 1-255 characters. Only one is supported.
</ParamField>

## Response

<ResponseField name="network" type="object[]">
  VPC network information.

  <Expandable title="properties">
    <ResponseField name="Id" type="string">
      VPC network ID.
    </ResponseField>

    <ResponseField name="user" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      VPC network name.
    </ResponseField>

    <ResponseField name="state" type="object[]">
      VPC network status.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          VPC network status. Possible values:

          * creating: Being created.
          * ready: Successfully created.
        </ResponseField>

        <ResponseField name="error" type="string">
          Error message if VPC network creation fails or the network is unavailable.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="segment" type="string">
      VPC network segment.
    </ResponseField>

    <ResponseField name="clusterId" type="string">
      Cluster ID.
    </ResponseField>

    <ResponseField name="Addresses" type="object[]">
      Instance information under the VPC network.

      <Expandable title="properties">
        <ResponseField name="Id" type="string">
          Instance ID.
        </ResponseField>

        <ResponseField name="Ip" type="string">
          Instance IP address.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="createTime" type="string">
      VPC network creation time. Format: Unix timestamp.
    </ResponseField>
  </Expandable>
</ResponseField>


# List Container Registry Auth
Source: https://docs.novita.ai/api-reference/gpu-instance-list-container-registry-auth

GET https://api.novita.ai/gpu-instance/openapi/v1/repository/auths

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseField name="data" type="object[]">
  <Expandable title="properties">
    <ResponseField name="id" type="string" />

    <ResponseField name="name" type="string" />

    <ResponseField name="username" type="string" />

    <ResponseField name="password" type="string" />
  </Expandable>
</ResponseField>

## Example

Request

```bash theme={"system"}
curl --request GET \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/repository/auths' \
  --header 'Authorization: Bearer {{API Key}}'
```

Response

```json theme={"system"}
{
  "data": [
    {
      "id": "75irlsvvsehnqdajg2r481mjk54ecodc",
      "name": "test",
      "username": "uuuu",
      "password": "1111"
    },
    {
      "id": "eih4rdk77avum5lyau291w1i54zd897w",
      "name": "test",
      "username": "uuuu",
      "password": "1111"
    }
  ]
}
```


# List CPU Products
Source: https://docs.novita.ai/api-reference/gpu-instance-list-cpu-products

GET https://api.novita.ai/gpu-instance/openapi/v1/cpu/products

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Filter by specified cluster ID. String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  Filter by product name (fuzzy match). String, length limit: 0-255 characters.
</ParamField>

## Response

<ResponseField name="id" type="string">
  CPU product ID.
</ResponseField>

<ResponseField name="name" type="string">
  CPU product name.
</ResponseField>

<ResponseField name="cpuNum" type="integer">
  Number of CPU cores.
</ResponseField>

<ResponseField name="memorySize" type="integer">
  Memory size (GB).
</ResponseField>

<ResponseField name="rootfsSize" type="integer">
  Root filesystem size (GB).
</ResponseField>

<ResponseField name="localVolumeSize" type="integer">
  Local disk size (GB).
</ResponseField>

<ResponseField name="availableDeploy" type="boolean">
  Whether this product can be used to create an instance. Values:

  * true: This product can be used to create an instance.
  * false: Insufficient resources, cannot create an instance.
</ResponseField>

<ResponseField name="price" type="integer">
  Unit price of the product.
</ResponseField>


# List Image Prewarm Tasks
Source: https://docs.novita.ai/api-reference/gpu-instance-list-image-prewarm

GET https://api.novita.ai/gpu-instance/openapi/v1/image/prewarm

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer">
  Page number. Default: 1.
</ParamField>

<ParamField type="integer">
  Page size. Default: 10.
</ParamField>

<ParamField type="string">
  Prewarm task state. Valid values: `Pending`, `Running`, `Succeeded`, `Failed`.
</ParamField>

<ParamField type="string">
  Cluster ID.
</ParamField>

<ParamField type="string">
  Image name or task note for fuzzy matching.
</ParamField>

## Response

<ResponseField name="data" type="object[]">
  List of prewarm tasks.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Prewarm task ID.
    </ResponseField>

    <ResponseField name="imageName" type="string">
      Image name (the last segment of the image URL without the tag).
    </ResponseField>

    <ResponseField name="imageUrl" type="string">
      Image URL.
    </ResponseField>

    <ResponseField name="repositoryAuth" type="string">
      Image registry authentication ID.
    </ResponseField>

    <ResponseField name="clusterId" type="string">
      Cluster ID.
    </ResponseField>

    <ResponseField name="clusterName" type="string">
      Cluster name.
    </ResponseField>

    <ResponseField name="products" type="object[]">
      Products to prewarm.

      <Expandable title="properties">
        <ResponseField name="productId" type="string">
          Product ID.
        </ResponseField>

        <ResponseField name="productName" type="string">
          Product name.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="imageSize" type="string">
      Image size in bytes.
    </ResponseField>

    <ResponseField name="createTime" type="string">
      Task creation time (Unix timestamp in seconds).
    </ResponseField>

    <ResponseField name="state" type="string">
      Task state. Valid values: `Pending`, `Running`, `Succeeded`, `Failed`.
    </ResponseField>

    <ResponseField name="completeTime" type="string">
      Task completion time (Unix timestamp in seconds).
    </ResponseField>

    <ResponseField name="note" type="string">
      Task note.
    </ResponseField>

    <ResponseField name="reason" type="string[]">
      Reason messages.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="integer">
  Total number of tasks.
</ResponseField>


# List Instances
Source: https://docs.novita.ai/api-reference/gpu-instance-list-instances

GET https://api.novita.ai/gpu-instance/openapi/v1/gpu/instances

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer">
  Maximum number of items returned per page. Integer, value >= 0.
</ParamField>

<ParamField type="integer">
  Page number to retrieve. Integer, value >= 0.
</ParamField>

<ParamField type="string">
  Instance name (supports fuzzy search). String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  Product name (supports fuzzy search). String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  Instance status. String, length limit: 0-63 characters. Possible values:

  * `toCreate`: Pending creation
  * `creating`: Creating
  * `pulling`: Pulling image
  * `running`: Running
  * `toStart`: Pending start
  * `starting`: Starting
  * `toStop`: Pending stop
  * `stopping`: Stopping
  * `exited`: Stopped
  * `toRestart`: Pending restart
  * `restarting`: Restarting
  * `toRemove`: Pending deletion
  * `removing`: Deleting
  * `removed`: Deleted
  * `toReset`: Pending reset (upgrade)
  * `resetting`: Resetting
  * `migrating`: Migrating
  * `freezing`: Freezing
</ParamField>

## Response

<ResponseField name="instances" type="object[]">
  Instance information.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Instance ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Instance name.
    </ResponseField>

    <ResponseField name="clusterId" type="string">
      Cluster ID.
    </ResponseField>

    <ResponseField name="clusterName" type="string">
      Cluster name.
    </ResponseField>

    <ResponseField name="status" type="string">
      Instance status.
    </ResponseField>

    <ResponseField name="imageUrl" type="string">
      Container image URL.
    </ResponseField>

    <ResponseField name="imageAuthId" type="string">
      Image repository authentication information.
    </ResponseField>

    <ResponseField name="command" type="string">
      Container startup command.
    </ResponseField>

    <ResponseField name="entrypoint" type="string">
      Container startup entrypoint.
    </ResponseField>

    <ResponseField name="cpuNum" type="string">
      Number of CPU cores for the instance.
    </ResponseField>

    <ResponseField name="memory" type="string">
      Memory size of the instance (GB).
    </ResponseField>

    <ResponseField name="gpuNum" type="string">
      Number of GPUs for the instance.
    </ResponseField>

    <ResponseField name="portMappings" type="object[]">
      Instance port information.

      <Expandable title="properties">
        <ResponseField name="port" type="integer">
          Port value.
        </ResponseField>

        <ResponseField name="type" type="string">
          Port type.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="productId" type="string">
      Product ID used to deploy the instance.
    </ResponseField>

    <ResponseField name="productName" type="string">
      Product name used to deploy the instance.
    </ResponseField>

    <ResponseField name="rootfsSize" type="integer">
      Root filesystem size (GB).
    </ResponseField>

    <ResponseField name="volumeMounts" type="object[]">
      Instance storage configuration.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Storage type. Values:

          * network: Cloud storage.
          * local: Local storage.
        </ResponseField>

        <ResponseField name="size" type="string">
          Storage capacity.
        </ResponseField>

        <ResponseField name="id" type="string">
          Cloud storage ID. Returned when type = network.
        </ResponseField>

        <ResponseField name="mountPath" type="string">
          Storage mount path.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="statusError" type="object">
      Error information when instance creation fails or the instance is unavailable.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          Abnormal instance status.
        </ResponseField>

        <ResponseField name="message" type="string">
          Error message.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="envs" type="object[]">
      Instance environment variable information.

      <Expandable title="properties">
        <ResponseField name="key" type="string">
          Environment variable name.
        </ResponseField>

        <ResponseField name="value" type="string">
          Environment variable value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="kind" type="string">
      Instance type.
    </ResponseField>

    <ResponseField name="billingMode" type="string">
      Billing mode for the instance. Values:

      * onDemand: Pay-as-you-go billing.
      * monthly: Subscription (monthly or yearly) billing.
      * spot: Spot instance billing.
    </ResponseField>

    <ResponseField name="endTime" type="string">
      Expiration time for subscription instances. For pay-as-you-go instances, returns -1.
    </ResponseField>

    <ResponseField name="spotStatus" type="string">
      Spot instance status. Values:

      * `running`: Running
      * `notified`: Notified for reclaim
      * `reclaiming`: Being reclaimed
      * `terminated`: Terminated
    </ResponseField>

    <ResponseField name="spotReclaimTime" type="string">
      Spot instance reclaim time. Value "0" means reclaim has not started.
    </ResponseField>
  </Expandable>
</ResponseField>

<ParamField name="total" type="integer">
  Total number of results.
</ParamField>


# List Jobs
Source: https://docs.novita.ai/api-reference/gpu-instance-list-jobs

GET https://api.novita.ai/gpu-instance/openapi/v1/jobs

## API Description

Retrieve a paginated list of background jobs for GPU instances. You can filter by job ID, state, type, time range, and creators.

## Request Headers

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer">
  Maximum number of items returned per page. Integer, value >= 0.
</ParamField>

<ParamField type="integer">
  Page number to retrieve. Integer, value >= 0.
</ParamField>

<ParamField type="string">
  Filter by job ID. String, length 0–255.
</ParamField>

<ParamField type="string">
  Filter by job state. One of: `pulling` (preparing), `running`, `fail`, `success`, `break`.
</ParamField>

<ParamField type="string">
  Filter by job type. One of: `saveImage`, `instanceMigrate`, `autoInstanceMigrate`.
</ParamField>

<ParamField type="integer">
  Start of time range (Unix timestamp in seconds). Integer, value >= 0. Default: 0.
</ParamField>

<ParamField type="integer">
  End of time range (Unix timestamp in seconds). Integer, value >= 0. Default: 0.
</ParamField>

<ParamField type="string">
  Filter by creator user ID.
</ParamField>

## cURL Example

```bash theme={"system"}
curl --location --request GET 'https://api.novita.ai/gpu-instance/openapi/v1/jobs?pageSize=5&pageNum=1&jobId=&type=&state=&startTime=&endTime=&creators=' \
--header 'Authorization: Bearer {{API_KEY}}'
```

## Response

<ResponseField name="jobs" type="object[]">
  Job list.

  <Expandable title="properties">
    <ResponseField name="Id" type="string">
      Job ID.
    </ResponseField>

    <ResponseField name="user" type="string">
      The user ID who initiated the job.
    </ResponseField>

    <ResponseField name="type" type="string">
      Job type. One of: `saveImage`, `instanceMigrate`, `autoInstanceMigrate`.
    </ResponseField>

    <ResponseField name="envs" type="object[]">
      Environment variables for the job.

      <Expandable title="properties">
        <ResponseField name="key" type="string">
          Environment variable name.
        </ResponseField>

        <ResponseField name="value" type="string">
          Environment variable value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="state" type="object">
      Current job state information.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          Current job state. One of: `pulling`, `running`, `fail`, `success`, `break`.
        </ResponseField>

        <ResponseField name="error" type="string">
          Error code, if any.
        </ResponseField>

        <ResponseField name="errorMessage" type="string">
          Error message, if any.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="logAddress" type="string">
      Log URL for viewing job execution logs.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      Job creation time (Unix timestamp in seconds).
    </ResponseField>

    <ResponseField name="instanceId" type="string">
      Associated instance ID when the job was executed.
    </ResponseField>
  </Expandable>
</ResponseField>

<ParamField name="total" type="integer">
  Total number of jobs matching the query.
</ParamField>


# List Network Storage
Source: https://docs.novita.ai/api-reference/gpu-instance-list-network-storage

GET https://api.novita.ai/gpu-instance/openapi/v1/networkstorages/list

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer" />

<ParamField type="integer" />

<ParamField type="string" />

<ParamField type="string" />

## Response

<ResponseField name="data" type="object[]">
  <Expandable title="properties">
    <ResponseField name="storageId" type="string" />

    <ResponseField name="storageName" type="string" />

    <ResponseField name="storageSize" type="integer" />

    <ResponseField name="clusterId" type="string" />

    <ResponseField name="clusterName" type="string" />

    <ResponseField name="price" type="string" />
  </Expandable>
</ResponseField>

<ResponseField name="total" type="integer" />

## Example

Request

```bash theme={"system"}
curl --request GET \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/networkstorages/list' \
  --header 'Authorization: Bearer {{API Key}}'
```

Response

```json theme={"system"}
{
  "data": [
    {
      "storageId": "d4e82677-3f80-4020-a731-d15b1c589aa8",
      "storageName": "123",
      "storageSize": 10,
      "clusterId": "5",
      "clusterName": "EU-01",
      "price": "100"
    },
    {
      "storageId": "082383c6-bc28-4bfa-a3b3-b6d6511bdf64",
      "storageName": "123",
      "storageSize": 10,
      "clusterId": "5",
      "clusterName": "EU-01",
      "price": "100"
    }
  ],
  "total": 2
}
```


# List GPU Products
Source: https://docs.novita.ai/api-reference/gpu-instance-list-products

GET https://api.novita.ai/gpu-instance/openapi/v1/products

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Filter by specified cluster ID. String, length limit: 0-255 characters.
</ParamField>

<ParamField type="integer">
  Filter by number of GPUs. Integer, valid range: \[0, 8].
</ParamField>

<ParamField type="string">
  Filter by product name (fuzzy match). String, length limit: 0-255 characters.
</ParamField>

<ParamField type="integer">
  Filter by minimum CPU cores per GPU. Integer, value must be greater than or equal to 0.
</ParamField>

<ParamField type="integer">
  Filter by minimum memory per GPU (GB). Integer, value must be greater than or equal to 0.
</ParamField>

<ParamField type="integer">
  Filter by minimum root filesystem size per GPU (GB). Integer, value must be greater than or equal to 0.
</ParamField>

<ParamField type="string">
  Filter by billing method. Options:

  * `onDemand`: Pay-as-you-go instance (default)
  * `monthly`: Subscription instance (monthly or yearly)
  * `spot`: Spot instance billing
</ParamField>

## Response

<ResponseField name="data" type="object[]">
  GPU product information.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Product ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Product name.
    </ResponseField>

    <ResponseField name="cpuPerGpu" type="integer">
      Number of CPU cores per GPU.
    </ResponseField>

    <ResponseField name="memoryPerGpu" type="integer">
      Memory size per GPU (GB).
    </ResponseField>

    <ResponseField name="diskPerGpu" type="integer">
      Disk size per GPU (GB).
    </ResponseField>

    <ResponseField name="availableDeploy" type="boolean">
      Whether this product can be used to create an instance. Values:

      * true: The product can be used to create an instance.
      * false: Insufficient resources, cannot create an instance.
    </ResponseField>

    <ResponseField name="minRootFS" type="integer">
      Minimum available root filesystem size (GB).
    </ResponseField>

    <ResponseField name="maxRootFS" type="integer">
      Maximum available root filesystem size (GB).
    </ResponseField>

    <ResponseField name="minLocalStorage" type="integer">
      Minimum available local storage size (GB).
    </ResponseField>

    <ResponseField name="maxLocalStorage" type="integer">
      Maximum available local storage size (GB).
    </ResponseField>

    <ResponseField name="regions" type="[string]">
      Available clusters. Indicates that this product is only available in the specified clusters. If the list is empty, the product is available in all clusters.
    </ResponseField>

    <ResponseField name="price" type="integer">
      Price for creating a pay-as-you-go instance with this product.
    </ResponseField>

    <ResponseField name="monthlyPrice" type="object[]">
      Price for creating a subscription (monthly or yearly) instance with this product.

      <Expandable title="properties">
        <ResponseField name="price" type="integer">
          Unit price for the subscription instance.
        </ResponseField>

        <ResponseField name="month" type="integer">
          Subscription duration, in months.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="billingMethods" type="string[]">
      The billing methods supported by this product. Valid values:

      * `onDemand`: Pay-as-you-go billing
      * `monthly`: Monthly subscription billing
      * `spot`: Spot instance billing
    </ResponseField>

    <ResponseField name="spotPrice" type="string">
      Spot billing instance price.
    </ResponseField>

    <ResponseField name="inventoryState" type="string">
      Product inventory status:

      * `none`: Out of stock
      * `low`: Low stock
      * `normal`: Normal stock
      * `high`: Sufficient stock
    </ResponseField>
  </Expandable>
</ResponseField>


# List Templates
Source: https://docs.novita.ai/api-reference/gpu-instance-list-templates

GET https://api.novita.ai/gpu-instance/openapi/v1/templates

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer">
  Page size. Integer, value >= 0.
</ParamField>

<ParamField type="integer">
  Page number. Integer, value >= 0.
</ParamField>

<ParamField type="string">
  Filter the templates by name (fuzzy match). String, length limit: 1-255 characters.
</ParamField>

<ParamField type="string">
  Filter the templates by type.
</ParamField>

<ParamField type="string">
  Filter the templates by channel.<br />
  Enum: `official`, `community`, `private`
</ParamField>

<ParamField type="boolean">
  Template visibility scope. Values: `true`, `false`.<br />
  Note:

  * Query official/community templates: set to `false`
  * Query private templates: set to `true`
</ParamField>

## Response

<ResponseField name="template" type="object[]">
  <Expandable title="properties">
    <ResponseField name="Id" type="string">
      Template ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Template name.
    </ResponseField>

    <ResponseField name="readme" type="string">
      Template README content (in Markdown format).
    </ResponseField>

    <ResponseField name="type" type="string">
      Template type.Enum: `instance`
    </ResponseField>

    <ResponseField name="channel" type="string">
      Template channel.Enum: `official`, `community`, `private`
    </ResponseField>

    <ResponseField name="image" type="string">
      Docker image address for instance startup.
    </ResponseField>

    <ResponseField name="imageAuth" type="string">
      Image repository authentication ID for private images.
    </ResponseField>

    <ResponseField name="startCommand" type="string">
      Startup command for the instance.
    </ResponseField>

    <ResponseField name="entrypoint" type="string">
      Instance startup entrypoint.
    </ResponseField>

    <ResponseField name="rootfsSize" type="integer">
      Rootfs storage (GB).
    </ResponseField>

    <ResponseField name="ports" type="object[]">
      Exposed ports settings.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Exposed port types.Enum: `http, tcp`
        </ResponseField>

        <ResponseField name="ports" type="[integer]">
          Exposed ports (maximum 10).
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="envs" type="object[]">
      Environment variables injected into instance.

      <Expandable title="properties">
        <ResponseField name="key" type="string">
          Environment variable key.
        </ResponseField>

        <ResponseField name="value" type="string">
          Environment variable value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="tools" type="object[]">
      Template built-in tools (only available for official templates).

      <Expandable title="properties">
        <ResponseField name="name" type="string">
          Tool name.
        </ResponseField>

        <ResponseField name="describe" type="string">
          Tool description.
        </ResponseField>

        <ResponseField name="port" type="integer">
          Tool port.
        </ResponseField>

        <ResponseField name="type" type="string">
          Tool protocol type. Enum: `http`, `tcp`
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      Template creation timestamp (Unix seconds).
    </ResponseField>

    <ResponseField name="recommendCards" type="object[]">
      Recommended GPU card specs (only available for official templates).

      <Expandable title="properties">
        <ResponseField name="gpuSpecId" type="string">
          GPU spec ID.
        </ResponseField>

        <ResponseField name="cardNum" type="string">
          Recommended card number.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="minCudaVersion" type="string">
      Minimum required CUDA version, e.g. 11.8, 12.4.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="integer">
  Total number of templates.
</ResponseField>

## Example

Request

```bash theme={"system"}
curl --request GET \
  --url 'https://api.novita.ai/gpu-instance/openapi/v1/templates' \
  --header 'Authorization: Bearer {{API Key}}'
```

Response

````json theme={"system"}
{
  "template": [
    {
      "Id": "100",
      "name": "name",
      "readme": "```\nreadme\n```\n# uiohiu\n68686",
      "type": "instance",
      "channel": "official",
      "image": "image",
      "imageAuth": "",
      "startCommand": "startCommand",
      "entrypoint": "",
      "rootfsSize": 10,
      "ports": [
        {
          "type": "tcp",
          "ports": [8080, 8090]
        },
        {
          "type": "http",
          "ports": [6000, 60001]
        }
      ],
      "envs": [
        {
          "key": "testkey",
          "value": "123"
        }
      ],
      "tools": [
        {
          "name": "Jupyter",
          "describe": "Start Jupyter Notebook",
          "port": 8888,
          "type": "http"
        }
      ],
      "createdAt": "1711995567",
      "recommendCards": [
        {
          "gpuSpecId": "4090.18c.60g",
          "cardNum": "2"
        }
      ],
      "minCudaVersion": "11.8"
    }
  ],
  "total": 10
}
````


# List VPC Networks
Source: https://docs.novita.ai/api-reference/gpu-instance-list-vpc

GET https://api.novita.ai/gpu-instance/openapi/v1/networks

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer">
  Number of items per page. Integer, value must be greater than or equal to 0.
</ParamField>

<ParamField type="integer">
  Page number to retrieve. Integer, value must be greater than or equal to 0.
</ParamField>

<ParamField type="string">
  Custom network name (supports fuzzy search). String, length limit: 0-255 characters.
</ParamField>

<ParamField type="string">
  Creator user ID. String, length limit: 0-255 characters.
</ParamField>

## Response

<ResponseField name="network" type="object[]">
  VPC network information.

  <Expandable title="properties">
    <ResponseField name="Id" type="string">
      VPC network ID.
    </ResponseField>

    <ResponseField name="user" type="string">
      User account ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      VPC network name.
    </ResponseField>

    <ResponseField name="state" type="object[]">
      VPC network status.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          VPC network status. Possible values:

          * creating: Being created.
          * ready: Successfully created.
        </ResponseField>

        <ResponseField name="error" type="string">
          Error message if VPC network creation fails or the network is unavailable.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="segment" type="string">
      VPC network segment.
    </ResponseField>

    <ResponseField name="clusterId" type="string">
      Cluster ID.
    </ResponseField>

    <ResponseField name="Addresses" type="object[]">
      Instance information under the VPC network.

      <Expandable title="properties">
        <ResponseField name="Id" type="string">
          Instance ID.
        </ResponseField>

        <ResponseField name="Ip" type="string">
          Instance IP address.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="createTime" type="string">
      VPC network creation time. Format: Unix timestamp.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="string">
  Total number of results.
</ResponseField>


# Migrate Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-migrate-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/migrate

## API Description

When migrating an instance, any new data on the system disk and local disk will not be preserved.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The ID of the instance to be migrated.
</ParamField>


# Renew Subscription Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-renewal-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/renewInstance

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The ID of the subscription instance to be renewed.
</ParamField>

<ParamField type="integer">
  Renewal period, in months.
</ParamField>


# Restart Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-restart-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/restart

**This API is used to restart a specific GPU instance.**

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  ID of the instance to be restarted.
</ParamField>


# Save Instance Image
Source: https://docs.novita.ai/api-reference/gpu-instance-save-image

POST https://api.novita.ai/gpu-instance/openapi/v1/job/save/image

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Instance ID. String, length: 1-255 characters.
</ParamField>

<ParamField type="string">
  Target image address, including registry/repository:tag. String, length: 1-4095 characters.
</ParamField>

<ParamField type="string">
  Container registry authentication ID. Not required for public registries or platform-provided registries. Required for third-party private registries.
</ParamField>

## Response

<ResponseField name="jobId" type="string">
  Image save job ID. Use this ID to check status and logs in the Console under Job Management.
</ResponseField>


# Set Auto Migrate Strategy
Source: https://docs.novita.ai/api-reference/gpu-instance-set-auto-migrate

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/setAutoMigrate

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Instance ID.
</ParamField>

<ParamField type="boolean">
  Enable/disable automatic instance migration.
</ParamField>

<ParamField type="boolean">
  Enable/disable automatic system disk migration during instance migration.
</ParamField>


# Set Auto Renew
Source: https://docs.novita.ai/api-reference/gpu-instance-set-autorenew

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/setAutoRenew

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string[]">
  Instance ID list.
</ParamField>

<ParamField type="boolean">
  Whether to enable automatic renewal.
</ParamField>

<ParamField type="integer">
  Automatic renewal period, in months.
</ParamField>


# Start Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-start-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/start

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  ID of the instance to be started.
</ParamField>


# Stop Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-stop-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/stop

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  ID of the instance to be stopped.
</ParamField>


# Update Image Prewarm Task
Source: https://docs.novita.ai/api-reference/gpu-instance-update-image-prewarm

POST https://api.novita.ai/gpu-instance/openapi/v1/image/prewarm/edit

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Prewarm task ID.
</ParamField>

<ParamField type="string">
  Task note.
</ParamField>


# Update VPC Network
Source: https://docs.novita.ai/api-reference/gpu-instance-update-vpc

POST https://api.novita.ai/gpu-instance/openapi/v1/network/update

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  Cluster ID. String, length limit: 1-255 characters.
</ParamField>

<ParamField type="string">
  Custom network name. String, length limit: 1-30 characters.
</ParamField>


# Upgrade Instance
Source: https://docs.novita.ai/api-reference/gpu-instance-upgrade-instance

POST https://api.novita.ai/gpu-instance/openapi/v1/gpu/instance/upgrade

## API Description

When upgrading an instance, all parameters must be provided in full.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The ID of the instance to be upgraded.
</ParamField>

<ParamField type="string">
  Container image URL. String, length limit: 1-500 characters.
</ParamField>

<ParamField type="string">
  Image registry authentication ID.
</ParamField>

<ParamField type="object[]">
  Instance environment variables. Up to 100 environment variable pairs can be created.

  <Expandable title="properties">
    <ParamField type="string">
      Environment variable name. String, length limit: 0-511 characters.
    </ParamField>

    <ParamField type="string">
      Environment variable value. String, length limit: 0-4095 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string">
  Container startup command. String, length limit: 0-2047 characters.
</ParamField>

<ParamField type="string">
  Container startup entrypoint. This setting will override the Docker image's ENTRYPOINT. String, length limit: 0-2047 characters.
</ParamField>

<ParamField type="boolean">
  Whether to retain data from the previous instance. Boolean, values: true or false.
</ParamField>

<ParamField type="object">
  Configure cloud storage (type: network). Each instance supports mounting up to 30 cloud storage volumes. To remove all cloud storage mounts, set volumeMounts to an empty array: \[].

  <Expandable title="properties">
    <ParamField type="object[]">
      Cloud storage mount information.

      <Expandable title="properties">
        <ParamField type="string">
          Storage type. Must be set to network.
        </ParamField>

        <ParamField type="string">
          Cloud storage ID.
        </ParamField>

        <ParamField type="string">
          Mount path for the cloud storage. Default: "/network". String, length limit: 1-4095 characters.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>


# Create Endpoint
Source: https://docs.novita.ai/api-reference/serverless-create-endpoint

POST https://api.novita.ai/gpu-instance/openapi/v1/endpoint/create

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="object">
  Endpoint configuration.

  <Expandable title="properties">
    <ParamField type="string">
      Endpoint name. String with a length limit of 0-220 characters.
    </ParamField>

    <ParamField type="string">
      Application name (reflected in the URL). The application name is part of the Endpoint URL, supports customization, and defaults to the Endpoint ID.
    </ParamField>

    <ParamField type="object">
      Worker configuration. The valid range is dynamically retrieved through the parameter limits API.

      <Expandable title="properties">
        <ParamField type="integer">
          Minimum number of workers.
        </ParamField>

        <ParamField type="integer">
          Maximum number of workers.
        </ParamField>

        <ParamField type="integer">
          Idle timeout in seconds.
        </ParamField>

        <ParamField type="integer">
          Maximum concurrency.
        </ParamField>

        <ParamField type="integer">
          Number of GPUs per worker.
        </ParamField>

        <ParamField type="integer">
          Request timeout (seconds).
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object[]">
      HTTP ports. Only one port is supported. Supported port range: 1-65535, excluding internal ports 2222, 2223, and 2224.

      <Expandable title="properties">
        <ParamField type="string">
          HTTP port.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object">
      Scaling policy. The valid range is dynamically retrieved through the parameter limits API.

      <Expandable title="properties">
        <ParamField type="string">
          Scaling policy type. Available values:

          * `queue`: Queue latency policy, scales workers based on request wait time in the queue.
          * `concurrency`: Queue request policy, scales workers based on the number of requests in the queue.
        </ParamField>

        <ParamField type="integer">
          The meaning of value depends on the type:

          * When type = queue, value represents the queue wait time in seconds.
          * When type = concurrency, value represents the maximum number of requests in the queue.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object">
      Container image configuration.

      <Expandable title="properties">
        <ParamField type="string">
          Image address. String with a length limit of 0-511 characters.
        </ParamField>

        <ParamField type="string">
          Private image credential ID (not required for public images or platform user images). String with a length limit of 0-255 characters.
        </ParamField>

        <ParamField type="string">
          Container startup command. String with a length limit of 0-2047 characters.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object[]">
      Product information.

      <Expandable title="properties">
        <ParamField type="string">
          Product ID.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="integer">
      Root filesystem size in GB. Currently fixed at 100.
    </ParamField>

    <ParamField type="object[]">
      Storage configuration in GB.

      <Expandable title="properties">
        <ParamField type="string">
          Storage type. Available values:

          * `local`: Local storage.
          * `network`: Network storage.
        </ParamField>

        <ParamField type="integer">
          Local storage size, currently fixed at 30. Not required for network storage.
        </ParamField>

        <ParamField type="string">
          Network storage ID. Not required for local storage.
        </ParamField>

        <ParamField type="string">
          Storage mount path. String with a length limit of 0-255 characters.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="string">
      Cluster information. Required when mounting cloud storage and must match the cluster ID where the cloud storage is located. String with a length limit of 0-255 characters.
    </ParamField>

    <ParamField type="object[]">
      Environment variables.

      <Expandable title="properties">
        <ParamField type="string">
          Environment variable name.
        </ParamField>

        <ParamField type="string">
          Environment variable value.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField type="object">
      Health check endpoint configuration.

      <Expandable title="properties">
        <ParamField type="string">
          The path to check when performing HTTP health checks.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="id" type="string">
  The created Endpoint ID.
</ResponseField>


# Delete Endpoint
Source: https://docs.novita.ai/api-reference/serverless-delete-endpoint

POST https://api.novita.ai/gpu-instance/openapi/v1/endpoint/delete

## Description

After deleting an endpoint, all worker processes associated with this endpoint will be automatically removed. Please proceed with caution.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The ID of the endpoint to delete.
</ParamField>


# Get Endpoint
Source: https://docs.novita.ai/api-reference/serverless-get-endpoint

GET https://api.novita.ai/gpu-instance/openapi/v1/endpoint

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="string">
  Endpoint ID.
</ParamField>

## Response

<ResponseField name="endpoint" type="object">
  Endpoint information.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Endpoint ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Endpoint name.
    </ResponseField>

    <ResponseField name="appName" type="string">
      Application name.
    </ResponseField>

    <ResponseField name="state" type="object">
      Endpoint status.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          Endpoint status. When the value is `serving`, the endpoint is available for service.
        </ResponseField>

        <ResponseField name="error" type="string">
          Error code when the endpoint is in an abnormal state.
        </ResponseField>

        <ResponseField name="message" type="string">
          Error message when the endpoint is in an abnormal state.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="url" type="string">
      Endpoint URL. You can access your HTTP service via this URL.
    </ResponseField>

    <ResponseField name="workerConfig" type="object">
      Worker configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="minNum" type="integer">
          Minimum number of workers.
        </ResponseField>

        <ResponseField name="maxNum" type="integer">
          Maximum number of workers.
        </ResponseField>

        <ResponseField name="freeTimeout" type="string">
          Idle timeout, in seconds.
        </ResponseField>

        <ResponseField name="maxConcurrent" type="string">
          Maximum concurrency.
        </ResponseField>

        <ResponseField name="gpuNum" type="integer">
          Number of GPUs per worker.
        </ResponseField>

        <ResponseField name="requestTimeout" type="integer">
          Request timeout (seconds).
        </ResponseField>

        <ResponseField name="cudaVersion" type="string">
          CUDA version.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="policy" type="object">
      Auto-scaling policy for the endpoint.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Policy type. Options:

          * `queue`: Queue latency policy, adjusts the number of workers based on the waiting time of requests in the queue.
          * `concurrency`: Queue request policy, automatically adjusts the number of workers based on the number of requests in the queue.
        </ResponseField>

        <ResponseField name="value" type="string">
          The meaning of value depends on the type:

          * If type = queue, value is the queue waiting time in seconds.
          * If type = concurrency, value is the maximum number of requests in the queue.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="image" type="object">
      Image configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="image" type="string">
          Image URL.
        </ResponseField>

        <ResponseField name="authId" type="string">
          Image repository credential.
        </ResponseField>

        <ResponseField name="command" type="string">
          Container startup command.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="rootfsSize" type="integer">
      System disk size.
    </ResponseField>

    <ResponseField name="volumeMounts" type="object[]">
      Storage configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Storage type. Options:

          * `local`: Local storage.
          * `network`: Cloud storage.
        </ResponseField>

        <ResponseField name="id" type="string">
          Cloud storage ID.
        </ResponseField>

        <ResponseField name="size" type="integer">
          Returned when type = local, indicates the size of the local disk.
        </ResponseField>

        <ResponseField name="mountPath" type="string">
          Mount path for the storage.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="envs" type="object[]">
      Environment variables.

      <Expandable title="properties">
        <ResponseField name="key" type="string">
          Environment variable name.
        </ResponseField>

        <ResponseField name="value" type="string">
          Environment variable value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="ports" type="object[]">
      HTTP ports.

      <Expandable title="properties">
        <ResponseField name="port" type="integer">
          HTTP port.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="workers" type="object[]">
      Worker information for the endpoint.

      <Expandable title="properties">
        <ResponseField name="id" type="string">
          Worker ID.
        </ResponseField>

        <ResponseField name="state" type="object">
          Worker status.

          <Expandable title="properties">
            <ResponseField name="state" type="string">
              Worker status. When the value is `running`, the worker is available for service.
            </ResponseField>

            <ResponseField name="error" type="string">
              Error code when the worker is in an abnormal state.
            </ResponseField>

            <ResponseField name="message" type="string">
              Error message when the worker is in an abnormal state.
            </ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="log" type="string">
          Log path for the worker.
        </ResponseField>

        <ResponseField name="metrics" type="string">
          Monitoring information for the worker.
        </ResponseField>

        <ResponseField name="healthy" type="boolean">
          Whether the worker is healthy.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="products" type="object[]">
      Product information.

      <Expandable title="properties">
        <ResponseField name="id" type="string">
          Product ID.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="healthy" type="object">
      Health check configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="path" type="string">
          Path to be checked via HTTP request for health monitoring.
        </ResponseField>

        <ResponseField name="initialDelay" type="integer">
          Time to wait after startup before starting health checks, in seconds.
        </ResponseField>

        <ResponseField name="period" type="integer">
          Interval between health checks, in seconds.
        </ResponseField>

        <ResponseField name="timeout" type="integer">
          Timeout for health checks, in seconds.
        </ResponseField>

        <ResponseField name="successThreshold" type="integer">
          Number of consecutive successes required to consider the check successful after a previous failure.
        </ResponseField>

        <ResponseField name="failureThreshold" type="integer">
          Number of consecutive failures required to consider the check failed after a previous success.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="clusterID" type="string">
      Cluster ID where the cloud storage resides. Returned when using cloud storage.
    </ResponseField>

    <ResponseField name="log" type="string">
      Log path for the endpoint.
    </ResponseField>
  </Expandable>
</ResponseField>


# List Endpoints
Source: https://docs.novita.ai/api-reference/serverless-list-endpoint

GET https://api.novita.ai/gpu-instance/openapi/v1/endpoints

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Query Parameters

<ParamField type="integer">
  Maximum number of items returned per page.
</ParamField>

<ParamField type="integer">
  Current page number.
</ParamField>

## Response

<ResponseField name="endpoints" type="object[]">
  Endpoint information.

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      Endpoint ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Endpoint name.
    </ResponseField>

    <ResponseField name="appName" type="string">
      Application name.
    </ResponseField>

    <ResponseField name="state" type="object">
      Endpoint status.

      <Expandable title="properties">
        <ResponseField name="state" type="string">
          Endpoint status. When the value is `serving`, the endpoint is available for service.
        </ResponseField>

        <ResponseField name="error" type="string">
          Error code when the endpoint is in an abnormal state.
        </ResponseField>

        <ResponseField name="message" type="string">
          Error message when the endpoint is in an abnormal state.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="url" type="string">
      Endpoint URL. You can access your HTTP service via this URL.
    </ResponseField>

    <ResponseField name="workerConfig" type="object">
      Worker configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="minNum" type="integer">
          Minimum number of workers.
        </ResponseField>

        <ResponseField name="maxNum" type="integer">
          Maximum number of workers.
        </ResponseField>

        <ResponseField name="freeTimeout" type="string">
          Idle timeout, in seconds.
        </ResponseField>

        <ResponseField name="maxConcurrent" type="string">
          Maximum concurrency.
        </ResponseField>

        <ResponseField name="gpuNum" type="integer">
          Number of GPUs per worker.
        </ResponseField>

        <ResponseField name="requestTimeout" type="integer">
          Request timeout (seconds).
        </ResponseField>

        <ResponseField name="cudaVersion" type="string">
          CUDA version.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="policy" type="object">
      Auto-scaling policy for the endpoint.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Policy type. Options:

          * `queue`: Queue latency policy, adjusts the number of workers based on the waiting time of requests in the queue.
          * `concurrency`: Queue request policy, automatically adjusts the number of workers based on the number of requests in the queue.
        </ResponseField>

        <ResponseField name="value" type="string">
          The meaning of value depends on the type:

          * If type = queue, value is the queue waiting time in seconds.
          * If type = concurrency, value is the maximum number of requests in the queue.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="image" type="object">
      Image configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="image" type="string">
          Image URL.
        </ResponseField>

        <ResponseField name="authId" type="string">
          Image repository credential.
        </ResponseField>

        <ResponseField name="command" type="string">
          Container startup command.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="rootfsSize" type="integer">
      System disk size.
    </ResponseField>

    <ResponseField name="volumeMounts" type="object[]">
      Storage configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="type" type="string">
          Storage type. Options:

          * `local`: Local storage.
          * `network`: Cloud storage.
        </ResponseField>

        <ResponseField name="id" type="string">
          Cloud storage ID. Returned when type = network.
        </ResponseField>

        <ResponseField name="size" type="integer">
          Returned when type = local, indicates the size of the local disk.
        </ResponseField>

        <ResponseField name="mountPath" type="string">
          Mount path for the storage.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="envs" type="object[]">
      Environment variables.

      <Expandable title="properties">
        <ResponseField name="key" type="string">
          Environment variable name.
        </ResponseField>

        <ResponseField name="value" type="string">
          Environment variable value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="ports" type="object[]">
      HTTP ports.

      <Expandable title="properties">
        <ResponseField name="port" type="string">
          HTTP port.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="workers" type="object[]">
      Worker information for the endpoint.

      <Expandable title="properties">
        <ResponseField name="id" type="string">
          Worker ID.
        </ResponseField>

        <ResponseField name="state" type="object">
          Worker status.

          <Expandable title="properties">
            <ResponseField name="state" type="string">
              Worker status. When the value is `running`, the worker is available for service.
            </ResponseField>

            <ResponseField name="error" type="string">
              Error code when the worker is in an abnormal state.
            </ResponseField>

            <ResponseField name="message" type="string">
              Error message when the worker is in an abnormal state.
            </ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="log" type="string">
          Log path for the worker.
        </ResponseField>

        <ResponseField name="metrics" type="string">
          Monitoring information for the worker.
        </ResponseField>

        <ResponseField name="healthy" type="boolean">
          Whether the worker is healthy.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="products" type="object[]">
      Product information.

      <Expandable title="properties">
        <ResponseField name="id" type="string">
          Product ID.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="healthy" type="object">
      Health check configuration for the endpoint.

      <Expandable title="properties">
        <ResponseField name="path" type="string">
          Path to be checked via HTTP request for health monitoring.
        </ResponseField>

        <ResponseField name="initialDelay" type="integer">
          Time to wait after startup before starting health checks, in seconds.
        </ResponseField>

        <ResponseField name="period" type="integer">
          Interval between health checks, in seconds.
        </ResponseField>

        <ResponseField name="timeout" type="integer">
          Timeout for health checks, in seconds.
        </ResponseField>

        <ResponseField name="successThreshold" type="integer">
          Number of consecutive successes required to consider the check successful after a previous failure.
        </ResponseField>

        <ResponseField name="failureThreshold" type="integer">
          Number of consecutive failures required to consider the check failed after a previous success.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="clusterID" type="string">
      Cluster ID where the cloud storage resides. Returned when using cloud storage.
    </ResponseField>

    <ResponseField name="log" type="string">
      Log path for the endpoint.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="integer">
  Total number of results.
</ResponseField>


# Get Endpoint Parameter Limit Ranges
Source: https://docs.novita.ai/api-reference/serverless-query-endpoint-limit

GET https://api.novita.ai/gpu-instance/openapi/v1/endpoint/limit

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Response

<ResponseField name="minRootfsSize" type="integer">
  Minimum root filesystem size (GB).
</ResponseField>

<ResponseField name="maxRootfsSize" type="integer">
  Maximum root filesystem size (GB).
</ResponseField>

<ResponseField name="freeRootfsSize" type="integer">
  Free root filesystem size (GB).
</ResponseField>

<ResponseField name="minLocalVolumeSize" type="integer">
  Minimum local volume size (GB).
</ResponseField>

<ResponseField name="maxLocalVolumeSize" type="integer">
  Maximum local volume size (GB).
</ResponseField>

<ResponseField name="freeLocalVolumeSize" type="integer">
  Free local volume size (GB).
</ResponseField>

<ResponseField name="minWorkerNum" type="integer">
  Minimum number of worker nodes.
</ResponseField>

<ResponseField name="maxWorkerNum" type="integer">
  Maximum number of worker nodes.
</ResponseField>

<ResponseField name="minFreeTimeout" type="integer">
  Minimum idle timeout (seconds).
</ResponseField>

<ResponseField name="maxFreeTimeout" type="integer">
  Maximum idle timeout (seconds).
</ResponseField>

<ResponseField name="minConcurrencyNum" type="integer">
  Minimum concurrency.
</ResponseField>

<ResponseField name="maxConcurrencyNum" type="integer">
  Maximum concurrency.
</ResponseField>

<ResponseField name="minQueueWaitTime" type="integer">
  Minimum queue wait time (seconds).
</ResponseField>

<ResponseField name="maxQueueWaitTime" type="integer">
  Maximum queue wait time (seconds).
</ResponseField>

<ResponseField name="minRequestNum" type="integer">
  Minimum number of requests.
</ResponseField>

<ResponseField name="maxRequestNum" type="integer">
  Maximum number of requests.
</ResponseField>

<ResponseField name="minGPUNum" type="integer">
  Minimum number of GPUs.
</ResponseField>

<ResponseField name="maxGPUNum" type="integer">
  Maximum number of GPUs.
</ResponseField>

<ResponseField name="minRequestTimeout" type="integer">
  Minimum request timeout.
</ResponseField>

<ResponseField name="maxRequestTimeout" type="integer">
  Maximum request timeout.
</ResponseField>

<ResponseField name="cudaVersionList" type="string[]">
  Supported CUDA version list.
</ResponseField>


# Update Endpoint
Source: https://docs.novita.ai/api-reference/serverless-update-endpoint

POST https://api.novita.ai/gpu-instance/openapi/v1/endpoint/update

## Description

When updating an endpoint, all parameters must be provided in full.

## Request Headers

<ParamField type="string">
  Enum: `application/json`
</ParamField>

<ParamField type="string">
  Bearer authentication format, for example: Bearer \{\{API Key}}.
</ParamField>

## Request Body

<ParamField type="string">
  The Endpoint ID to update.
</ParamField>

<ParamField type="string">
  Endpoint name.
</ParamField>

<ParamField type="string">
  Cluster information. Required when mounting cloud storage and must match the cluster ID where the cloud storage is located. String with a length limit of 0-255 characters.
</ParamField>

<ParamField type="object">
  Worker configuration. The valid range is dynamically retrieved through the parameter limits API.

  <Expandable title="properties">
    <ParamField type="integer">
      Minimum number of workers.
    </ParamField>

    <ParamField type="integer">
      Maximum number of workers.
    </ParamField>

    <ParamField type="integer">
      Idle timeout in seconds.
    </ParamField>

    <ParamField type="integer">
      Maximum concurrency.
    </ParamField>

    <ParamField type="integer">
      Number of GPUs per worker.
    </ParamField>

    <ParamField type="integer">
      Request timeout (seconds).
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  HTTP ports. Only one port is supported. Supported port range: 1-65535, excluding internal ports 2222, 2223, and 2224.

  <Expandable title="properties">
    <ParamField type="string">
      HTTP port.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  Scaling policy. The valid range is dynamically retrieved through the parameter limits API.

  <Expandable title="properties">
    <ParamField type="string">
      Scaling policy type. Available values:

      * `queue`: Queue latency policy, scales workers based on request wait time in the queue.
      * `concurrency`: Queue request policy, scales workers based on the number of requests in the queue.
    </ParamField>

    <ParamField type="integer">
      The meaning of value depends on the type:

      * When type = queue, value represents the queue wait time in seconds.
      * When type = concurrency, value represents the maximum number of requests in the queue.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  Container image configuration.

  <Expandable title="properties">
    <ParamField type="string">
      Image address. String with a length limit of 0-511 characters.
    </ParamField>

    <ParamField type="string">
      Private image credential ID (not required for public images or platform user images). String with a length limit of 0-255 characters.
    </ParamField>

    <ParamField type="string">
      Container startup command. String with a length limit of 0-2047 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Storage configuration in GB.

  <Expandable title="properties">
    <ParamField type="string">
      Storage type. Available values:

      * `local`: Local storage.
      * `network`: Network storage.
    </ParamField>

    <ParamField type="integer">
      Local storage size, currently fixed at 30. Not required for network storage.
    </ParamField>

    <ParamField type="string">
      Network storage ID. Not required for local storage.
    </ParamField>

    <ParamField type="string">
      Storage mount path. String with a length limit of 0-255 characters.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object[]">
  Environment variables.

  <Expandable title="properties">
    <ParamField type="string">
      Environment variable name.
    </ParamField>

    <ParamField type="string">
      Environment variable value.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="object">
  Health check endpoint configuration.

  <Expandable title="properties">
    <ParamField type="string">
      The path for health checks.
    </ParamField>
  </Expandable>
</ParamField>


# September 5, 2025 Product Updates
Source: https://docs.novita.ai/changelog/01-09-25--05-09-25



## **Model APIs Updates**

### **New Features**

* **Optimize the Novita LLM Playground**

  The Novita LLM Playground supports TTFT/TPS performance demonstration.

***

## **GPUs Updates**

### **New Features**

* **Prewarm image**

  Prewarming image can shorten the time for instances to pull image and save costs


# Sep 30, 2026 Multimodal Model Deprecation Notice
Source: https://docs.novita.ai/changelog/01-09-26

Novita will retire selected image, video, audio, and translation models on Sep 30, 2026. Review the affected models and migrate before the cutoff.

The following multimodal models will be retired from the Novita platform on Sep 30, 2026. If your application uses an affected model, migrate to the recommended replacement before the retirement date. Where no replacement is listed, update your application to stop using the model before the cutoff.

## Affected models

| Model ID | Retirement date | Replacement model | Notes |
| - | - | - | - |
| Qwen-Image Text to Image | Sep 30, 2026 | — | |
| Qwen-Image Edit | Sep 30, 2026 | — | |
| Wan 2.5 Preview Text to Video | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.5 Preview Image to Video | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.6 Text to Video | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.6 Image to Video | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.6 Reference-to-Video | Sep 30, 2026 | Kling 3.0 | |
| Minimax Hailuo 2.3 Text to Video | Sep 30, 2026 | Kling 3.0 | |
| Minimax Hailuo 2.3 Image to Video | Sep 30, 2026 | Minimax H3 | |
| Minimax Hailuo 2.3 Fast Image to Video | Sep 30, 2026 | Minimax H3 | |
| Wan 2.7 Video Editing | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.7 Reference-to-Video | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.7 Text-to-Video | Sep 30, 2026 | Kling 3.0 | |
| Wan 2.7 Image-to-Video | Sep 30, 2026 | Kling 3.0 | |
| Heygen Video-translate | Sep 30, 2026 | — | |
| MiniMax Music | Sep 30, 2026 | — | |
| MiniMax Lyrics | Sep 30, 2026 | — | |
| MOSS TTS v1.5 | Sep 30, 2026 | — | |

## Action required

Migrate affected workloads before Sep 30, 2026. For models without a listed replacement, update your application to stop sending requests to the model before the retirement date.


# GPU Pricing Update Notice
Source: https://docs.novita.ai/changelog/02-03-26



## **GPU Services Pricing Adjustment**

Effective **March 16, 2026**, we will be updating our pricing for **GPU Instances** and **Serverless GPU** services globally.

We remain committed to delivering the best value and performance. If you have any questions or concerns, please reach out to us via our **Discord** community or submit a support ticket.

Thank you for your continued support.

**The Novita AI Team**


# June 6, 2025 Product Updates
Source: https://docs.novita.ai/changelog/02-06-25--06-06-25



## **New Features**

* **Recommend GPUs according to model templates**

  Recommend GPUs based on model templates to help developers quickly decide on GPU selection.
* **New API Error Code Reference for Model Services**

  To enhance the developer experience, we’ve added a detailed list of API error codes for **image, video, audio, and large language model services** in our Documentation Center. It covers common issues like request validation, format limits, and content compliance, helping you troubleshoot more efficiently.

  Visit the [Documentation](https://novita.ai/docs/api-reference/basic-error-code) Center to learn more.


# July 03, 2025 Notice
Source: https://docs.novita.ai/changelog/03-07-25



### **Model Decommissioning Notice**

To continuously enhance platform service efficiency, focus on core product capability upgrades, and deliver more advanced, high-quality, and standardized services, Novita will decommission certain large language models (LLMs) on **July 5, 2025(UTC 0:00)**. **The deprecated models and their corresponding replacements** are listed below:

| **Deprecated Models** | **Deprecation Date** | **Replacement Models** |
| :- | :- | :- |
| thudm/glm-z1-9b-0414 | 2025-7-5 | qwen/qwen3-4b-fp8 |
| thudm/glm-4-9b-0414 | 2025-7-5 | qwen/qwen3-4b-fp8 |
| thudm/glm-z1-rumination-32b-0414 | 2025-7-5 | qwen/qwen3-32b-fp8 |
| thudm/glm-z1-32b-0414 | 2025-7-5 | qwen/qwen3-30b-a3b-fp8 |


# Aug 3, 2026 Model API Updates
Source: https://docs.novita.ai/changelog/03-08-26



## Model Deprecation Notice

The following image, video and audio models will be retired from the Novita platform on **Aug 19, 2026**.

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| FLUX.1 Kontext Dev | Aug 19, 2026 | Qwen-Image Text to Image | - |
| FLUX.1 Kontext Pro | Aug 19, 2026 | Qwen-Image Text to Image | - |
| FLUX.1 Kontext Max | Aug 19, 2026 | Qwen-Image Text to Image | - |
| Flux 2 Dev Image Gen | Aug 19, 2026 | Qwen-Image Text to Image | - |
| Flux 2 Flex Image Gen | Aug 19, 2026 | Qwen-Image Text to Image | - |
| Flux 2 Pro Image Gen | Aug 19, 2026 | Qwen-Image Text to Image | - |
| Z Image Turbo LoRA | Aug 19, 2026 | Qwen-Image Text to Image | - |
| Z Image Turbo | Aug 19, 2026 | Qwen-Image Text to Image | - |
| Image Eraser | Aug 19, 2026 | - | - |
| Image Upscaling | Aug 19, 2026 | - | - |
| Image Remove Background | Aug 19, 2026 | - | - |
| Wan 2.1 Text to Video | Aug 19, 2026 | Wan 2.7 Text-to-Video | - |
| Wan 2.1 Image to Video | Aug 19, 2026 | Wan 2.7 Image-to-Video | - |
| Wan 2.2 Text to Video | Aug 19, 2026 | Wan 2.7 Text-to-Video | - |
| Wan 2.2 Image to Video | Aug 19, 2026 | Wan 2.7 Image-to-Video | - |
| Seedance 1.5 Pro Text To Video | Aug 19, 2026 | Wan 2.7 Text-to-Video | - |
| Seedance 1.5 Pro Image To Video | Aug 19, 2026 | Wan 2.7 Image-to-Video | - |
| MiniMax Speech-2.6-hd Text to Speech | Aug 19, 2026 | MiniMax Speech 2.8 HD Sync Text-to-Speech | - |
| MiniMax Speech-2.6-hd Async Long TTS | Aug 19, 2026 | MiniMax Speech 2.8 HD Async Text-to-Speech | - |
| MiniMax Speech-2.6-turbo Text to Speech | Aug 19, 2026 | MiniMax Speech 2.8 Turbo Sync Text-to-Speech | - |
| MiniMax Speech-2.6-turbo Async Long TTS | Aug 19, 2026 | MiniMax Speech 2.8 Turbo Async Text-to-Speech | - |

## Action required

If your application uses any affected model, please migrate to the replacement model where one is listed, or stop sending new requests before the retirement date.


# Nov 3, 2025 Product Updates
Source: https://docs.novita.ai/changelog/03-11-25



**Purpose**

Storage-related costs for Agent Sandbox have continued to increase, and the platform can no longer maintain free storage usage.To ensure sustainable operations and more efficient resource allocation, the Agent Sandbox Storage Billing feature has been launched.

**Billing Scope**

* Storage space used by Sandbox Templates
* Files and data retained when a Sandbox is paused

**Free Period**

A half-month free period is provided until November 17, 2025 (UTC).

During this period, users will not be charged and can check their usage at:

[https://novita.ai/sandbox-console/usage](https://novita.ai/sandbox-console/usage)

**Next Steps**

After the free period, storage billing will take effect. Users will be notified in advance via in-app messages or email.


# LLM Serverless Model Deprecation Notice
Source: https://docs.novita.ai/changelog/04-06-26



**Dear Developer,**

We are writing to inform you of upcoming changes to our **Serverless Endpoints**.

As part of our regular product lifecycle management to ensure high performance and resource optimization, we will be deprecating the models listed below on **June 19, 2026 (UTC)**.

**You have 15 days** from today to update your integration before these models are removed from the **Serverless Endpoints**.

***

## Impacted Models & Recommended Alternatives

Please refer to the table below to see if your application is using any of the impacted models and find their recommended alternatives:

| **Deprecated model** | **Recommended alternative** | **Notes** |
| :- | :- | :- |
| `meta-llama/llama-3-70b-instruct` | `meta-llama/llama-3.3-70b-instruct` | — |
| `meta-llama/llama-3-8b-instruct` | `meta-llama/llama-3.1-8b-instruct` | — |
| `deepseek/deepseek-prover-v2-671b` | `deepseek/deepseek-v4-pro` | — |
| `zai-org/glm-4.5` | `zai-org/glm-5.1` | — |

***

## What actions should you take?

To avoid service interruption, please choose one of the following options before the deadline:

### Option 1: Switch to the Alternative (Recommended)

Update your API code to point to the **Recommended Alternative** listed above.

The newer models offer improved reasoning capabilities, faster inference speeds, and better cost-efficiency.

### Option 2: Continue Using Legacy Models (via Dedicated Endpoints)

If your workflow strictly requires a specific version from the deprecated list (e.g., for reproducibility or specific fine-tuning), you can deploy it on your own private GPU resources using our **Dedicated Endpoints**.

* **Benefit:** This guarantees exclusive access, zero cold starts, and consistent performance.
* **Action:** [https://novita.ai/dedicated-endpoint](https://novita.ai/dedicated-endpoint)

***

## Timeline

* **Announcement Date:** June 4, 2026
* **End of Life (EOL):** June 19, 2026 (UTC)

**Note:** After the EOL date, API requests to the deprecated models via Serverless Endpoints will result in an error.

If you have any questions or need assistance with the migration, please reach out to us via our **Discord** community or submit a support ticket.

Best regards,

**The Novita AI Team**


# August 8, 2025 Notice
Source: https://docs.novita.ai/changelog/04-08-25--08-08-25



## **Model APIs Updates**

### **New Features**

* **New Model Available: Qwen-Image**

  The **Qwen-Image** is a 20B MMDIT model for next-gen text-to-image generation. Especially strong at creating stunning graphic posters with native text.

  Key Highlights:

  * SOTA text rendering — rivals GPT-4o in English, best-in-class for Chinese
  * In-pixel text generation — no overlays, fully integrated
  * Bilingual support, diverse fonts, complex layouts

  Also excels at general image generation — from photorealistic to anime, impressionist to minimalist. A true creative powerhouse.

  Qwen-Image provided text-to-image generation with strong text rendering.
* **New Model Available: Wan 2.2 Text to Video, Wan 2.2 Image to Video**

  Wan2.2 is the world's first open-source MoE-architecture video generation model with cinematic control. Its Mixture-of-Experts (MoE) architecture scales model capacity without increasing computational requirements.

  Key Highlights:

  * First-of-its-kind MoE Architecture: The first model to apply an MoE framework to video generation, drastically cutting down on resource consumption.
  * 50% More Efficient: Saves approximately 50% of computational resources compared to models of a similar parameter scale.
  * Enhanced Capabilities: Achieves significant improvements in generating complex motion, character interactions, and overall aesthetic quality.

  Unified Video Generation API provided a single endpoint for video generation.

***

## **GPUs Updates**

### **New Features**

* **Add region selection when creating an endpoint in Serverless**

  Novita now supports creating endpoints in selected regions.


# Sandbox SDK / CLI Upgrade Advance Notice
Source: https://docs.novita.ai/changelog/05-06-26



Novita Agent Sandbox will release a new SDK / CLI version and update the default cluster used by the official documentation starting **June 7, 2026 UTC**.

The new SDK / CLI is **not fully compatible** with older versions. If you are using Sandbox in production, please **avoid automatic upgrades** and complete compatibility checks before upgrading.

This notice is intended to help existing users prepare in advance. The new cluster information and full migration guide will be provided in the official release announcement.

### **If you do not plan to upgrade for now**

To avoid unexpected impact on your existing Sandbox workloads, we recommend that you:

1. Pin your current SDK / CLI version.
2. Avoid automatic dependency upgrades.
3. Explicitly configure the current Sandbox domain through the SDK / CLI configuration or by setting the `NOVITA_DOMAIN` environment variable. For domain configuration, use `sandbox.novita.ai`.
4. Review any production automation scripts that depend on SDK / CLI behavior, and avoid switching to the new version before completing compatibility validation.

### **If you plan to upgrade after the release**

Please wait for the official release announcement and migration guide. Before upgrading production workloads, we recommend testing in a staging environment and reviewing resource, API, CLI, network, and runtime compatibility.

If needed, our team can help review your current integration and provide upgrade guidance.


# July 11, 2025 Product Updates
Source: https://docs.novita.ai/changelog/07-07-25--11-07-25



## **Model APIs Updates**

### **New Features**

* **MiniMax Speech-02 and MiniMax Voice Cloning Now Available on Novita**

  Novita now supports **MiniMax Speech-02-HD** and **MiniMax Speech-02-Turbo** audio generation models, along with the **MiniMax Voice Cloning** model.

  **MiniMax Speech-02** features powerful generalization capabilities, effortlessly handling speech across 32 languages, various accents, and a wide range of emotional tones. Since its release in May, it has consistently ranked No.1 globally on the Artificial Analysis leaderboard.

  **MiniMax Voice Cloning** is designed for use cases such as IP voice replication and timbre cloning, offering fast and accurate voice reproduction. It supports both mono and stereo voice cloning, enabling quick replication of a target voice from an audio sample.

  Get started now: **MiniMax Speech-02-HD**; MiniMax Speech-02-Turbo; [MiniMax Voice Cloning](https://novita.ai/docs/api-reference/model-apis-minimax-voice-cloning).
* **GLM-4.1V-9B-Thinking Now Available on Novita**

  As a lightweight model with 9 billion parameters, **GLM-4.1V-9B-Thinking** delivers impressive performance—matching or even surpassing the 72B-parameter Qwen-2.5-VL-72B in 18 out of 28 authoritative benchmark evaluations.

  It excels in image-text understanding, scientific and mathematical reasoning, and video comprehension tasks. The model also supports ultra-high-resolution images up to 4K and accepts inputs with arbitrary aspect ratios.

  Get started now: [**GLM-4.1V-9B-Thinking**](https://novita.ai/models/llm/thudm-glm-4.1v-9b-thinking)**.**
* **Kimi-K2-Instruct Now Available on Novita**

  **Kimi K2** is a state-of-the-art Mixture-of-Experts (MoE) language model featuring 32 billion active parameters and 1 trillion total parameters.

  Trained with the Muon optimizer, Kimi K2 delivers exceptional performance in frontier knowledge tasks, reasoning, and coding. It is also carefully optimized for agent capabilities—designed specifically for tool use, advanced reasoning, and autonomous problem solving.

  Get started now: [**Kimi-K2-Instruct**](https://novita.ai/models/llm/thudm-glm-4.1v-9b-thinking)

***

## **GPUs Updates**

### **New Features**

* **Instance migration supports migrating system disks**

  Supports instance migration and also supports migrating the system disk. After migration, instance information (such as instance ID, egress IP, etc.) will not change. After migration, the mirroring will be stored in the Novita official mirroring repository. If the system disk is migrated, the system disk data will be packaged into a mirroring and migrated together to the new resource. The new mirroring contains the system disk data and is ready to use immediately.
* **GPU Instance metrics**

  Supports real-time monitoring of the following data:

  * CPU Utilization
  * Memory Utilization
  * GPU Utilization
  * Video Memory Utilization
  * System Disk Utilization


# Jul 7, 2026 Model API Updates
Source: https://docs.novita.ai/changelog/07-07-26



## Model Deprecation Notice

The following image and video models will be retired from the Novita platform on **Jul 22, 2026**.

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| Upscale | Jul 22, 2026 | None | - |
| Remove Background | Jul 22, 2026 | None | - |
| Replace Background | Jul 22, 2026 | None | - |
| Inpainting | Jul 22, 2026 | None | - |
| Remove Text | Jul 22, 2026 | None | - |
| Cleanup | Jul 22, 2026 | None | - |
| Image to Prompt | Jul 22, 2026 | None | - |
| Kling V2.5 Turbo | Jul 22, 2026 | Kling v3.0 | - |
| Kling-o1 | Jul 22, 2026 | Kling v3.0 | - |
| Kling V2.6 Pro | Jul 22, 2026 | Kling v3.0 | - |
| PixVerse V4.5 | Jul 22, 2026 | Kling v3.0 | - |
| VIDU Q2 | Jul 22, 2026 | Kling v3.0 | - |
| VIDU Q2 Turbo | Jul 22, 2026 | Kling v3.0 | - |
| VIDU Q2 Pro | Jul 22, 2026 | Kling v3.0 | - |
| Vidu Q3 Turbo | Jul 22, 2026 | Kling v3.0 | - |
| Vidu Q3 Pro | Jul 22, 2026 | Kling v3.0 | - |

## Action required

If your application uses any affected model, please migrate to the replacement model where one is listed, or stop sending new requests before the retirement date.


# Sandbox SDK / CLI Upgrade Notice
Source: https://docs.novita.ai/changelog/08-06-26



Novita Agent Sandbox has released a new SDK / CLI version and updated the default cluster used by the official documentation starting **June 8, 2026 UTC**.

Released versions:

* Python SDK: `2.0.1`
* TypeScript SDK: `2.0.0`
* CLI: `2.0.0`

The latest SDK / CLI will use the new Sandbox cluster by default when no domain is explicitly configured:

`https://us-phx-1.sandbox.novita.ai`

The new SDK / CLI is **not fully compatible** with older versions. If you are using Sandbox in production, please **avoid automatic upgrades** and complete compatibility checks before upgrading.

The legacy Sandbox endpoint remains available for existing users:

`https://sandbox.novita.ai`

## If you do not plan to upgrade for now

To avoid unexpected impact on your existing Sandbox workloads, we recommend that you:

1. Pin your current SDK / CLI version.
2. Avoid automatic dependency upgrades.
3. Explicitly configure the current Sandbox domain through the SDK / CLI configuration or by setting the `NOVITA_DOMAIN` environment variable. For SDK / CLI domain configuration, use `sandbox.novita.ai`.
4. Review any production automation scripts that depend on SDK / CLI behavior, and avoid switching to the new version before completing compatibility validation.

## If you plan to upgrade

Before upgrading production workloads, we recommend that you:

1. Test in a staging environment first.
2. Add the new Sandbox cluster endpoint to your firewall, proxy, CORS, or enterprise gateway allowlist if applicable: `https://us-phx-1.sandbox.novita.ai`.
3. For SDK / CLI domain configuration, use `us-phx-1.sandbox.novita.ai`.
4. Review resource, API, CLI, network, and runtime compatibility.
5. Check whether your integration depends on sandboxes, paused sandboxes, templates, snapshots, or commit artifacts from the legacy cluster. Resources are not shared across the legacy and new clusters, so some resources may need to be rebuilt or migrated.

If needed, our team can help review your current integration and provide upgrade guidance.


# September 12, 2025 Product Updates
Source: https://docs.novita.ai/changelog/08-09-25--12-09-25



## **Model APIs Updates**

### **New Features**

* **Wan2.2 I2V and Wan2.2 T2V supported LoRA**

  Wan2.2 I2V and Wan2.2 T2V supported LoRA.

  1080P is not supported. List of LoRAs to apply (max 3).


# Sandbox Upgrade Notice
Source: https://docs.novita.ai/changelog/08-09-26



## SDK & CLI

The Novita CLI has been upgraded to version `2.1.1`.

## What's New

* Create templates from OCI/Docker images, including private registry support.
* Add batch template builds with improved progress and error handling.
* Add snapshot, template inspection, tag management, sandbox copy, clone, reset, quota, and timeout commands.
* Add secrets management with value, environment, and stdin input options.
* Hide deprecated agent-runtime and hotplug commands.

## Fixes & Improvements

* Fixed bundled template assets not being included correctly in packaged CLI builds.

**Released on September 8, 2026 UTC.**


# August 15, 2025 Product Updates
Source: https://docs.novita.ai/changelog/11-08-25--15-08-25



## **Model APIs Updates**

### **New Features**

* **Batch Inference API for Large Language Models**

  Our Batch Inference API enables asynchronous processing of high-volume inference requests while maintaining full compatibility with the OpenAI API standard. This cost-efficient solution is ideal when immediate results aren't required, offering significantly higher rate limits than online calls while guaranteeing delivery within a reasonable 48-hour window.

  Perfect for:

  • Evaluation and data analysis at scale

  • Large dataset classification

  • Offline document summarization generation

  **Pricing:** Available at 50% off standard model inference API rates.
* **New Model Available: Vidu Q1**

  **Vidu Q1** is a multimodal video generation model globally launched by Shengshu Technology in April 2025. It features direct output capability at 1080P resolution and AI sound synchronization functionality. The model achieves state-of-the-art (SOTA) performance in video quality and semantic consistency metrics on VBench-1.0, as well as in commonsense reasoning and physical understanding dimensions on VBench-2.0. Additionally, it ranks first in both anime and realistic style categories on the SuperCLUE text-to-video leaderboard in China.

  Discover the power of Vidu Q1 on Novita by exploring its text-to-video, image-to-video, start-end-to-video, and reference-to-video generation capabilities—and bring your creative ideas to life.
* **New Model Available: Vidu 2.0**

  **Vidu 2.0** is a multimodal video generation model globally launched by Shengshu Technology in January 2025. It can generate a single 4-second video clip at 512P resolution within 10 seconds, delivering more consistent styles and themes, smoother transitions between the first and last frames, and producing four materials in one go—doubling efficiency.

  Discover the efficiency of Vidu 2.0 on this platform by exploring its image-to-video, start-end-to-video, and reference-to-video generation capabilities—and see how effortless video creation can be.


# Aug 11, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/11-08-26



The following large language models will be retired from the Novita platform on Aug 24, 2026. If your application uses any affected model, please migrate to the recommended replacement model before the retirement date.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `inclusionai/ling-2.6-flash` | Aug 24, 2026, 09:00 UTC | `inclusionai/ling-3.0-flash` | Released on Apr 23, 2026 |
| `inclusionai/ling-2.6-1t` | Aug 24, 2026, 09:00 UTC | `inclusionai/ling-3.0-flash` | Released on Apr 24, 2026 |
| `inclusionai/ring-2.6-1t` | Aug 24, 2026, 09:00 UTC | `inclusionai/ling-3.0-flash` | Released on May 8, 2026 |

## Action required

If your application uses any of the affected models, please migrate to the corresponding replacement model before the retirement time.


# May 16, 2025 Product Updates
Source: https://docs.novita.ai/changelog/12-05-25--16-05-25



## **Model APIs Updates**

### **Improvements**

* **Hunyuan Video Fast Now 67% Cheaper**

  We’ve significantly reduced the pricing for the Hunyuan Video Fast model — **now only one-third of the original price**. Enjoy high-quality video generation at a much lower cost.

***

## **GPUs Updates**

### **New Features**

* **Compute Capacity Expanded to Cover All Major Continents**

  Our global compute footprint continues to grow. Leveraging a distributed resource pool, we now offer GPU services across all major continents—empowering customers worldwide.

  **New Regions:**

  1. **Middle East:** United Arab Emirates (AS-AE-01)
  2. **Europe:** United Kingdom (EU-GB-01)
  3. **South Asia:** India (AS-IN-01)
  4. **South America:** Brazil (SA-BR-01)

### **Improvements**

* **Explore & Instances Interface Enhancements**

  We’ve refined both the Explore and Instances pages to surface more information and streamline interactions:

  1. Display architecture names for H100, H200, and A100 GPU card types
  2. On the Explore page, display the maximum GPUs supported per server as "\[card count] max" and streamline the per-instance GPU selector.
  3. On the Instances page, display the number of GPUs configured for each instance


# Aug 12, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/12-08-26



The following large language model will be retired from the Novita platform on Aug 13, 2026. If your application uses the affected model, please stop sending new requests before the retirement time.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `inclusionai/ling-3.0-tiny` | Aug 13, 2026, 15:00 UTC | None | Time-limited free trial model; released on Aug 5, 2026 |

## Action required

If your application uses the affected model, please stop sending new requests before the retirement time.


# May 14, 2025 Notice
Source: https://docs.novita.ai/changelog/14-05-25



### **Model Decommissioning Notice**

To continuously enhance platform service efficiency, focus on core product capability upgrades, and deliver more advanced, high-quality, and standardized services, Novita will decommission certain large language models (LLMs) on **May 28, 2025(UTC 00:00)** . **The deprecated models and their corresponding replacements** are listed below:

| **Deprecated Models** | **Deprecation Date** | **Replacement Models** |
| :- | :- | :- |
| jondurbin/airoboros-l2-70b | 2025-5-28 | sao10k/l3-70b-euryale-v2.1 |
| qwen/qwen3-14b-fp8 | 2025-5-28 | qwen/qwen3-32b-fp8 |
| qwen/qwen3-0.6b-fp8 | 2025-5-28 | qwen/qwen3-32b-fp8 |
| qwen/qwen3-1.7b-fp8 | 2025-5-28 | qwen/qwen3-32b-fp8 |
| meta-llama/llama-3.2-11b-vision-instruct | 2025-5-28 | qwen/qwen2.5-vl-72b-instruct |
| google/gemma-2-9b-it | 2025-5-28 | google/gemma-3-27b-it |
| meta-llama/llama-3.1-70b-instruct | 2025-5-28 | meta-llama/llama-3.3-70b-instruct |
| qwen/qwq-32b | 2025-5-28 | meta-llama/llama-3.3-70b-instruct |


# July 18, 2025 Product Updates
Source: https://docs.novita.ai/changelog/14-07-25--18-07-25



## **GPUs Updates**

### **New Features**

* **GPU Instance Spot Pricing**

  Advantages of Functional Features:

  1. Greatest Advantage: **Affordable!! The price is generally 50% off the on-demand price**, making it highly price-competitive in the market. For example, the current price of 4090 is \$0.35, while the spot 4090 price is as low as \$0.18, and larger usage volumes may result in greater discounts.
  2. Why is it so cheap? Spot instances are essentially designed to handle idle inventory, so **they may be reclaimed at any time due to resource shortages** (instance termination).
  3. How can we ensure the stability of our customers' business as much as possible while keeping costs low? Our spot billing instances will adopt the following strategies:
     * After creation, it enjoys **a 1-hour grace period**, during which the instance is guaranteed not to be reclaimed
     * Before resource recycling, a notice will be sent **1 hour in advance, and another notice will be sent 5 minutes before instance recycling**. When customers receive the notice, they can back up their data, while we give customers more time to back up their data!

***

## **Agent SandBox Updates**

### **New Features**

* **The product page for Agent Sandbox is now live**

  This page provides a concise overview of Agent Sandbox's core features, use cases, pricing model, and helpful documentation, enabling users to quickly get started and explore its powerful capabilities.

  **Agent Sandbox** is a high-performance cloud runtime purpose-built for **AI Agent workloads**, designed to support the full lifecycle of autonomous agents.

  Key features include:

  * **Millisecond-Level Startup**
  * **Secure Isolation**
  * **High Concurrency**

  Explore the [Agent Sandbox](https://novita.ai/sandbox) product page to discover more details!


# Aug 14, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/14-08-26

Novita will retire the kwaipilot/kat-coder-pro large language model on Aug 31, 2026. Stop sending new requests to the affected model before then.

The following large language model will be retired from the Novita platform on Aug 31, 2026. If your application uses the affected model, please stop sending new requests before the retirement time.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `kwaipilot/kat-coder-pro` | Aug 31, 2026, 15:59 UTC | None | |

## Action required

If your application uses the affected model, please stop sending new requests before the retirement time.


# September 19, 2025 Product Updates
Source: https://docs.novita.ai/changelog/15-09-25--19-09-25



## **Model APIs Updates**

### **New Features**

* **Seedream-4.0 Now Available**

  Seedream is a state-of-the-art (SOTA) multimodal image creation model based on a cutting-edge architecture. It breaks the creative boundaries of traditional text-to-image models by natively supporting text, single-image, and multi-image inputs. Users can freely combine text and images to enable diverse creative approaches within the same model, including multi-image fusion creation with subject consistency, image editing, and batch image generation. This makes image creation more free and controllable.

  Charges are based on the actual number of images generated; no fees will be incurred for failed generation.

***

## **Agent SandBox Updates**

### **New Features**

* **Metrics History Available for Paused Sandboxes**

  When a Sandbox is in Paused state, you can now access metrics data from up to 72 hours before the last pause time.  This enhancement allows developers to review and analyze recent activity even after a Sandbox has been paused.


# Oct 29, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/15-10-26

Novita retires the baidu/ernie-4.5-21B-a3b large language model at 08:00 UTC on Oct 29, 2026. Migrate to deepseek/deepseek-v4.1-flash before the cutoff.

As we release better and more powerful models, we routinely retire older ones. We plan to take `baidu/ernie-4.5-21B-a3b` offline at **08:00 UTC on October 29, 2026**. At that point, the model will officially be taken out of service, so please complete your migration to the recommended replacement model, `deepseek/deepseek-v4.1-flash`, before then.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `baidu/ernie-4.5-21B-a3b` | Oct 29, 2026, 08:00 UTC | `deepseek/deepseek-v4.1-flash` | |

## Action required

If your application uses the affected model, please migrate to `deepseek/deepseek-v4.1-flash` before the retirement time.


# June 20, 2025 Product Updates
Source: https://docs.novita.ai/changelog/16-06-25--20-06-25



## **Model APIs Updates**

### **New Features**

* **MiniMax-Hailuo-02 Now Available on Novita**

  Novita now supports MiniMax’s latest high-performance video generation model — **Hailuo 02**.

  Hailuo 02 delivers industry-leading instruction-following capabilities and excels at generating videos in complex physical scenarios, including high-intensity stunt simulations. It supports ultra-high-definition video generation, with:

  * Up to **6 seconds** at **768P or 1080P**
  * Up to **10 seconds** at **768P**

  Explore more creative possibilities with one of the most advanced video models available.

***

## **GPUs Updates**

### **New Features**

* **Instances are allowed to upgrade in any state**


# April 17, 2026
Source: https://docs.novita.ai/changelog/17-04-26



## **Dedicated Endpoint**

### New Features

* **Metrics Dashboard.** A new Metrics tab on the instance detail page lets you monitor TTFT, TPOT, token throughput, concurrent requests, and replica count with configurable time ranges. Key 24H metrics are also surfaced on the Overview tab.

### Improvements

* **Full-flow UX improvements.** Streamlined the experience across deployment, instance detail, and instance list pages — including a price calculator on the creation page, visual deployment pipeline, and a redesigned instance detail page with three tabs: Overview, Metrics, and Settings.

### Bug Fixes

* Fixed minor bugs and improved system stability.
* Fixed UI and interaction issues across instance pages.


# Jun 17, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/17-06-26



The following model will be retired from the Novita platform.

## Affected model

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `nex-agi/nex-n2-pro` | Jun 21, 2026, 17:00 Pacific Time (UTC-7) | None | Released on Jun 4, 2026 |

## Action required

If your application uses `nex-agi/nex-n2-pro`, please stop sending new requests to this model before the retirement time.


# August 22, 2025 Product Updates
Source: https://docs.novita.ai/changelog/18-08-25--22-08-25



## **Model APIs Updates**

### **New Features**

* **New Model Available: MiniMax Speech-2.5**

  Novita now features MiniMax's next-generation Text-to-Speech models: **Speech-2.5-HD** and **Speech-2.5-Rurbo**. Speech 2.5 represents a major breakthrough in voice generation, delivering significant advancements in multi-language expressiveness, voice cloning fidelity, and language coverage.

  Speech-2.5-HD (High-Fidelity):The latest HD model, offering exceptional prosody and remarkable voice cloning accuracy.Specifically designed for high-quality audio applications, providing studio-grade, high-fidelity sound.

  Speech-2.5-Turbo:The latest Turbo model, engineered for extreme responsiveness and ultra-low latency.Ideal for high-concurrency, real-time scenarios and includes support for 40 languages.

  Get started now: MiniMax Speech-2.5-hd-preview Text to Speech, MiniMax Speech-2.5-turbo-preview Text to Speech
* **New Model Available: Qwen-Image-Edit**

  Built upon the powerful 20B Qwen-Image model, Qwen-Image-Edit extends its unique text rendering capabilities into the image editing domain, enabling highly precise in-image text manipulation.

  Key Highlights:

  * **Comprehensive Editing:** Supports a full spectrum of edits, from precise local adjustments (adding, removing, or modifying elements while keeping the rest of the image intact) to high-level creative edits (like IP creation, object rotation, and style transfer).
  * **Precise Text Editing:** Directly add, delete, or modify both Chinese and English text within an image while perfectly preserving the original font, size, and style.
  * **SOTA Performance:** Achieves state-of-the-art (SOTA) results on multiple public benchmarks, establishing it as a powerful foundational model for image editing tasks.

  Qwen-Image-Edit extended the Qwen-Image model to precise image editing.
* **New Model Available: Pixverse V4.5 Text to Video, Pixverse V4.5 Image to Video**

  Pixverse 4.5 is the latest AI video model released by Pixverse AI. It introduces major upgrades in cinematic control, multi-image fusion, and complex motion handling.

  Key Highlights:

  * **20+ Cinematic Camera Controls:** Access over 20 camera controls like panning and zooming, all precisely guided by text prompts for professional-grade results.
  * **Multi-Image Reference & Fusion:** Seamlessly fuse elements from multiple reference images with the new Fusion mode for richer, stylistically consistent videos.
  * **Optimized Complex Motion:** Superior handling of complex actions like high-speed movement and group interactions, delivering smoother and more natural animations.

  Pixverse V4.5 supported text-to-video and image-to-video generation.

***

## **Platform Service Updates**

### **New Features**

* **Budget Management Now Live for Teams**

  Our new Budget Management feature is now available, giving you precise control over your team's API spending. This system is designed to help you efficiently manage member-level spending limits, with key capabilities including:

  * **Member-Level Budget Control:** Assign unique spending caps to individual team members.
  * **Smart Enforcement & Alerts:** Automatically prevent cost overruns and receive timely notifications.
  * **Flexible Budget Types:** Implement one-time or recurring (daily, weekly, or monthly) budgets.
  * **Real-Time Adjustments:** Instantly modify budget allocations as your needs change.

  To get started, visit [Billing > Budgets](https://novita.ai/billing/budgets) in your console. For more details, please see our [help documentation](https://novita.ai/docs/guides/budgets).

  **Updated:** August 20, 2025


# Sandbox Upgrade Notice
Source: https://docs.novita.ai/changelog/18-09-26



## What's New

### Hugging Face OpenEnv Integration

* Novita Sandbox is now integrated with [Hugging Face OpenEnv](/guides/sandbox-integrations-openenv) as a provider for running OpenEnv server environments in isolated cloud sandboxes.

**Released on September 18, 2026 UTC.**


# January 19, 2026 Product Updates
Source: https://docs.novita.ai/changelog/19-01-26



## **GPUs Updates**

### **New Features**

* **Added Automatic Instance Migration for newly GPU instance**

  The GPU **Automatic Instance Migration** entry has been relocated to the **Instance Management** page, and now supports configuring migration policies at the **instance** level. To ensure business continuity, **Automatic Instance Migration** will be enabled by default for **newly created resources starting at 2026**. You can manage migration settings via `More Operations`→  `Automatic Instance Migration` or configure migration attributes through the **OpenAPI**.


# May 23, 2025 Product Updates
Source: https://docs.novita.ai/changelog/19-05-25--23-05-25



## **Model APIs Updates**

### **New Features**

* **Kling V1.6: Now Supports 10-Second Video Generation**

  Kling V1.6 supported generating videos up to 10 seconds long, enabling more complete and coherent visual storytelling. It was ideal for short narratives, ad snippets, and dynamic showcases.

***

## **GPUs Updates**

### **New Features**

* **New GPU Type L40S Now Available**

  The **L40S** is a **high-performance data center GPU** from NVIDIA, built on the Ada Lovelace architecture. It is optimized for **multi-tasking scenarios** such as AI inference, large model training, graphics rendering, and virtualization. The L40S is well-suited for **inference and training of medium to large language models** like LLaMA, GPT, and BERT, as well as for **Stable Diffusion and image/video generation**. It supports concurrent operation of multiple hardware engines and offers excellent **multi-user VM deployment** and **GPU virtualization capabilities**. Get started now: [Novita AI](https://novita.ai/gpus-console).


# Novita GPUs Access Authentication Upgrade
Source: https://docs.novita.ai/changelog/19-10-26



To protect the platform and your workloads, Novita AI is upgrading access authentication for the GPUs product line (Serverless and GPU Instance).

Starting **October 26, 2026 (UTC)**, requests that reach these services through Novita's default domains must carry a valid API key, or originate from a whitelisted source IP. Unauthenticated public access will no longer be supported.

## What changes

* **Serverless**: Endpoint calls must include a valid API key. Unauthenticated requests return HTTP 401.
* **GPU Instance HTTP port mapping**: Requests to instance HTTP services through `*.gpu-instance.novita.ai` must include a valid API key.
* **GPU Instance TCP port mapping**: SSH, database, and other TCP services require a source IP whitelist. TCP port mapping is no longer open to the internet. If you use TCP port mapping, submit your whitelist before October 26, 2026.

## How to adapt

Add a standard Bearer token header to every request, using your Novita API key:

```text theme={"system"}
Authorization: Bearer {api-key}
```

The hosts stay the same as today. Callers only need the extra header.

| Access path | Host |
| - | - |
| Serverless Endpoint | `*.runsync.novita.dev` |
| GPU Instance HTTP port mapping | `*.gpu-instance.novita.ai` |
| GPU Instance TCP port mapping | `proxy.{region}.gpu-instance.novita.ai` — requires a source IP whitelist instead of an API key |

If you have shared your endpoint URL with third parties, or embedded it in frontend or mobile code, update those callers and redeploy.

* Manage API keys: [https://novita.ai/settings/key-management](https://novita.ai/settings/key-management)
* API key documentation: [/guides/llm-api-keys](/guides/llm-api-keys)

For TCP port mapping, submit your source IP whitelist through customer support.

## Transition period

**October 19 to October 26, 2026** is the transition period. Existing access methods keep working during this window. Please complete your changes as early as possible to avoid service interruption.


# Sep 29 and Oct 9, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/20-09-26

Novita will retire 34 models in two batches on Sep 29 and Oct 9, 2026, covering the DeepSeek, Qwen, GLM, MiniMax, and ERNIE series. Migrate to the recommended replacement model before each cutoff.

The following models will be retired from the Novita platform in two batches, on Sep 29 and Oct 9, 2026. If your application uses an affected model, migrate to the recommended replacement model before its retirement date. A `—` in the replacement column means the model has no direct replacement.

## Affected models

| Model ID | Retirement date | Replacement model |
| - | - | - |
| `deepseek/deepseek-v3.2` | Sep 29, 2026 | `deepseek/deepseek-v4.1-flash` |
| `deepseek/deepseek-r1` | Sep 29, 2026 | `deepseek/deepseek-v4.1-flash` |
| `deepseek/deepseek-v3.1` | Sep 29, 2026 | `deepseek/deepseek-v4.1-flash` |
| `deepseek/deepseek-v3.1-terminus` | Sep 29, 2026 | `deepseek/deepseek-v4.1-flash` |
| `deepseek/deepseek-v3.2-exp` | Sep 29, 2026 | `deepseek/deepseek-v4.1-flash` |
| `deepseek/deepseek-r1-distill-llama-70b` | Sep 29, 2026 | — |
| `deepseek/deepseek-v3/community` | Sep 29, 2026 | `deepseek/deepseek-v4.1-flash` |
| `qwen/qwen3.6-plus` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-vl-235b-a22b-instruct` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `openai/gpt-oss-20b` | Oct 9, 2026 | — |
| `deepseek/deepseek-v4-flash-vision-exp` | Oct 9, 2026 | `zai-org/glm-5.3-flash` |
| `qwen/qwen3-coder-30b-a3b-instruct` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-coder-480b-a35b-instruct` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-coder-next` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen-2.5-72b-instruct` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-next-80b-a3b-instruct` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3.5-plus` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `sao10k/l31-70b-euryale-v2.2` | Oct 9, 2026 | — |
| `qwen/qwen3-vl-235b-a22b-thinking` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-max` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `mistralai/mistral-nemo` | Oct 9, 2026 | — |
| `sao10k/l3-8b-lunaris` | Oct 9, 2026 | — |
| `zai-org/glm-5v-turbo` | Oct 9, 2026 | `zai-org/glm-5.3-flash` |
| `qwen/qwen3-235b-a22b-thinking-2507` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-235b-a22b-instruct-2507` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `baidu/ernie-4.5-vl-424b-a47b` | Oct 9, 2026 | — |
| `minimax/minimax-m2` | Oct 9, 2026 | `minimax/minimax-m3` |
| `qwen/qwen3-omni-30b-a3b-instruct` | Oct 9, 2026 | `qwen/qwen3.8-flash` |
| `minimax/minimax-m2.1` | Oct 9, 2026 | `minimax/minimax-m3` |
| `zai-org/glm-5-turbo` | Oct 9, 2026 | `zai-org/glm-5.3-flash` |
| `minimax/minimax-m2.5-highspeed` | Oct 9, 2026 | `minimax/minimax-m3` |
| `qwen/qwen3-235b-a22b-fp8` | Oct 9, 2026 | `qwen/qwen3.8-max` |
| `qwen/qwen3-embedding-0.6b` | Oct 9, 2026 | — |
| `thudm/glm-4-32b-0414` | Oct 9, 2026 | `zai-org/glm-5.3-flash` |

## Action required

If your application uses an affected model, migrate to the recommended replacement model before its retirement date. For models with no direct replacement, remove the affected calls from your application before that date.


# July 25, 2025 Product Updates
Source: https://docs.novita.ai/changelog/21-07-25--25-07-25



## **Model APIs Updates**

### **New Features**

* **New Model Available: Qwen3 Coder 480B A35B Instruct**

  Novita now supports **Qwen3 Coder 480B A35B Instruct**, the cutting-edge open-source coding model from the Qwen team. This model delivers performance on par with Claude Sonnet for agentic coding, browser automation, and other core development tasks, enabling highly efficient code generation and the handling of complex problems.

  Get started now: [**Qwen3 Coder 480B A35B Instruct**](https://novita.ai/models/llm/qwen-qwen3-coder-480b-a35b-instruct)
* **New Model Available: Qwen3 235B A22b Thinking 2507**

  Novita now supports Qwen3 235B A22B Instruct 2507, a powerful Mixture-of-Experts (MoE) model designed for ultimate performance and efficiency. By activating just 22B of its 235B parameters during inference, it dynamically handles diverse tasks with incredible speed.

  With its native 262k-token context window, the model excels at demanding use cases, including analyzing long documents, comprehending full code repositories, and performing complex multi-paragraph reasoning.

  Get started now: [**Qwen3 235B A22b Thinking 2507**](https://novita.ai/models/llm/qwen-qwen3-235b-a22b-thinking-2507)

***

## **Platform Service Updates**

### **Improvements**

* **Pricing Page Optimization**

  Non-MoE Models are now systematically sorted by vendor/manufacturer for clear differentiation

  Dedicated MoE (Mixture of Experts) category added and placed at the end of the page for quick access


# LLM Serverless Model Deprecation Notice
Source: https://docs.novita.ai/changelog/22-05-26



**Dear Developer,**

We are writing to inform you of upcoming changes to our **Serverless Endpoints**.

As part of our regular product lifecycle management to ensure high performance and resource optimization, we are deprecating two batches of models: an **immediate batch effective today (May 22, 2026)** and a **scheduled batch on June 5, 2026 (UTC)**.

For the scheduled batch, **you have 14 days** from today to update your integration before these models are removed from the **Serverless Endpoints**.

***

## Impacted Models & Recommended Alternatives

Please refer to the tables below to see if your application is using any of the impacted models and find their recommended alternatives.

### Effective Immediately (May 22, 2026)

| **Deprecated model** | **Recommended alternatives** | **Notes** |
| :- | :- | :- |
| `baidu/ernie-4.5-21B-a3b` | `qwen/qwen3.5-35b-a3b`, `qwen/qwen3.6-35b-a3b` | — |
| `baidu/ernie-4.5-vl-28b-a3b` | `qwen/qwen3.5-35b-a3b`, `qwen/qwen3.6-35b-a3b` | — |
| `baidu/ernie-4.5-300b-a47b-paddle` | `deepseek/deepseek-v3.2` | — |
| `baidu/ernie-4.5-21B-a3b-thinking` | `qwen/qwen3.5-35b-a3b`, `qwen/qwen3.6-35b-a3b` | — |
| `qwen/qwen2.5-7b-instruct` | `qwen/qwen3.5-27b`, `qwen/qwen3.6-27b` | — |
| `qwen/qwen2.5-vl-72b-instruct` | `qwen/qwen3.5-27b`, `qwen/qwen3.6-27b` | — |
| `qwen/qwen2.5-32b-instruct` | `qwen/qwen3.5-27b`, `qwen/qwen3.6-27b` | — |
| `qwen/qwen3-4b` | `qwen/qwen3.5-27b`, `qwen/qwen3.6-27b` | — |

### Scheduled for June 5, 2026

| **Deprecated model** | **Recommended alternatives** | **Notes** |
| :- | :- | :- |
| `qwen/qwen3-30b-a3b` | `qwen/qwen3.5-35b-a3b`, `qwen/qwen3.6-35b-a3b` | Also available on Dedicated Endpoints |
| `qwen/qwen3-32b` | `qwen/qwen3.5-27b`, `qwen/qwen3.6-27b` | Also available on Dedicated Endpoints |
| `nousresearch/hermes-2-pro-llama-3-8b` | — | Available on Dedicated Endpoints only |
| `meta-llama/llama-3.2-1b-instruct` | — | Available on Dedicated Endpoints only |
| `sao10k/l3-70b-euryale-v2.1` | — | Available on Dedicated Endpoints only |
| `deepseek/deepseek-ocr` | `deepseek/deepseek-ocr-2` | — |

***

## What actions should you take?

To avoid service interruption, please choose one of the following options before the deadline:

### Option 1: Switch to the Recommended Alternative

For deprecated models that have a Serverless alternative listed above, update your API code to point to the recommended alternative.

These newer models offer improved reasoning capabilities, faster inference speeds, and better cost-efficiency.

### Option 2: Continue Using Specific Models via Dedicated Endpoints

Several models in the **June 5, 2026** batch remain available on **Dedicated Endpoints**:

* **Dedicated Endpoints only** (no Serverless alternative): `nousresearch/hermes-2-pro-llama-3-8b`, `meta-llama/llama-3.2-1b-instruct`, `sao10k/l3-70b-euryale-v2.1`
* **Also available on Dedicated Endpoints** (alongside Serverless alternatives): `qwen/qwen3-30b-a3b`, `qwen/qwen3-32b`

If your workflow requires any of these specific versions (e.g., for reproducibility or specific fine-tuning), you can deploy them on your own private GPU resources using our **Dedicated Endpoints**.

* **Benefit:** This guarantees exclusive access, zero cold starts, and consistent performance.
* **Action:** [https://novita.ai/dedicated-endpoint](https://novita.ai/dedicated-endpoint)

***

## Timeline

* **Announcement Date:** May 22, 2026
* **Immediate End of Life (EOL):** May 22, 2026 (UTC) — effective upon announcement
* **Scheduled End of Life (EOL):** June 5, 2026 (UTC)

**Note:** After each EOL date, API requests to the corresponding deprecated models via Serverless Endpoints will result in an error.

If you have any questions or need assistance with the migration, please reach out to us via our **Discord** community or submit a support ticket.

Best regards,

**The Novita AI Team**


# Jun 22, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/22-06-26



The following multimodal models will be retired from the Novita platform on July 7, 2026. If your application uses any affected model, please migrate to the recommended replacement model before the retirement date.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `GLM Text to Speech` | Jul 7, 2026 | MiniMax Speech 2.8 Turbo Text-to-Speech | |
| `GLM Audio to Text` | Jul 7, 2026 | None | |
| `GLM Voice Clone` | Jul 7, 2026 | MiniMax Quick Voice Cloning | |
| `GLM Image Generation` | Jul 7, 2026 | Seedream 5.0 lite | |
| `Hunyuan Image 3` | Jul 7, 2026 | Seedream 5.0 lite | |

## Action required

Please validate your replacement model integration and stop sending new requests to the retired models before Jul 7, 2026.


# September 28, 2025 Product Updates
Source: https://docs.novita.ai/changelog/22-09-25--28-09-25



## **Model APIs Updates**

### **New Features**

* **qwen/qwen3-vl-235b-a22b-thinking Model Release**

  Qwen3-VL—the most powerful vision-language model in the Qwen series to date. Novita offers world-first support!
* **qwen/qwen3-vl-235b-a22b-instruct Model Release**

  Qwen3-VL—the most powerful vision-language model in the Qwen series to date. Novita offers world-first support!
* **minimax-hailuo-02 First-last Frame**

  First-Last Frame is a video generation technology introduced by Hailuo AI. By specifying the start and end images (the first and last frames), it automatically generates dynamic transition videos in between. The technology supports complex instruction execution and delivers cinematic-quality effects, making it suitable for applications such as e-commerce advertising, one-click outfit changes, and more.
* **Seedance V1 Lite/Pro**

  Seedance 1.0 is the latest video generation foundational model launched by ByteDance's Doubao AI team, featuring both lite and pro versions. Its capabilities and pricing are fully aligned with the official website.

  Seedance V1 Lite Text to Video

  Seedance V1 Lite Image to Video

  **Seedance V1 Pro Text to Video**

  **Seedance V1 Pro Image to Video**

***

## **GPUs Updates**

### **New Features**

* **Subscribtion Instance Auto-Renewal Feature**

  Supports setting up automatic renewal for Subscribtion Instances.
* **Creating instances and templates support the function of specifying the minimum CUDA version**

  Creating instances and templates support the function of specifying the minimum CUDA version.


# Jun 23, 2026 Model API Updates
Source: https://docs.novita.ai/changelog/23-06-26



## New Features

### Model API Cost Observability Center

We are introducing a **Cost Observability Center for Novita Model API**, transforming billing from a "black-box invoice" into an attributable and controllable cost visibility system, directly addressing one of the most critical enterprise pain points — not knowing where the money is spent.

**Key highlights:**

* **Three-level Cost Drill-down**\
  We now support hierarchical cost exploration from organization → team → member → API key. Strict permission isolation ensures secure and non-overlapping data access across roles.

* **Budget & Quota Management**\
  We introduce a Budget Quota card across User and API Key levels, showing real-time usage progress. This enables precise cost control and predictable budget management across teams and projects.

* **Cost Trend Visualization**\
  A new cost trend dashboard provides structured visibility into spending patterns over time, with support for daily, monthly, and custom time ranges. It offers clear insights into usage distribution across different models.

* **Cost Ranking & Hotspot Detection**\
  Two new ranking tables — **Top Models by Cost** and **Top API Keys by Cost** — help instantly identify cost drivers and usage hotspots.

***

### Model API Request Logging

We now support **detailed request-level logging** for Model API calls, significantly improving debugging efficiency and operational transparency for production AI systems.

**Key highlights:**

* Full request and response traceability
* Searchable logs by request ID, model, or timestamp
* Error diagnostics and debugging support
* Latency tracking per request
* Secure and structured log storage for enterprise use

***

## Improvements

### Enhanced Model Marketplace Experience

We've significantly improved the Model Marketplace to make model discovery and selection easier and more intuitive. These enhancements help developers quickly find the right model for their use case and reduce evaluation time.

**Key improvements:**

* Improved filtering and categorization
* Enhanced model cards with richer specifications and benchmarks
* Faster browsing performance and smoother navigation
* Better model discoverability for new and trending options

***

## Model Deprecation Notice

The following image, audio, and video models will be retired from the Novita platform on Jul 15, 2026.

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `Seedream 3.0` | Jul 15, 2026 | `Seedream 5.0 lite` | — |
| `MiniMax Speech-02-hd` | Jul 15, 2026 | `MiniMax Speech 2.8 HD` | — |
| `MiniMax Speech-02-turbo` | Jul 15, 2026 | `MiniMax Speech 2.8 Turbo` | — |
| `MiniMax Speech-2.5-hd` | Jul 15, 2026 | `MiniMax Speech 2.8 HD` | — |
| `MiniMax Speech-2.5-turbo` | Jul 15, 2026 | `MiniMax Speech 2.8 Turbo` | — |
| `Seedance V1 Lite` | Jul 15, 2026 | `Seedance 1.5 Pro` | — |
| `Seedance V1 Pro` | Jul 15, 2026 | `Seedance 1.5 Pro` | — |
| `Kling V1.6` | Jul 15, 2026 | `Kling v3.0` | — |
| `Kling V2.1` | Jul 15, 2026 | `Kling v3.0` | — |
| `Kling V2.1 Master` | Jul 15, 2026 | `Kling v3.0` | — |
| `Minimax Video-01` | Jul 15, 2026 | `Minimax Hailuo 2.3` | — |
| `Minimax Hailuo-02` | Jul 15, 2026 | `Minimax Hailuo 2.3` | — |
| `Vidu 2.0` | Jul 15, 2026 | `Vidu Q3` | — |
| `Vidu Q1` | Jul 15, 2026 | `Vidu Q3` | — |

If your application uses any of these models, please migrate to the replacement model before the retirement time. After retirement, API requests to the retired models will return errors.


# Sandbox Agent Skills Update
Source: https://docs.novita.ai/changelog/23-09-26



## Agent Skills

Novita Sandbox now provides an agent skill, `novita-sandbox-skills`, in the [novita-skills](https://github.com/novitalabs/novita-skills) repository.

## What's New

* Release the `novita-sandbox-skills` agent skill.
* Install it with `npx skills add novitalabs/novita-skills --skill novita-sandbox-skills`.
* See [Install Agent Skills](/guides/sandbox-installation#install-agent-skills) for installation instructions.

**Released on September 23, 2026 UTC.**


# May 25, 2026
Source: https://docs.novita.ai/changelog/25-05-26



## **Dedicated Endpoint**

### New Features

* **On-Demand Deployment.** Introduced a "Deploy on Demand" button on the Model Library, enabling one-click conversion from Shared API to Dedicated Endpoint with auto-filled configurations.

* **RTX 5090 Support.** Added support for RTX 5090 (32GB, Blackwell architecture), providing a cost-effective GPU option for small-to-medium models (≤32B parameters).

* **LoRA Management.** Introduced LoRA adapter management for Dedicated Endpoints. You can now add, remove, and update LoRA adapters within your endpoint configuration, with alias support for easier organization and referencing across multiple LoRA setups.

### Improvements

* **Deployment Reliability.** Improved deployment success rate to ≥95%, ensuring more consistent and stable endpoint provisioning.

* **Transparent Context Length.** Model maximum context length is now clearly displayed, with GPU recommendations based on full context capacity rather than minimum requirements.


# Jun 25, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/25-06-26



The following models will be retired from the Novita platform.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `deepseek/deepseek-r1-distill-qwen-14b` | Jul 1, 2026, 00:00 UTC | `deepseek/deepseek-v4-flash` | |
| `deepseek/deepseek-r1-distill-qwen-32b` | Jul 1, 2026, 00:00 UTC | `deepseek/deepseek-v4-flash` | |
| `qwen/qwen3-14b` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |
| `qwen/qwen3-30b-a3b` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-35b-a3b` | |
| `qwen/qwen3-30b-a3b-instruct-2507` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-35b-a3b` | |
| `qwen/qwen3-30b-a3b-thinking-2507` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-35b-a3b` | |
| `qwen/qwen3-32b` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |
| `qwen/qwen3-8b` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |
| `qwen/qwen3-next-80b-a3b-thinking` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-35b-a3b` | |
| `qwen/qwen3-vl-30b-a3b-thinking` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-35b-a3b` | |
| `qwen/qwen3-vl-32b-instruct` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |
| `qwen/qwen3-vl-32b-thinking` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |
| `qwen/qwen3-vl-8b-instruct` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |
| `qwen/qwen3-vl-8b-thinking` | Jul 1, 2026, 00:00 UTC | `qwen/qwen3.6-27b` | |

## Action required

If your application uses any of the affected models, please migrate to the corresponding replacement model before the retirement time.


# August 29, 2025 Product Updates
Source: https://docs.novita.ai/changelog/25-08-25--29-08-25



## **Model APIs Updates**

### **New Features**

* **New Model Available**

  **Kling V2.1**:

  Kling V2.1 is the latest evolution of Kuaishou’s AI-powered video‑generation model, designed to seamlessly create short, cinematic‑quality clips from a single image or a text prompt. It represents a major leap forward from Kling 2.0, emphasizing motion fidelity, visual coherence, and prompt accuracy.

  **Kling 2.1 Master**:

  The premium endpoint for Kling 2.1, designed for top-tier image-to-video generation with unparalleled motion fluidity, cinematic visuals, exceptional prompt precision, and 1080p video quality.

  **Seedance V1 Lite**:

  Seedance V1 Lite is an AI video model designed for coherent multi-shot video generation, offering smooth motion and precise adherence to detailed prompts. It supports resolutions of 480p, 720p, and 1080p.

  **Seedance V1 Pro**:

  Seedance V1 Pro is an AI video model designed for coherent multi-shot video generation, offering smooth motion and precise adherence to detailed prompts. It supports resolutions of 480p, 720p, and 1080p.

***

## **GPUs Updates**

### **New Features**

* **Self-migration function of faulty devices**

  When a user enables automatic migration, in the event of device failure, the system will automatically migrate the instance to a healthy node within the same cluster, and the instance ID, port mapping address, etc. will remain unchanged;

  It should be noted that:

  1. During the migration process, the user's instance will still experience**short-term interruption**, and the interruption time depends on the size of the system disk;
  2. Therefore, this feature is very suitable for **stateless tasks**. For stateful tasks, it is recommended not to enable this feature for the time being. After receiving a fault notification, users can choose to perform migration during off-peak business hours. In the future, we will also upgrade the feature for this scenario;


# LLM Serverless Model Deprecation Notice
Source: https://docs.novita.ai/changelog/25-12-25



**Dear Developer,**

We are writing to inform you of upcoming changes to our **Serverless Endpoints**.

As part of our regular product lifecycle management to ensure high performance and resource optimization, we will be deprecating a list of specific models on **January 8, 2026 (UTC)**.

**You have 14 days** from today to update your integration before these models are removed from the **Serverless Endpoints**.

***

## Impacted Models & Recommended Alternatives

Please refer to the table below to see if your application is using any of the impacted models and find their recommended alternatives:

| **Deprecated model** | **Recommended alternatives** |
| :- | :- |
| baichuan/baichuan-m2-32b | qwen/qwen3-32b-fp8 |
| paddlepaddle/paddleocr-vl | qwen/qwen3-vl-8b-instruct |
| qwen/qwen3-reranker-8b | baai/bge-reranker-v2-m3 |
| deepseek/deepseek-ocr | qwen/qwen3-vl-235b-a22b-instruct |
| thudm/glm-4.1v-9b-thinking | zai-org/glm-4.6v |
| deepseek/deepseek-r1-distill-qwen-14b | deepseek/deepseek-r1-0528 |
| meta-llama/llama-3.2-3b-instruct | meta-llama/llama-3.3-70b-instruct |
| google/gemma-3-12b-it | google/gemma-3-27b-it |
| gryphe/mythomax-l2-13b | deepseek/deepseek-v3-0324 |
| deepseek/deepseek-r1-0528-qwen3-8b | deepseek/deepseek-r1-0528 |
| deepseek/deepseek-r1-distill-qwen-32b | deepseek/deepseek-r1-0528 |
| qwen/qwen3-8b-fp8 | qwen/qwen3-next-80b-a3b-instruct |

***

## What actions should you take?

To avoid service interruption, please choose one of the following options before the deadline:

### Option 1: Switch to the Alternative (Recommended)

Update your API code to point to the **Recommended Alternative** listed above.

These newer models offer improved reasoning capabilities, faster inference speeds, and better cost-efficiency.

### Option 2: Continue Using Legacy Models (via Dedicated Endpoints)

If your workflow strictly requires a specific version from the deprecated list (e.g., for reproducibility or specific fine-tuning), you can deploy it on your own private GPU resources using our **Dedicated Endpoints**.

* **Benefit:** This guarantees exclusive access, zero cold starts, and consistent performance.
* **Action:** [https://novita.ai/dedicated-endpoint](https://novita.ai/dedicated-endpoint)

***

## Timeline

* **Announcement Date:** December 25, 2025
* **End of Life (EOL):** January 8, 2026 (UTC)

**Note:** After the EOL date, API requests to any models in the **Deprecated** list via Serverless Endpoints will result in an error.

If you have any questions or need assistance with the migration, please reach out to us via our **Discord** community or submit a support ticket.

Best regards,

**The Novita AI Team**


# May 30, 2025 Product Updates
Source: https://docs.novita.ai/changelog/26-05-25--30-05-25



## **Model APIs Updates**

### **New Features**

* **New Model Available: DeepSeek-R1-0528**

  Novita now supports the latest high-performance DeepSeek model — [**DeepSeek-R1-0528**](https://novita.ai/models/llm/deepseek-deepseek-r1-0528).

  Released by the DeepSeek team as an open-source model, DeepSeek-R1-0528 features impressive reasoning capabilities, particularly achieving performance comparable to OpenAI's o1 model in mathematics, coding, and reasoning tasks.

### **Discontinued Features**

* **Certain LLMs Officially Decommissioned**

  To continuously improve model performance and enhance user experience, several large language models (LLMs) have now been officially decommissioned. Below is the list of retired models along with their recommended replacements:

  | **Deprecated Models** | **Deprecation Date** | **Replacement Models** |
  | :- | :- | :- |
  | jondurbin/airoboros-l2-70b | 2025-05-28 | sao10k/l3-70b-euryale-v2.1 |
  | qwen/qwen3-14b-fp8 | 2025-05-28 | qwen/qwen3-32b-fp8 |
  | qwen/qwen3-0.6b-fp8 | 2025-05-28 | qwen/qwen3-32b-fp8 |
  | qwen/qwen3-1.7b-fp8 | 2025-05-28 | qwen/qwen3-32b-fp8 |
  | meta-llama/llama-3.2-11b-vision-instruct | 2025-05-28 | qwen/qwen2.5-vl-72b-instruct |
  | google/gemma-2-9b-it | 2025-05-28 | google/gemma-3-27b-it |
  | meta-llama/llama-3.1-70b-instruct | 2025-05-28 | meta-llama/llama-3.3-70b-instruct |
  | qwen/qwq-32b | 2025-05-28 | meta-llama/llama-3.3-70b-instruct |

  If you encounter any issues during this transition, please don’t hesitate to contact our technical support team.

***

## **GPUs Updates**

### **New Features**

* **New Bare Metal Server Reservation Page Launched**

  Novita has launched a [**bare metal server reservation interface**](https://novita.ai/gpu-baremetal) on the official website. Users can now browse available GPU models that support bare metal services, along with detailed configuration information.

  **Bare metal** refers to a computing service that provides **direct access to physical servers without a virtualization layer**, offering superior performance, greater control, and lower latency. It is ideal for scenarios with high demands on resource isolation and computing power, such as deep learning, AI model training, and large-scale inference.

  If you require bare metal resources, you can **select your preferred GPU model and complete the reservation online** to gain exclusive access to dedicated physical servers.


# LLM API Billing Rules Explained
Source: https://docs.novita.ai/changelog/27-03-26



Dear novita AI users,

This notice clarifies our LLM API billing policy, with a focus on how disconnected requests (HTTP 499) are handled.

## Pricing

novita AI LLM API is billed per token:

* **Input Tokens** — tokens consumed by the prompt
* **Output Tokens** — tokens generated by the model

Total Cost = Input Tokens × Input Rate + Output Tokens × Output Rate

Model-specific rates are available on the [Pricing Page](https://novita.ai/pricing).

## Billing Conditions

Charges apply when the model has begun inference. Requests that do not reach the model are not charged.

| Scenario | Charged | Reason |
| :- | :- | :- |
| Successful request (200) | Yes | Model completed inference |
| Client disconnected (499) | Yes | Model had begun inference |
| Invalid request / auth failure / rate limit (400/401/403/429) | No | Request rejected before reaching the model |
| Error due to platform issues (500/503/504) | No | Infrastructure error; absorbed by the platform |

Full status code reference: [Error Codes](/guides/error).

## 499 (Client Disconnected)

When a request reaches the model, inference starts immediately. If the client disconnects mid-request, the compute resources already consumed are still billable.

| Request Mode | Billing |
| :- | :- |
| Non-Streaming | Charged for actual token usage regardless of disconnect timing |
| Streaming | Charged for actual token usage regardless of disconnect timing |

This policy is consistent with standard industry practice across major LLM providers.

## Recommendations

1. **Set `max_tokens`** to control output length at the request level
2. **Configure client timeout ≥ 60s** to prevent unintended disconnections
3. **Select appropriate models** based on task requirements to optimize cost

## FAQ

<AccordionGroup>
  <Accordion title="Why am I charged if I disconnected before receiving a response?">
    Inference begins upon request arrival. Disconnecting does not stop computation already in progress.
  </Accordion>

  <Accordion title="How can I avoid 499 charges?">
    Use the `max_tokens` parameter to cap output length before sending the request.
  </Accordion>

  <Accordion title="Where do 499 charges appear in my account?">
    In the usage dashboard, listed alongside standard requests with actual token consumption.
  </Accordion>
</AccordionGroup>

For further questions, please contact our support team.

**The novita AI Team**


# Nov 11, 2025 Product Updates
Source: https://docs.novita.ai/changelog/27-10-25--07-11-25



## **Agent Sandbox Updates**

### **New Features**

* **Agent Sandbox Storage Billing**

  Agent Sandbox storage billing is now enabled to address increasing storage costs and support sustainable operations.

  **Billing Scope:**

  * Storage used by Sandbox Templates

  * Data retained when a Sandbox is paused

  **Free Period:**\
  Free until **November 17, 2025 (UTC)**.\
  Users can view usage at: [https://novita.ai/sandbox-console/usage](https://novita.ai/sandbox-console/usage)

  **Next Steps:**\
  Billing will begin after the free period. Users will be notified in advance via in-app message or email.
* **Sandbox Auto Pause and Resume**

  Sandboxes now automatically enter a paused state during inactivity and seamlessly resume when accessed again.

  This helps reduce unnecessary resource usage while ensuring fast recovery when workloads restart.

  Guide → [*Sandbox Auto Pause and Resume*](/guides/sandbox-legacy-auto-persistence)
* **Sandbox Auto Pause and Resume**

  Sandboxes now automatically enter a paused state during inactivity and seamlessly resume when accessed again.

  This helps reduce unnecessary resource usage while ensuring fast recovery when workloads restart.

  Guide → [*Sandbox Auto Pause and Resume*](/guides/sandbox-legacy-auto-persistence)
* **Sandbox Clone**

  You can now duplicate an existing Sandbox—including its code, environment state, and configurations—with a single click.

  This is ideal for parallel experimentation, debugging, or creating environment snapshots.

  Guide → [*Sandbox Clone*](/guides/sandbox-legacy-clone)
* **Sandbox Idle Timeout**

  Sandboxes will automatically shut down after a configurable idle period.\
  This helps avoid unexpected costs and ensures better resource hygiene for long-running projects.

  Guide → [*Sandbox Idle Timeout*](https://novita.ai/docs/guides/sandbox-idle-timeout)


# August 1, 2025 Notice
Source: https://docs.novita.ai/changelog/28-07-25--01-08-25



## **Model APIs Updates**

### **New Features**

* **New Model Available: Seedream 3.0 Text to Image**

  The **Seedream 3.0 Text to Image** model is a new generation text-to-image model built on ByteDance’s large visual model. It offers accurate understanding of textual input and the ability to generate images with high text-image alignment and aesthetic quality. The model supports direct output at resolutions below 2048, with significantly improved performance across various scenarios.

  Get started now: **Seedream 3.0 Text to Image**


# Aug 28, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/28-08-26

Novita retires the inclusionai/ling-3.0-flash-fast large language model on Aug 28, 2026. Migrate to inclusionai/ling-3.0-flash before the cutoff.

The following large language model will be retired from the Novita platform on Aug 28, 2026. If your application uses the affected model, please migrate to the recommended replacement model before the retirement time. The replacement model `inclusionai/ling-3.0-flash` has been available since Aug 18, 2026.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `inclusionai/ling-3.0-flash-fast` | Aug 28, 2026, 03:30 UTC | `inclusionai/ling-3.0-flash` | |

## Action required

If your application uses the affected model, please migrate to the corresponding replacement model before the retirement time.


# Oct 9, 2026 Model Deprecation Notice
Source: https://docs.novita.ai/changelog/28-09-26

Novita retires the deepseek/deepseek-v4-flash large language model at 16:00 UTC on Oct 9, 2026. Migrate to deepseek/deepseek-v4.1-flash before the cutoff.

As we release better and more powerful models, we routinely retire older ones. We plan to take `deepseek/deepseek-v4-flash` offline at **16:00 UTC on October 9, 2026**. At that point, the model will officially be taken out of service, so please complete your migration to the recommended replacement model, `deepseek/deepseek-v4.1-flash`, before then.

## Affected models

| Model ID | Retirement time | Replacement model | Notes |
| - | - | - | - |
| `deepseek/deepseek-v4-flash` | Oct 9, 2026, 16:00 UTC | `deepseek/deepseek-v4.1-flash` | |

## Action required

If your application uses the affected model, please migrate to `deepseek/deepseek-v4.1-flash` before the retirement time.


# LLM Serverless Model Deprecation Notice
Source: https://docs.novita.ai/changelog/29-05-26



**Dear Developer,**

We are writing to inform you of upcoming changes to our **Serverless Endpoints**.

As part of our regular product lifecycle management to ensure high performance and resource optimization, we will be deprecating the model listed below on **June 5, 2026 (UTC)**.

**You have 7 days** from today to update your integration before this model is removed from the **Serverless Endpoints**.

***

## Impacted Models & Recommended Alternatives

Please refer to the table below to see if your application is using the impacted model and find its recommended alternative:

| **Deprecated model** | **Recommended alternatives** | **Notes** |
| :- | :- | :- |
| `xiaomimimo/mimo-v2-flash` | `xiaomimimo/mimo-v2.5` | — |

***

## What actions should you take?

To avoid service interruption, please choose one of the following options before the deadline:

### Option 1: Switch to the Alternative (Recommended)

Update your API code to point to the **Recommended Alternative** listed above.

The newer model offers improved reasoning capabilities, faster inference speeds, and better cost-efficiency.

### Option 2: Continue Using Legacy Models (via Dedicated Endpoints)

If your workflow strictly requires a specific version from the deprecated list (e.g., for reproducibility or specific fine-tuning), you can deploy it on your own private GPU resources using our **Dedicated Endpoints**.

* **Benefit:** This guarantees exclusive access, zero cold starts, and consistent performance.
* **Action:** [https://novita.ai/dedicated-endpoint](https://novita.ai/dedicated-endpoint)

***

## Timeline

* **Announcement Date:** May 29, 2026
* **End of Life (EOL):** June 5, 2026 (UTC)

**Note:** After the EOL date, API requests to the deprecated model via Serverless Endpoints will result in an error.

If you have any questions or need assistance with the migration, please reach out to us via our **Discord** community or submit a support ticket.

Best regards,

**The Novita AI Team**


# July 4, 2025 Product Updates
Source: https://docs.novita.ai/changelog/30-06-25-04-07-2025



## **Model APIs Updates**

### **New Features**

* **Novita LLMs Dedicated Endpoints**

  Fully Managed Deployment for Your Custom AI Models

  High cost performance

  * Deploy available models and LoRA adapters on customizable GPU endpoints with per-hour billing.
  * Lower prices, No hard rate limits, Cheaper under high utilization, Custom base models and multiple LoRAs, Predictable performance unaffected.

    Learn more about [Pricing](https://novita.ai/pricing) and read this [Blog](https://blogs.novita.ai/deploy-custom-base-model-with-llm-dedicated-endpoint/) to learn how to deploy a custom base model with Novita LLMs Dedicated Endpoints.

***

## **Agent SandBox Updates**

### **New Features**

* **Agent Sandbox Is Now Live**

  **Agent Sandbox** is a high-performance cloud runtime purpose-built for **AI Agent workloads**, designed to support the full lifecycle of autonomous agents.

  Key capabilities include:

  * Millisecond-level startup, optimized for high-concurrency tasks
  * **Multi-language support**, including Python, JavaScript, and C++
  * Enables **code execution, web access, and system-level interactions**
  * **Per-second billing** for vCPU and memory — flexible, cost-efficient usage

  As the **infrastructure foundation for the runtime era of AI Agents**, Agent Sandbox empowers the next generation of generative agents with fast, scalable, and reliable execution capabilities.

  Visit the [**Documentation**](https://novita.ai/docs/guides/sandbox-overview) Center to learn more.

***

## **Platform Service Updates**

### **Improvements**

* **In-App Messaging Now Live for Critical Notifications**

  Our new **in-app messaging system** is now available, allowing you to receive important platform-level updates in one place, including:

  * **GPU failures, service interruptions, and operational alerts**
  * **Account-related notices**, such as coupon delivery or login activity
  * **Promotional events and platform campaigns**

  All messages can be viewed in your **Message Center** in the console. Stay informed, effortlessly.

***


# July 31, 2025 Notice
Source: https://docs.novita.ai/changelog/31-07-25



### **Model Decommissioning Notice**

To optimize resources and enhance the overall user experience, Novita will decommission the **Stable Diffusion 3  API** on **August 15, 2025 (UTC 0:00).**

The API will be inaccessible after this date. Please update your applications before then to prevent service interruptions.


# June 2, 2024
Source: https://docs.novita.ai/changelog/02-06-24



For the asynchronous V3 APIs, to receive the generated outputs, in addition to polling the [Task Result API](/api-reference/model-apis-task-result),
<br />now you can also listen to **"ASYNC\_TASK\_RESULT"** event on your webhook endpoint.

Also, you can test your integration without making actual API calls by enabling Test Mode, which will help you thoroughly test your logic.
<br />More details can be found at: [Webhook](/api-reference/model-apis-webhook).


# February 6, 2025
Source: https://docs.novita.ai/changelog/06-02-25



**Announcement: qwen/qwen-2-72b-instruct Model Decommission Notice**

We plan to decommission the **qwen/qwen-2-72b-instruct** model on **February 6 2025, at 23:59 Pacific Standard Time (PST).**

Please make any necessary preparations in advance. Thank you for your understanding and support!


# May 9, 2025 Product Updates
Source: https://docs.novita.ai/changelog/06-05-25--09-05-25



## **Model APIs Updates**

### **New Features**

* **Added filters to the Model Library for easier model selection**

  **Model API now features a newly launched filtering system!** Users can now filter models in the Model Library by **provider, pricing, context length, and model specifications**, enabling faster discovery and more efficient model selection and usage.


# September 6, 2024
Source: https://docs.novita.ai/changelog/06-09-24



📢 Video Merge Face API is now available on Novita AI.


# April 7, 2024
Source: https://docs.novita.ai/changelog/07-04-24



Now the LLM Chat API supports seamless integration into your product, offering compatibility with the OpenAI API standard. 🚀
<br />More details: [LLM API Docmentation](/guides/llm-api).


# April 11, 2025 Product Updates
Source: https://docs.novita.ai/changelog/07-04-25--11-04-25



## **New Features**

* **Template Library Added to Console**
  1. We've added a Template Library to the console, featuring templates created by both the official team and the user community.
  2. In addition to deploying and saving templates, users are encouraged to **share templates** they like by copying the link **with a single click**.
  3. To contribute your own templates, simply **set the visibility** to Public when creating one. Public templates will appear in the Template Library, and those that gain popularity may be featured.
* **New Model: meta-llama/llama-4-maverick-17b-128e-instruct-fp8**

  Novita has deployed **Llama 4 Maverick 17B Instruct (128E)** , a high-performance multimodal language model developed by Meta using a **Mixture-of-Experts (MoE)** architecture. The model dynamically activates 17 billion parameters per inference from a total of 400 billion parameters, distributed across 128 expert modules. It supports **text and image inputs in 12 languages**, and produces **text and code outputs**. Designed specifically for **vision-language tasks**, the model has been instruction-tuned to handle assistant-style interactions, image reasoning, and a wide range of general multimodal applications.
* **New Model: meta-llama/llama-4-scout-17b-16e-instruct**

  Novita has deployed **Llama 4 Scout 17B Instruct (16E)**, a **Mixture-of-Experts (MoE)** language model developed by Meta. The model dynamically activates 17 billion parameters per inference from a total of 109 billion parameters, utilizing 16 expert modules each time. It supports **multimodal inputs** (text and images) and generates **text or code outputs** in **12 languages**. Designed for **intelligent assistant interactions** and **visual reasoning tasks**, It also supports a **10 million-token context window**, and was trained on a **massive 40-trillion-token dataset**.

## **Improvements**

* **Improved Instance Status Visibility**

  Two new intermediate statuses—**“Starting”** and **“Stopping”**—have been introduced to provide **more granular visibility** during instance state transitions.


# December 7, 2024
Source: https://docs.novita.ai/changelog/07-12-24



Llama 3.3 70B is Here!

We're thrilled to announce the launch of Llama 3.3 70B, a model optimized for multilingual dialogue. It delivers exceptional performance, surpassing many open-source and proprietary chat models in industry benchmarks.

Pricing: **\$0.39 per 1M tokens** (input & output).


# January 9, 2025
Source: https://docs.novita.ai/changelog/09-01-25



For improved security, **API Keys** will no longer be visible in plaintext after creation. Please make sure to save your key when it's generated, as it cannot be retrieved afterward.


# May 9, 2024
Source: https://docs.novita.ai/changelog/09-05-24



Now Novita AI has released the Text-To-Speech API.

Text-To-Speech API generates realistic speech in various languages.


# March 14, 2025 Product Updates
Source: https://docs.novita.ai/changelog/10-03-25--14-03-25



## **New Features**

* **Embedding Model-BAAI: BGE-M3 is Added to Model Library**

  Novita supports the `baai/bge-m3` text embedding model. This model is capable of simultaneously achieving three core functionalities: dense retrieval, multi-vector retrieval, and sparse retrieval. Designed to be compatible with over 100 languages, it supports a variety of input formats, ranging from short sentences to long texts of up to 8,192 tokens.

  For detailed usage instructions, please refer to the API documentation: [Model-BAAI: BGE-M3](https://novita.ai/docs/api-reference/model-apis-llm-create-embeddings)
* **Login Security: Limit on Incorrect Password Attempts**

  To enhance account security, a login attempt limit has been implemented: Maximum 5 incorrect passwords within 1 hour.

## **Improvements**

* **Pricing Update for Wan 2.1 T2V Video Model**

  Optimized pricing strategy for video generation models: The pricing for all resolutions is now standardized at \$0.08 per second.


# April 11, 2024
Source: https://docs.novita.ai/changelog/11-04-24



We've recently added more hot LLM models for you, now the supported models include:

* lzlv\_70b ([https://huggingface.co/lizpreciatior/lzlv\\\_70b\\\_fp16\\\_hf](https://huggingface.co/lizpreciatior/lzlv\\_70b\\_fp16\\_hf))

* Nous-Hermes-2-Mixtral-8x7B-DPO ([https://huggingface.co/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO](https://huggingface.co/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO))

* gryphe/mythomax-l2-13b ([https://huggingface.co/Gryphe/MythoMax-L2-13b](https://huggingface.co/Gryphe/MythoMax-L2-13b))

* nousresearch/nous-hermes-llama2-13b ([https://huggingface.co/NousResearch/Nous-Hermes-Llama2-13b](https://huggingface.co/NousResearch/Nous-Hermes-Llama2-13b))


# November 11, 2024
Source: https://docs.novita.ai/changelog/11-11-24



Building on our last update, where we brought you unbeatable pricing on Llama 3.1 8B, our team has kept working hard to bring even more savings across our platform!

📉 New Pricing for LLama 3.1 70B Instruct

* Input: **\$0.34/Mtokens** (↓33.3% from \$0.51)
* Output: **\$0.39/Mtokens** (↓47.2% from \$0.74)

These new rates are live on both Novita AI and OpenRouter, where we've secured the #1 spot for competitive pricing.


# February 14, 2025
Source: https://docs.novita.ai/changelog/14-02-25



We've introduced a referral program for developers. Refer a friend to Novita, and both of you will receive \$20 in DeepSeek API credits, with a total of up to \$500 in credits available.

For more details, please refer to [Referral Program](https://novita.ai/referral).


# March 14, 2024
Source: https://docs.novita.ai/changelog/14-03-24



We've released novita.ai Javascript SDK version 1.0.35, available at [https://www.npmjs.com/package/novita-sdk/v/1.0.35](https://www.npmjs.com/package/novita-sdk/v/1.0.35).

This version introduces the img2ImgV3 and txt2ImgV3 methods for calling the API v3 txt2img and img2img,
<br />and you can find examples here: [https://github.com/novitalabs/javascript-sdk/blob/main/examples](https://github.com/novitalabs/javascript-sdk/blob/main/examples).


# April 18, 2025 Product Updates
Source: https://docs.novita.ai/changelog/14-04-25--18-04-25



## **New Features**

* **Sign Up with Google, GitHub, or Hugging Face Now Supported**

  Users can now register quickly and securely using their existing **Google**, **GitHub**, or **Hugging Face** accounts—no need to create a new password.
* **Rate Limit Display Added for LLM Usage in Console**

  The console now displays **rate limit information** for large language models (LLMs), allowing users to easily view the default request limits for each model and better plan their usage.

  In addition, a new rate limit increase request entry has been added, enabling users to submit requests for higher limits based on specific business needs.
* **New Video Model Added: Wan 2.1 I2V**

  The **Wan 2.1 I2V (Image-to-Video)** model is now available on the platform. It enables the generation of continuous video sequences from a single input image.

  This model delivers strong performance in **motion consistency**, **visual stability**, and **detail fidelity**, making it well-suited for **creative content generation**, **virtual character animation**, and related use cases.
* **Pricing Update for Video Model Wan 2.1 T2V**

  We’ve introduced a **resolution-based pricing model** for the **Wan 2.1 T2V (Text-to-Video)** model.\
  Users are now charged based on the output video resolution, enabling more flexible and cost-effective usage based on quality requirements.
* **Function Calling Now Supported on meta-llama/llama-4-maverick-17b-128e-instruct-fp8**

  The model `meta-llama/llama-4-maverick-17b-128e-instruct-fp8` supports the function call feature. For usage instructions, please refer to the [documentation](https://novita.ai/docs/guides/llm-function-calling).
* **Function Calling Now Supported on meta-llama/llama-4-scout-17b-16e-instruct**

  The model `meta-llama/llama-4-scout-17b-16e-instruct` supports the function call feature. For usage instructions, please refer to the [documentation](https://novita.ai/docs/guides/llm-function-calling).


# January 16, 2025
Source: https://docs.novita.ai/changelog/16-01-25



We're excited to bring DeepSeek V3, a state-of-the-art MoE language model, to Novita AI!

Why Choose DeepSeek V3?

* A robust 671B total parameter model, outperforming open-source peers and rivaling top-tier closed-source models.
* Performs exceptionally well for coding tasks, making it a great companion for developers.
* Handle complex inputs with 16K context size and 16K max output.
* Just \$0.89/M tokens, delivering exceptional value without compromising performance.

How to Access DeepSeek-V3:

* Explore it through our Playground: [https://novita.ai/models/llm/deepseek-deepseek\_v3](https://novita.ai/models/llm/deepseek-deepseek_v3);
* Use it seamlessly via OpenRouter: [https://openrouter.ai/deepseek/deepseek-chat](https://openrouter.ai/deepseek/deepseek-chat).

Experience the future of AI—try DeepSeek V3 today! 🚀


# Weekly Product Updates - March 21, 2025
Source: https://docs.novita.ai/changelog/17-03-25--21-03-25



## **New Features**

* **LLM Full Model Upgrade: Function Calling & Structured Outputs Available Now**

  Novita now supports Function Calling and Structured Outputs for all capability-supported models on the platform. Developers can now filter models using these tags for more precise API integration and structured data generation that aligned with custom schemas. Refer to the API documentation for details: [**Function calling**](https://novita.ai/docs/guides/llm-function-calling) & [**Structured Outputs**](https://novita.ai/docs/guides/llm-structured-outputs)
* **Image Dedicated Endpoint now supports billing queries**

  Billing for Image Dedicated Endpoints now includes monthly query support.
* **The annual and monthly subscription functionality of GPU Instance is now officially released**
  1. **Flexible Billing Model:** Users can freely choose between on-demand and monthly subscription when launching instances.
  2. **Enhanced Resource Guarantee:** During the monthly subscription period, even if an instance is shut down, it remains reserved for the user, significantly enhancing the user experience.
  3. **Seamless Service Transition:** Supports converting on-demand instances to annual or monthly subscriptions, as well as renewals during the subscription period.
  4. **Monthly Subscription Discounts:** Monthly subscriptions start at a 10% discount over pay-as-you-go, with additional saving for longer terms.
* **Team members' nicknames are now displayed in the task lists for Instance, Serverless GPUs, and more**

  When the account is in Team mode, the GPUs Console now displays member nicknames.


# April 19, 2024
Source: https://docs.novita.ai/changelog/19-04-24



🔥 Llama 3 models are now available on novita.ai.

You can try them out in the playground:

* meta-llama/llama-3-8b-instruct: [https://novita.ai/model-api/llm-api/playground#meta-llama-llama-3-8b-instruct](https://novita.ai/llm-api/playground#meta-llama-llama-3-8b-instruct)

* meta-llama/llama-3-70b-instruct: [https://novita.ai/model-api/llm-api/playground#meta-llama-llama-3-70b-instruct](https://novita.ai/llm-api/playground#meta-llama-llama-3-70b-instruct)


# July 19, 2024
Source: https://docs.novita.ai/changelog/19-07-24



Novita AI has released **"GPU Instance"**, which provides cost-effective, accessible high-performance computing with easy GPU access, ideal for complex AI tasks. Users benefit from on-demand rental, optimizing efficiency and cost-effectiveness.
<br />For more details, please visit: [https://novita.ai/gpu-instance](https://novita.ai/gpu-instance).


# October 19, 2024
Source: https://docs.novita.ai/changelog/19-10-24



We've recently deployed some new LLM models. You can try them out in the playground:

* **qwen/qwen-2-72b-instruct**: [https://novita.ai/llm-api/playground#qwen-qwen-2-72b-instruct](https://novita.ai/llm-api/playground#qwen-qwen-2-72b-instruct)

* **qwen/qwen-2.5-72b-instruct**: [https://novita.ai/llm-api/playground#qwen-qwen-2.5-72b-instruct](https://novita.ai/llm-api/playground#qwen-qwen-2.5-72b-instruct)

* **qwen/qwen-2-7b-instruct**: [https://novita.ai/llm-api/playground#qwen-qwen-2-7b-instruct](https://novita.ai/llm-api/playground#qwen-qwen-2-7b-instruct)

* **sao10k/l31-70b-euryale-v2.2**: [https://novita.ai/llm-api/playground#sao10k-l31-70b-euryale-v2.2](https://novita.ai/llm-api/playground#sao10k-l31-70b-euryale-v2.2)

Or integrate with API: [LLM API Documentation](/guides/llm-api).


# December 19, 2024
Source: https://docs.novita.ai/changelog/19-12-24



The **Team** feature is now available.

**With Team, you can:**

* Convert your personal account into a team account or join an existing team to collaborate with others;
* Invite other users to join your team, and share the team's resources with them;
* Manage your members' permissions to the team's resources by assigning different roles;
* Analyze usage data for each team member.

For more information, please visit: [Team Documentation](/guides/team).


# March 20, 2024
Source: https://docs.novita.ai/changelog/20-03-24



We've released novita.ai Python SDK version 0.5.0 🐍

This version introduces the txt2img\_v3 and img2img\_v3 methods for calling the API v3 txt2img and img2img,
<br />and you can find examples here: [https://github.com/novitalabs/python-sdk/tree/main/examples](https://github.com/novitalabs/python-sdk/tree/main/examples).


# April 25, 2025 Product Updates
Source: https://docs.novita.ai/changelog/21-04-25--25-04-25



## **Model APIs Updates**

### **New Features**

* **New Models: The GLM-4-0414 Series and the Qwen2.5-7B-Instruct Model**

  As early as March this year, Zhipu AI had announced plans to open source new GLM models in mid-April, and true to their word, the GLM-4-0414 series was launched on schedule. The newly open-sourced models cover three categories: **foundation, reasoning, and reflection**. According to official data, some models in this series can rival OpenAI’s GPT-4o and DeepSeek’s V3/R1 series in performance.

  As a one-stop AIGC cloud service platform, Novita promptly launched the above models along with a limited-time free version of the Qwen2.5-7B-Instruct model. Novita also carried out full-chain optimization on the models, significantly reducing inference latency while maintaining the original model accuracy. Developers can efficiently access and utilize these models through API interfaces.

  1. [thudm/glm-4-9b-0414](https://novita.ai/models/llm/thudm-glm-4-9b-0414)
  2. [thudm/glm-z1-9b-0414](https://novita.ai/models/llm/thudm-glm-z1-9b-0414)
  3. [thudm/glm-z1-32b-0414](https://novita.ai/models/llm/thudm-glm-z1-32b-0414)
  4. [thudm/glm-4-32b-0414](https://novita.ai/models/llm/thudm-glm-4-32b-0414)
  5. [thudm/glm-z1-rumination-32b-0414](https://novita.ai/models/llm/thudm-glm-z1-rumination-32b-0414)
* **New Product: LLM Dedicated Endpoint**

  Novita introduces LLM Dedicated Endpoint – a fully managed, high-performance AI deployment solution. Designed for scaling teams and growing businesses, it delivers reliable, high-performance AI infrastructure without the complexity of self-management.\_For details, see:  [Dedicated Endpoint](https://novita.ai/dedicated-endpoint)
* **Novita Referral Vouchers Now Usable Across All LLM Models**

  Novita's referral program vouchers can be used with all LLM models, subject to applicable rate limits. For details, see: [llm-rate-limits](https://novita.ai/docs/guides/llm-rate-limits)

***

## **GPUs Updates**

### **Improvements**

* **Optimized Storage Architecture for GPU Container Instances**

  To address the overlap between system disks and local disks, we have updated the instance storage policy. Effective immediately, the option to configure **a local disk for new instances has been discontinued**. Instances and templates that have already been created with local disk settings will remain unaffected.

  Following these changes, instances now support **system disk expansion**. For instances that have existing local disks, users can now unmount or resize the local disk space. When local storage is unmounted, all associated data will be automatically migrated to the system disk to ensure data continuity.\
  For more details, please refer to the [Instance Management](https://novita.ai/gpus-console/instances) page.


# January 22, 2025
Source: https://docs.novita.ai/changelog/22-01-25



Introducing the brand-new Novita AI, including our new brand logo and site style.

<Frame>
  <img />
</Frame>

We've reimagined our brand to reflect what we've always stood for: rock-solid infrastructure that empowers developers to build the future. From startups to larger organizations, we're making AI cloud accessible to all.


# January 24, 2025
Source: https://docs.novita.ai/changelog/24-01-25



Today, Novita AI has been onboarded to **Artificial Analysis**, which provides benchmarking and related information to support people & organizations in choosing the right model for their use-case and which provider to use for that model.

Please check out our performance benchmarks at: [https://artificialanalysis.ai/providers/novita](https://artificialanalysis.ai/providers/novita).


# Weekly Product Updates - March 28, 2025
Source: https://docs.novita.ai/changelog/24-03-25--28-03-25



## **New Features**

* **Added new model: deepseek-v3-0324**

  deepseek-v3-0324 is the latest high-performance large language model launched by Novita, representing the newest version of the flagship conversational model series from the DeepSeek team. It adopts a Mixture-of-Experts (MoE) architecture and boasts a parameter scale of 685 billion parameters.

## **Improvements**

* **Optimized third-party login integration**

  Fixed an issue where multiple clicks on third-party login prevented users from signing in.
* **Exclusive discounts now displayed in "Explore-Template and** **GPU Server" section**

  Customers with exclusive discounts now see discounted prices directly within the "Explore-Template and GPU Server" sections, rather than solely in billing. This provides clearer discount visibility to support decision-making.
* **Improved user interaction for the Model Library**

  Clicking "See All" in a Model Library category now takes users directly to a dedicated page showcasing all templates within that category, instead of redirecting them to the homepage.


# July 24, 2024
Source: https://docs.novita.ai/changelog/24-07-24



🔥 Llama 3.1 models are now available on Novita AI.

You can try them out in the playground:

* **meta-llama/llama-3.1-405b-instruct**: [https://novita.ai/llm-api/playground#meta-llama-llama-3.1-405b-instruct](https://novita.ai/llm-api/playground#meta-llama-llama-3.1-405b-instruct)

* **meta-llama/llama-3.1-70b-instruct**: [https://novita.ai/llm-api/playground#meta-llama-llama-3.1-70b-instruct](https://novita.ai/llm-api/playground#meta-llama-llama-3.1-70b-instruct)

* **meta-llama/llama-3.1-8b-instruct**: [https://novita.ai/llm-api/playground#meta-llama-llama-3.1-8b-instruct](https://novita.ai/llm-api/playground#meta-llama-llama-3.1-8b-instruct)

Or integrate with API: [LLM API Documentation](/guides/llm-api).


# March 26, 2025 Notice
Source: https://docs.novita.ai/changelog/26-03-25



## Platform Service Adjustment Notice

### 1. Model Decommissioning Notice

To continuously enhance platform service efficiency, focus on core product capability upgrades, and deliver more advanced, high-quality, and standardized services, Novita platform will decommission certain large language models (LLMs) on April 3, 2025(UTC 0:00). The list of models scheduled for removal is as follows:

* teknium/openhermes-2.5-mistral-7b
* qwen/qwen-2-7b-instruct
* nousresearch/nous-hermes-llama2-13b
* qwen/qwen-2-vl-72b-instruct
* openchat/openchat-7b


# December 26, 2024
Source: https://docs.novita.ai/changelog/26-12-24



Introducing the **Observability system**, which helps you monitor the availability and performance of your services in Novita AI.

The Observability system is currently in beta and only available for the LLM API service, more details can be found in the [LLM API Metrics Documentation](/guides/observability-llm-api-metrics).


# November 27, 2024
Source: https://docs.novita.ai/changelog/27-11-24



The latest Llama 3.2 Instruct models are now available at Novita AI, offering outstanding performance at competitive prices:

* **Llama 3.2 3B Instruct:** \$0.03/Mtokens (input), \$0.05/Mtokens (output)
* **Llama 3.2 1B Instruct:** \$0.02/Mtokens (input and output)
* **Llama 3.2 11B Vision Instruct:** \$0.06/Mtokens (input and output)

Experience these cutting-edge models in our newly updated LLM playground.


# April 30, 2025 Product Updates
Source: https://docs.novita.ai/changelog/28-04-25--30-04-25



## **Model APIs Updates**

### **New Features**

* **Rate Limits Now Applied to Image, Video, and Audio APIs**

  Rate limiting has been enabled for the **Image, Video, and Audio API products** to ensure reliable performance and fair usage:

  1. **IPM (Images Per Minute)**: Caps the number of images that can be generated per minute.
  2. **RPM (Requests Per Minute)**: Limits total API calls per minute across all endpoints.

  If your requests exceed the set thresholds, rate limit errors will be returned.

  You can check your current limits and request higher quotas directly in the console. Look for the **“Increase Limit”** button next to each metric if your use case requires more capacity.
* **Five Qwen3 Series Models Now Available**

  We’ve added five new models from the Qwen3 series on Novita.:

  1. [Qwen3 0.6B](https://novita.ai/models/llm/qwen-qwen3-0.6b-fp8)
  2. [Qwen3 1.7B](https://novita.ai/models/llm/qwen-qwen3-1.7b-fp8)
  3. [Qwen3 4B](https://novita.ai/models/llm/qwen-qwen3-4b-fp8)
  4. [Qwen3 8B](https://novita.ai/models/llm/qwen-qwen3-8b-fp8)
  5. [Qwen3 14B](https://novita.ai/models/llm/qwen-qwen3-14b-fp8)
* **New Model: DeepSeek Prover V2 671B**

  Novita supports [**DeepSeek Prover V2 671B**](https://novita.ai/models/llm/deepseek-deepseek-prover-v2-671b) via Serverless Endpoints.
* **Updated LLM API Rate Limits**

  User accounts are now automatically tiered into **L1-L5 service levels** based on **recent top-up amount**, with each tier granting different **RPM (Requests Per Minute)** and **TPM (Tokens Per Minute)** limits. For more details, please refer to [Rate limits - Documentation](https://novita.ai/docs/guides/llm-rate-limits).
* **First Batch of Large-Parameter Qwen3 Models Now Available**

  Three large-parameter Qwen3 models have been newly launched:

  1. [Qwen3-235B-A22B](https://novita.ai/models/llm/qwen-qwen3-235b-a22b-fp8)
  2. [Qwen3-30B-A3B](https://novita.ai/models/llm/qwen-qwen3-30b-a3b-fp8)
  3. [Qwen3-32B](https://novita.ai/models/llm/qwen-qwen3-32b-fp8)


# May 28, 2024
Source: https://docs.novita.ai/changelog/28-05-24



The img2img Playground now supports ControlNet and IPAdapter.

And txt2img Playground allows you to share your parameters with a share-link.

*Historical note: The original playground and example share links are no longer available.*


# February 29, 2024
Source: https://docs.novita.ai/changelog/29-02-24



We've launched some new features lately, here are the details:

1. You can now check if the civitai models exist on novita.ai. Simply copy the civitai model page URL and paste it into the search box on the model search page. (The original model search page link is no longer available.)

2. SDXL Lightning LoRAs are now supported in the API /v3/async/txt2img. Available models include sdxl\_lightning\_2step\_lora, sdxl\_lightning\_4step\_lora, and sdxl\_lightning\_8step\_lora.

***

[View More](https://discord.gg/QwpfJqcxCX)


# April 29, 2024
Source: https://docs.novita.ai/changelog/29-04-24



Now you can chat with your favorite character by using the novita.ai LLM features.
<br />Play it now 👉 [https://novita.ai//model-api/llm-api/playground](https://novita.ai/llm-api/playground)


# April 04, 2025 Product Updates
Source: https://docs.novita.ai/changelog/31-03-25--04-04-25



## **New Features**

* **Added Support for Hugging Face Login**

  Users can now sign in using their Hugging Face account. Social login now supports Hugging Face in addition to Google and GitHub.
* **Added Qwen2.5 VL 72B Instruct to the Model Library**

  Qwen2.5 VL 72B Instruct is the latest high-performance multimodal large language model launched by Novita.

  Featuring enhanced visual comprehension capabilities, it also demonstrates strong performance in complex reasoning and decision-making tasks.
* **Added Gemma-3-27b-it to the Model Library**

  Gemma-3-27b-it is the latest high-performance multimodal large language model launched by Novita.

  Built on Gemini 2.0 architecture, it possesses cross-modal reasoning capabilities across text, images, and short videos.


