# Anthropic SDK (https://docs.inference.net/api/anthropic-sdk)
The Inference.net API supports the Anthropic Messages format at `POST https://api.inference.net/v1/messages`. This means you can use the Anthropic SDK to call any serverless model hosted on Inference.net, such as `glm-5.2`. Only your Inference API key is needed.
Requests are converted to and from the model's native format for you. Both non-streaming and streaming responses are supported.
This page covers calling Inference-hosted models with the Anthropic SDK. To route requests to Anthropic's own Claude models using your Anthropic API key, see the [Anthropic integration](/integrations/model-providers/anthropic) instead.
## Authentication [#authentication]
The Anthropic SDK sends its `apiKey` as the `x-api-key` header, but the Inference API expects your key as a Bearer token in the `Authorization` header. Pass your Inference API key as `authToken` (TypeScript) or `auth_token` (Python), which sends `Authorization: Bearer `. In TypeScript, also set `apiKey: null` so the SDK does not look for an Anthropic key.
```bash
export INFERENCE_API_KEY=
```
## Non-Streaming [#non-streaming]
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.inference.net",
apiKey: null,
authToken: process.env.INFERENCE_API_KEY,
});
const message = await client.messages.create({
model: "glm-5.2",
max_tokens: 1024,
messages: [{ role: "user", content: "What is the meaning of life?" }],
});
console.log(message.content);
```
```python Python
import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://api.inference.net",
auth_token=os.environ["INFERENCE_API_KEY"],
)
message = client.messages.create(
model="glm-5.2",
max_tokens=1024,
messages=[{"role": "user", "content": "What is the meaning of life?"}],
)
print(message.content)
```
```bash cURL
curl https://api.inference.net/v1/messages \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "glm-5.2",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "What is the meaning of life?"}]
}'
```
## Streaming [#streaming]
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.inference.net",
apiKey: null,
authToken: process.env.INFERENCE_API_KEY,
});
const stream = client.messages.stream({
model: "glm-5.2",
max_tokens: 1024,
messages: [{ role: "user", content: "Count from 1 to 5." }],
});
stream.on("text", (text) => {
process.stdout.write(text);
});
const message = await stream.finalMessage();
console.log("\n", message.usage);
```
```python Python
import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://api.inference.net",
auth_token=os.environ["INFERENCE_API_KEY"],
)
with client.messages.stream(
model="glm-5.2",
max_tokens=1024,
messages=[{"role": "user", "content": "Count from 1 to 5."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
```
```bash cURL
curl -N https://api.inference.net/v1/messages \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "glm-5.2",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": "Count from 1 to 5."}]
}'
```
The Messages format requires `max_tokens`. Reasoning models such as `glm-5.2` spend part of the token budget on reasoning before producing text, so set `max_tokens` high enough for both. If the budget runs out during reasoning, the response can come back with empty content.
The same code works for every serverless model. Set `model` to the model id you want. Browse available models at [inference.net/models](https://inference.net/models).
# API Quickstart (https://docs.inference.net/api/api-quickstart)
The Inference.net API is OpenAI-compatible, so you can use the OpenAI SDK or plain HTTP to make requests. There are three ways to use it:
1. **Call a model serverless**: call open-source models and popular closed-source models (GPT, Claude, Gemini) with just your Inference API key. Usage is billed per token to your credit balance.
2. **Proxy through Inference Gateway**: route requests to any provider (OpenAI, Anthropic, etc.) through Inference Gateway with your own provider API key.
3. **Call your custom model**: hit a model you've fine-tuned and deployed on the platform.
All three paths go through Inference Gateway, so you get the same metrics, cost tracking, and eval-readiness whichever one you use.
## Get an API Key [#get-an-api-key]
Visit [inference.net](https://inference.net) and create an account.
On the dashboard, go to the **API Keys** tab in the left sidebar. Create a new key or use the default key.
```bash
export INFERENCE_API_KEY=
```
***
## 1. Call a Model Serverless [#1-call-a-model-serverless]
Call models with just your Inference API key. No provider API key is needed. This works for two kinds of models:
* **Open-source models** hosted on Inference.net, such as `glm-5.2`.
* **Popular closed-source models**, such as `claude-haiku-4-5`, `gpt-5-mini`, and `gemini-3.5-flash`. Inference.net routes the request to the provider for you and bills the usage per token to your credit balance.
Browse available models at [inference.net/models](https://inference.net/models), or list them with `GET https://api.inference.net/v1/models`.
Prefer the Anthropic SDK? The API also supports the Anthropic Messages format. See [Anthropic SDK](/api/anthropic-sdk).
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const response = await client.chat.completions.create({
model: "glm-5.2",
messages: [{ role: "user", content: "What is the meaning of life?" }],
stream: true,
});
for await (const chunk of response) {
process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
response = client.chat.completions.create(
model="glm-5.2",
messages=[{"role": "user", "content": "What is the meaning of life?"}],
stream=True,
)
for chunk in response:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="", flush=True)
```
```rust Rust
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box> {
let api_key = std::env::var("INFERENCE_API_KEY")?;
let response: Value = reqwest::Client::new()
.post("https://api.inference.net/v1/chat/completions")
.bearer_auth(api_key)
.json(&json!({
"model": "glm-5.2",
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
]
}))
.send()
.await?
.json()
.await?;
println!(
"{}",
response["choices"][0]["message"]["content"]
.as_str()
.unwrap_or_default()
);
Ok(())
}
```
```go Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"model": "glm-5.2",
"messages": []map[string]string{
{"role": "user", "content": "What is the meaning of life?"},
},
})
req, _ := http.NewRequest("POST", "https://api.inference.net/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFERENCE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.Choices[0].Message.Content)
}
```
```bash cURL
curl -N https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.2",
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
],
"stream": true
}'
```
The same code works for every serverless model. Set `model` to the model id you want, for example `claude-haiku-4-5`. This includes our purpose-built [Schematron](/workhorse-models/schematron) models for structured data extraction.
***
## 2. Proxy Through Inference Gateway [#2-proxy-through-inference-gateway]
Route requests to any LLM provider (OpenAI, Anthropic, Groq, etc.) through Inference Gateway. You keep your existing provider API key, and the provider bills you directly. The gateway adds observability, cost tracking, and eval-readiness with roughly 10ms of added latency.
Use this path when you want a model that is not in the serverless catalog, or when you want usage billed to your own provider account. The captured metrics are the same as for serverless calls.
Your Inference project API key authenticates with the gateway. Your provider API key is forwarded to the provider via the `x-inference-provider-api-key` header.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
defaultHeaders: {
"x-inference-provider-api-key": process.env.OPENAI_API_KEY,
"x-inference-provider": "openai",
},
});
const response = await client.chat.completions.create({
model: "gpt-4.1",
messages: [{ role: "user", content: "What is the meaning of life?" }],
});
console.log(response.choices[0].message.content);
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
default_headers={
"x-inference-provider-api-key": os.environ["OPENAI_API_KEY"],
"x-inference-provider": "openai",
},
)
response = client.chat.completions.create(
model="gpt-4.1",
messages=[{"role": "user", "content": "What is the meaning of life?"}],
)
print(response.choices[0].message.content)
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "x-inference-provider-api-key: $OPENAI_API_KEY" \
-H "x-inference-provider: openai" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4.1",
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
]
}'
```
For detailed setup guides per provider (Anthropic, Groq, Cerebras, OpenRouter, and more), see the [Integrations](/integrations/overview) docs.
***
## 3. Call Your Custom Model [#3-call-your-custom-model]
Hit a model you've fine-tuned and deployed on Inference.net. The model path is your team slug followed by the deployment name, shown on your deployment's detail page in the dashboard.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const response = await client.chat.completions.create({
model: "your-team/your-model",
messages: [{ role: "user", content: "Hello, world!" }],
});
console.log(response.choices[0].message.content);
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
response = client.chat.completions.create(
model="your-team/your-model",
messages=[{"role": "user", "content": "Hello, world!"}],
)
print(response.choices[0].message.content)
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-team/your-model",
"messages": [
{"role": "user", "content": "Hello, world!"}
]
}'
```
Learn more about deploying models in the [Deploy](/platform/deploy/overview) docs.
***
## Headers Reference [#headers-reference]
| Header | Required | Description |
| ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization` | Yes | `Bearer ` — authenticates the request. For OpenAI-compatible SDKs, set this as the SDK's `apiKey`. |
| `Content-Type` | Yes | Must be `application/json`. |
| `x-inference-provider` | Proxy only | Routes the request to the correct provider: `openai`, `anthropic`, `cerebras`, `vertex-ai`, `gemini`, etc. For providers without a dedicated value that require it (for example Groq or Together AI), route by base URL instead with `x-inference-provider-url`. |
| `x-inference-provider-api-key` | Proxy only | Your provider's API key. The gateway forwards it downstream. For Anthropic's native SDK, use `x-api-key` instead. |
| `x-inference-provider-url` | No | Routes to any OpenAI-compatible provider by base URL, even if it doesn't have a dedicated integration. |
| `x-inference-environment` | No | Tags requests with an environment label, such as `production` or `staging`. |
| `x-inference-task-id` | No | Groups requests under a logical task for filtering and analytics in the dashboard. |
| `x-inference-metadata-*` | No | Attach arbitrary metadata to a request. The prefix is stripped to form the key — e.g., `x-inference-metadata-chat-id: abc123` stores `chat-id: abc123`. You can filter inferences and create datasets based on these keys in the dashboard. |
## Supported Request Parameters [#supported-request-parameters]
The API supports the standard OpenAI chat completions parameters:
| Parameter | Type | Description |
| ------------------- | --------- | ---------------------------------------------------------------------------------------------------- |
| `model` | `string` | The model to use. |
| `messages` | `array` | The conversation messages. |
| `stream` | `boolean` | Whether to stream the response. |
| `max_tokens` | `integer` | Maximum number of tokens to generate. |
| `temperature` | `number` | Sampling temperature (0–2). |
| `top_p` | `number` | Nucleus sampling threshold. |
| `frequency_penalty` | `number` | Penalizes repeated tokens based on frequency. |
| `presence_penalty` | `number` | Penalizes tokens based on whether they've appeared. |
| `response_format` | `object` | Set to `{"type": "json_object"}` or a JSON schema for [structured outputs](/api/structured-outputs). |
| `tools` | `array` | Tool/function definitions for [function calling](/api/function-calling). |
Need a parameter that isn't listed here? [Contact us](mailto:support@inference.net) and we'll add it.
## Next Steps [#next-steps]
Set up Inference platform with OpenAI, Anthropic, Groq, and other providers.
Get typed JSON responses from your API calls.
Process up to 50,000 requests in a single batch job.
Explore all models available on Inference.net.
# Batch API (https://docs.inference.net/api/async-inference/batch-api)
Learn how to use our OpenAI-compatible Batch API to send asynchronous groups of inference requests to Inference.net, with nearly unlimited rate limits and fast completion times. The service is ideal for processing a large number of jobs that don't require immediate responses.
Batch API is currently compatible with all the [models](https://inference.net/models) we offer.
Use [https://batch.inference.net/v1](https://batch.inference.net/v1) for all Batch API requests (including Files and Batches). Do not use [https://api.inference.net/v1](https://api.inference.net/v1) for batch jobs.
## Overview [#overview]
While some uses require you to send synchronous requests, there are many cases where requests do not need an immediate response or rate limits prevent you from executing a large number of queries quickly. Batch processing jobs are often helpful in use cases like:
1. Extracting structured data from a large number of documents.
2. Generating synthetic data for training.
3. Translating a large number of documents into other languages.
4. Summarizing a large number of customer interactions.
Inference.net's Batch API offers a straightforward set of endpoints that allow you to upload a batch of requests, kick off a batch processing job, query for the status of the batch, and eventually retrieve the collected results when the batch is complete.
Compared to using standard endpoints directly, Batch API has:
1. **Higher rate limits:** Substantially more headroom compared to the [synchronous APIs](/api/rate-limits).
2. **Fast completion times:** Each batch completes within 24 hours (and often much more quickly).
## Getting Started [#getting-started]
You'll need an Inference.net account and API key to use the Batch API. See our [Quick Start Guide](/api/api-quickstart) for instructions on how to create an account and get an API key.
Install the [OpenAI SDK](https://platform.openai.com/docs/libraries) for your language of choice.
To connect to Inference.net's Batch API using the OpenAI SDK, set the base URL to `https://batch.inference.net/v1`.
In this example, we are reading the API key from the environment variable `INFERENCE_API_KEY`.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://batch.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://batch.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL
export INFERENCE_API_KEY=
# All Batch API requests use https://batch.inference.net/v1
```
## Running A Batch Processing Job [#running-a-batch-processing-job]
### 1. Preparing Your Batch File [#1-preparing-your-batch-file]
Prepare a `.jsonl` file where each line is a separate JSON object that represents an individual request.
Each JSON object must be on a single line and cannot contain any line breaks.
Each JSON object must include the following fields:
* `custom_id`: A unique identifier for the request. This is used to reference the request's results after completion. It must be unique for each request in the file.
* `method`: The HTTP method to use for the request. Currently, only `POST` is supported.
* `url`: The URL to send the request to. Currently, only `/v1/chat/completions` and `/v1/completions` are supported.
* `body`: The request body, which contains the input for the inference request. The parameters in each line's `body` field are the same as the parameters for the underlying endpoint specified by the `url` field. See this [example](/quickstart#test-request) for more details.
Here's an example of an input file with 2 requests using the `/v1/chat/completions` endpoint.
```jsonl
{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "google/gemma-3-27b-instruct/bf-16", "messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"}], "max_tokens": 1000}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "google/gemma-3-27b-instruct/bf-16", "messages": [{"role": "system", "content": "You are an unhelpful assistant."}, {"role": "user", "content": "What is the capital of Belgium?"}], "max_tokens": 1000}}
```
And here is an example of an input file using the `/v1/completions` endpoint:
```jsonl
{"custom_id": "request-1", "method": "POST", "url": "/v1/completions", "body": {"model": "google/gemma-3-27b-instruct/bf-16", "prompt": "What is the capital of France?", "max_tokens": 1000}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/completions", "body": {"model": "google/gemma-3-27b-instruct/bf-16", "prompt": "What is the capital of Belgium?", "max_tokens": 1000}}
```
### 2. Uploading Your Batch Input File [#2-uploading-your-batch-input-file]
In order to create a Batch Processing job, you must first upload your input file.
```typescript TypeScript
import fs from "fs";
const batchInputFile = await client.files.create({
file: fs.createReadStream("batchinput.jsonl"),
purpose: "batch",
});
console.log(batchInputFile);
```
```python Python
batch_input_file = client.files.create(
file=open("batchinput.jsonl", "rb"),
purpose="batch",
)
print(batch_input_file)
```
```bash cURL
curl https://batch.inference.net/v1/files \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-F purpose="batch" \
-F file="@batchinput.jsonl"
```
The response will look similar to this, depending on the language you are using:
```json JSON
{
"id": "file-abc123"
}
```
### 3. Starting the Batch Processing Job [#3-starting-the-batch-processing-job]
Once you've successfully uploaded your input file, you can use the ID of the file to create a batch.
In this case, let's assume the file ID is `file-abc123`.
The completion window accepts any value ending in `h`, `m`, `s`, or `ms` (e.g. `24h`), bounded between 24 hours and 7 days; `24h` is the default.
To associate custom metadata with the batch, you can provide an optional `metadata` parameter.
This metadata is not used by Inference.net to complete requests, but it is included when retrieving the status of a batch so that you can associate custom metadata with the batch.
> **Note:** The Batch Processing job will begin processing immediately after creation.
Create the Batch
```typescript TypeScript
import type { BatchCreateParams } from "openai/resources/batches";
const batch = await client.batches.create({
input_file_id: batchInputFile.id,
endpoint: "/v1/chat/completions",
completion_window: "24h",
metadata: {
description: "nightly eval job",
},
// Optional. Must be HTTPS.
webhook_url: "https://example.com/my_webhook",
} as BatchCreateParams);
console.log(batch);
```
```python Python
batch = client.batches.create(
input_file_id=batch_input_file.id,
endpoint="/v1/chat/completions",
completion_window="24h",
metadata={
"description": "nightly eval job",
},
# Optional. Must be HTTPS.
extra_body={
"webhook_url": "https://example.com/my_webhook",
},
)
print(batch)
```
```bash cURL
curl https://batch.inference.net/v1/batches \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_file_id": "file-abc123",
"endpoint": "/v1/chat/completions",
"completion_window": "24h",
"metadata": {
"description": "nightly eval job"
},
"webhook_url": "https://example.com/webhook"
}'
```
This request will return a batch object with metadata about your batch:
```json JSON
{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/chat/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "validating",
"output_file_id": null,
"error_file_id": null,
"created_at": 1714508499,
"in_progress_at": 1714508500,
"expires_at": 1714536634,
"completed_at": null,
"failed_at": null,
"expired_at": null,
"request_counts": {
"total": 2,
"completed": 0,
"failed": 0
},
"metadata": {
"description": "nightly eval job"
}
}
```
Inference.net supports a `webhook_url` that you can set to receive a webhook notification when the batch is complete.
The `webhook_url` must be an HTTPS URL that can receive POST requests.
If no metadata is provided when the batch is created, the `metadata` field will be null.
Your webhook will receive a POST with a request JSON body that looks like this:
```json JSON
{
"batch_id": "batch_abc123",
"status": "completed",
"metadata": {
"description": "nightly eval job"
}
}
```
The `webhook_url` parameter is not part of the official OpenAI SDK types. In TypeScript, cast the params as `BatchCreateParams` to avoid type errors. In Python, pass it via `extra_body`.
### 4. Checking the Status of a Batch [#4-checking-the-status-of-a-batch]
You can check the status of a batch at any time, which will also return a Batch object.
Check the status of a batch by retrieving it using the Batch ID assigned to it by Inference.net (represented here by `batch_abc123`).
```typescript TypeScript
const retrievedBatch = await client.batches.retrieve(batch.id);
console.log(retrievedBatch);
```
```python Python
retrieved_batch = client.batches.retrieve(batch.id)
print(retrieved_batch)
```
```bash cURL
curl https://batch.inference.net/v1/batches/batch_abc123 \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json"
```
The status of a given Batch object can be any of the following:
| Status | Description |
| ------------ | ------------------------------------------------------------------------------ |
| validating | the input file is being validated before the batch can begin |
| failed | the input file has failed the validation process |
| in\_progress | the input file was successfully validated and the batch is currently being run |
| finalizing | the batch has completed and the results are being prepared |
| completed | the batch has been completed and the results are ready |
| expired | the batch was not able to be completed within the completion window |
| cancelling | the batch is being cancelled (may take up to 10 minutes) |
| cancelled | the batch was cancelled |
The `cancelling` and `cancelled` states are reserved for compatibility with the OpenAI schema but are not reachable: cancellation is not currently supported, and `POST /batches/{id}/cancel` returns an unsupported-endpoint error.
### 5. Retrieving the Results [#5-retrieving-the-results]
You will receive an email notification when the batch is complete.
Once the batch is complete, you can download the output by making a request against the Files API using the `output_file_id` field from the Batch object.
Similarly, you can retrieve the error file (containing all failed requests) by making a request against the Files API using the `error_file_id` field from the Batch object.
```typescript TypeScript
const fileResponse = await client.files.content(batch.output_file_id);
const fileContents = await fileResponse.text();
console.log(fileContents);
```
```python Python
file_response = client.files.content(batch.output_file_id)
print(file_response.text)
```
```bash cURL
curl https://batch.inference.net/v1/files/output-file-id/content \
-H "Authorization: Bearer $INFERENCE_API_KEY" > batch_output.jsonl
```
The output `.jsonl` file will have one response line for every successful request line in the input file. Any failed requests in the batch will have their error information written to an error file that can be found via the batch's `error_file_id`.
> Note that the output line order **may not match** the input line order.
Instead of relying on order to process your results, use the custom\_id field which will be present in each line of your output file and allow you to map requests in your input to results in your output.
```jsonl
{"id": "batch_req_123", "custom_id": "request-2", "response": {"status_code": 200, "request_id": "req_123", "body": {"id": "chatcmpl-123", "object": "chat.completion", "created": 1711652795, "model": "google/gemma-3-27b-instruct/bf-16", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello."}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 22, "completion_tokens": 2, "total_tokens": 24}, "system_fingerprint": "fp_123"}}, "error": null}
{"id": "batch_req_456", "custom_id": "request-1", "response": {"status_code": 200, "request_id": "req_789", "body": {"id": "chatcmpl-abc", "object": "chat.completion", "created": 1711652789, "model": "google/gemma-3-27b-instruct/bf-16", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello! How can I assist you today?"}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 20, "completion_tokens": 9, "total_tokens": 29}, "system_fingerprint": "fp_3ba"}}, "error": null}
```
## Listing All Batches [#listing-all-batches]
At any time, you can see all your batches. For users with many batches, you can use the `limit` and `after` parameters to paginate your results.
If an `after` parameter is provided, the list will return batches after the specified batch ID.
```typescript TypeScript
const list = await client.batches.list({
limit: 10,
after: batch.id,
});
for await (const b of list) {
console.log(b);
}
```
```python Python
batches = client.batches.list(limit=10, after=batch.id)
for b in batches:
print(b)
```
```bash cURL
curl 'https://batch.inference.net/v1/batches?limit=10&after=batch_abc123' \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json"
```
## Batch Expiration [#batch-expiration]
Batches that do not complete in time eventually move to an `expired` state; unfinished requests within that batch are cancelled, and any responses to completed requests are made available via the batch's output file. You will only be charged for tokens consumed from any completed requests.
Expired requests will be written to your error file with the message as shown below. You can use the `custom_id` to retrieve the request data for expired requests.
```jsonl
{"id": "batch_req_123", "custom_id": "request-3", "response": null, "error": {"code": "batch_expired", "message": "This request could not be executed before the completion window expired."}}
{"id": "batch_req_123", "custom_id": "request-7", "response": null, "error": {"code": "batch_expired", "message": "This request could not be executed before the completion window expired."}}
```
## Rate Limits [#rate-limits]
Batch API rate limits are separate from existing per-model rate limits. A batch input file may include up to 1,000,000 requests (one JSONL line each). If you need higher rate limits, please contact us at [support@inference.net](mailto:support@inference.net).
## Compatibility Notes [#compatibility-notes]
### 1. Batch Cancellation [#1-batch-cancellation]
Although the OpenAI SDK supports the ability to cancel an in-progress batch, Inference.net does not currently support batch cancellation. This is under development and will be available soon.
### 2. Model Availability [#2-model-availability]
Inference.net's Batch Processing is compatible with all of Inference.net's supported models. See our list of [supported models](https://inference.net/models) for a complete list.
# Group API (https://docs.inference.net/api/async-inference/group)
Learn how to use our Group API to submit multiple inference requests together, perfect for processing related tasks that need to be tracked as a unit. The Group API supports both chat completions and text completions with up to 50 requests per group.
Group API is available for `/v1/slow/group/chat/completions`, `/v1/slow/group/completions`, `/v1/slow/group/embeddings`, and `/v1/slow/group/responses` endpoints.
You should not mix completion and chat-completion requests in the same group.
## Overview [#overview]
The Group API provides a streamlined way to submit multiple asynchronous inference requests as a single unit. Unlike the Batch API which requires JSONL file uploads, the Group API accepts requests directly in the request body, making it ideal for:
* **Small to medium batches:** Process up to 50 requests at once
* **Related tasks:** Group related inference requests together
* **Webhook notifications:** Get notified when all requests in a group complete
* **Simpler integration:** No file uploads or JSONL formatting required
* **Faster implementation:** Direct JSON API calls without file management
## Group API vs Batch API [#group-api-vs-batch-api]
| Feature | Group API | Batch API |
| ---------------- | ----------------------------------- | -------------------------------- |
| Maximum requests | 50 | 1,000,000 |
| Input format | JSON array in request body | JSONL file upload |
| File management | Not required | Required |
| Use case | Small batches, quick implementation | Large-scale processing |
| Webhook support | Yes | Yes |
| Completion time | 72 hours | 24 hours (default), up to 7 days |
## Getting Started [#getting-started]
### 1. Submit a Group Request [#1-submit-a-group-request]
Submit multiple requests together by sending them as an array in the request body:
```typescript TypeScript
const response = await fetch(
"https://api.inference.net/v1/slow/group/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFERENCE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
requests: [
{
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is the capital of France?" },
],
max_tokens: 100,
},
{
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is the capital of Germany?" },
],
max_tokens: 100,
},
],
webhook_id: "my-webhook-123", // Optional: attach a webhook for notifications
}),
},
);
const result = await response.json();
console.log(result); // { groupId: "group_abc123", groupSize: 2 }
```
```python Python
import os
import requests
response = requests.post(
"https://api.inference.net/v1/slow/group/chat/completions",
headers={
"Authorization": f"Bearer {os.environ['INFERENCE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"requests": [
{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"},
],
"max_tokens": 100,
},
{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of Germany?"},
],
"max_tokens": 100,
},
],
"webhook_id": "my-webhook-123", # Optional: attach a webhook for notifications
},
)
result = response.json()
print(result) # {"groupId": "group_abc123", "groupSize": 2}
```
```bash cURL
curl https://api.inference.net/v1/slow/group/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"requests": [
{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
],
"max_tokens": 100
},
{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of Germany?"}
],
"max_tokens": 100
}
],
"webhook_id": "my-webhook-123"
}'
```
The response will include a group ID and the number of requests:
```json JSON
{
"groupId": "group_xY3kL9mN2pQ",
"groupSize": 2
}
```
### 2. Retrieve Group Results [#2-retrieve-group-results]
Once your group is processed, retrieve all generation results using the group ID:
```typescript TypeScript
const response = await fetch(
`https://api.inference.net/v1/slow/group/${groupId}/generations`,
{
headers: {
Authorization: `Bearer ${process.env.INFERENCE_API_KEY}`,
},
},
);
const result = await response.json();
console.log(result.generations); // Array of all completed generations
```
```python Python
import os
import requests
response = requests.get(
f"https://api.inference.net/v1/slow/group/{group_id}/generations",
headers={"Authorization": f"Bearer {os.environ['INFERENCE_API_KEY']}"},
)
result = response.json()
print(result["generations"]) # Array of all completed generations
```
```bash cURL
curl https://api.inference.net/v1/slow/group/group_xY3kL9mN2pQ/generations \
-H "Authorization: Bearer $INFERENCE_API_KEY"
```
The response includes all generations in the group:
```json JSON
{
"generations": [
{
"id": "gen_abc123",
"state": "Success",
"request": {
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
],
"max_tokens": 100
},
"response": {
"id": "gen_abc123",
"object": "chat.completion",
"choices": [
{
"message": {
"role": "assistant",
"content": "The capital of France is Paris."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 8,
"total_tokens": 33
}
}
},
{
"id": "gen_def456",
"state": "Success",
"request": {
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of Germany?"}
],
"max_tokens": 100
},
"response": {
"id": "gen_def456",
"object": "chat.completion",
"choices": [
{
"message": {
"role": "assistant",
"content": "The capital of Germany is Berlin."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 8,
"total_tokens": 33
}
}
}
]
}
```
## Using Webhooks [#using-webhooks]
Attach a webhook to receive notifications when your group completes processing. Include the `webhook_id` when submitting the group:
```typescript TypeScript
const response = await fetch(
"https://api.inference.net/v1/slow/group/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFERENCE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
requests: [
/* your requests */
],
webhook_id: "my-webhook-123",
}),
},
);
```
```python Python
import os
import requests
response = requests.post(
"https://api.inference.net/v1/slow/group/chat/completions",
headers={
"Authorization": f"Bearer {os.environ['INFERENCE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"requests": [
# your requests
],
"webhook_id": "my-webhook-123",
},
)
```
```bash cURL
curl https://api.inference.net/v1/slow/group/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type": "application/json" \
-d '{
"requests": [ ... ],
"webhook_id": "my-webhook-123"
}'
```
When all requests in the group complete, your webhook will receive a notification with:
* Group ID
* Completion status
* Summary of successful and failed requests
* Custom IDs for each request (if provided)
See our [Webhook Documentation](/api/async-inference/webhooks/getting-started-with-webhooks) for setup instructions.
## Text Completions Support [#text-completions-support]
The Group API also supports text completions:
```typescript TypeScript
const response = await fetch(
"https://api.inference.net/v1/slow/group/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFERENCE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
requests: [
{
model: "google/gemma-3-27b-instruct/bf-16",
prompt: "The capital of France is",
max_tokens: 10,
},
{
model: "google/gemma-3-27b-instruct/bf-16",
prompt: "The capital of Germany is",
max_tokens: 10,
},
],
}),
},
);
```
```python Python
import os
import requests
response = requests.post(
"https://api.inference.net/v1/slow/group/completions",
headers={
"Authorization": f"Bearer {os.environ['INFERENCE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"requests": [
{
"model": "google/gemma-3-27b-instruct/bf-16",
"prompt": "The capital of France is",
"max_tokens": 10,
},
{
"model": "google/gemma-3-27b-instruct/bf-16",
"prompt": "The capital of Germany is",
"max_tokens": 10,
},
],
},
)
```
```bash cURL
curl https://api.inference.net/v1/slow/group/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"requests": [
{
"model": "google/gemma-3-27b-instruct/bf-16",
"prompt": "The capital of France is",
"max_tokens": 10
},
{
"model": "google/gemma-3-27b-instruct/bf-16",
"prompt": "The capital of Germany is",
"max_tokens": 10
}
]
}'
```
## Limits and Constraints [#limits-and-constraints]
* **Maximum requests per group:** 50
* **Request format:** Direct JSON (no JSONL files required)
* **Supported endpoints:**
* `/v1/slow/group/chat/completions`
* `/v1/slow/group/completions`
* `/v1/slow/group/embeddings`
* `/v1/slow/group/responses`
* **Completion time:** 72 hours
* **Request expiration:** Groups expire after 72 hours if not completed
## Best Practices [#best-practices]
1. **Group related requests:** Use groups for requests that logically belong together (e.g., analyzing multiple documents from the same source).
2. **Use webhooks for notifications:** Instead of polling, configure webhooks to be notified when your group completes.
3. **Handle individual failures:** Some requests in a group may fail while others succeed. Check each generation's status.
4. **Stay under limits:** Keep groups to 50 requests or less. For larger batches, use the [Batch API](/api/async-inference/batch-api).
5. **Include metadata:** Add custom IDs or metadata to your requests for easier tracking:
```json JSON
{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [{"role": "user", "content": "..."}],
"metadata": {
"custom_id": "doc_123",
"type": "summary"
}
}
```
## Error Handling [#error-handling]
The API validates your request structure immediately. Common errors include:
```json JSON
{
"error": {
"message": "Invalid request body.",
"type": "BadRequestError",
"fields": {
"_errors": ["Unrecognized key(s) in object: 'webhook_url'"]
}
}
}
```
Ensure you use the correct field names:
* `webhook_id` (correct)
* `webhook_url` (incorrect — this is for the Batch API)
## When to Use Group API [#when-to-use-group-api]
Choose the Group API when you need:
* Quick implementation without file management
* To process 50 or fewer related requests
* Webhook notifications for a set of requests
* Simple JSON-based integration
For larger workloads (50+ requests), consider using the [Batch API](/api/async-inference/batch-api) instead.
# Overview (https://docs.inference.net/api/async-inference/overview)
Learn how to use our OpenAI-compatible Asynchronous Inference API to send individual inference requests that complete within 24 hours at reduced costs. Simply use `/v1/slow` instead of `/v1/` in your API calls to access this feature.
Background inference is cheaper, and easier to build with when your application isn't serving real-time inference.
Asynchronous Inference API is compatible with all the [models](https://inference.net/models) we offer.
Webhook support is available for all slow endpoints: `/chat/completions`, `/completions`, and `/embeddings` calls.
Asynchronous and Batch requests require inference results retention to be enabled for your team. Requests and results are stored until they are delivered, so teams that have disabled retention receive a `403 Forbidden` on all `/v1/slow`, `/v1/batches`, and `/v1/files` upload requests. Enable retention in your team settings, or use the synchronous `/v1` endpoints instead.
## Overview [#overview]
The Asynchronous Inference API provides a simple way to make cost-effective inference requests when immediate responses aren't required. By using the `/v1/slow` prefix instead of `/v1/`, you can:
1. **Get immediate request IDs:** Your API call returns instantly with a unique ID.
2. **Save on costs:** Enjoy significantly cheaper pricing compared to synchronous requests.
3. **Flexible completion:** Requests complete within 24 hours.
4. **Same familiar API:** Uses the exact same request format as our standard endpoints.
This API is perfect for use cases like:
* Large-scale content generation
* Batch document processing
* Non-urgent data analysis
* Cost-sensitive workloads
* Background processing tasks
## Getting Started [#getting-started]
Using the Asynchronous Inference API is as simple as changing your base URL from `/v1/` to `/v1/slow/`. The API maintains full compatibility with the OpenAI SDK.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1/slow", // Note the /v1/slow prefix
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1/slow", # Note the /v1/slow prefix
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL
# Use /v1/slow/ instead of /v1/ in the URL
curl https://api.inference.net/v1/slow/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ ... }'
```
## Making Asynchronous Requests [#making-asynchronous-requests]
### 1. Submit a Request [#1-submit-a-request]
Make requests exactly as you would with the standard API, but responses will include a request ID instead of the completion result:
```typescript TypeScript
const response = await client.chat.completions.create({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is the capital of France?" },
],
max_tokens: 1000,
});
console.log(response.id); // Returns immediately with request ID
```
```python Python
response = client.chat.completions.create(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"},
],
max_tokens=1000,
)
print(response.id) # Returns immediately with request ID
```
```bash cURL
curl https://api.inference.net/v1/slow/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
],
"max_tokens": 1000
}'
```
The initial response will include a unique request ID:
```json JSON
{
"id": "N2mZQjrvh-k_m8nMMN7Jn",
"choices": [],
"created": 1749061362809,
"model": "google/gemma-3-27b-instruct/bf-16",
"object": "chat.completion"
}
```
### 2. Retrieve Results [#2-retrieve-results]
Once your request is processed, retrieve the results using the generation endpoint:
```typescript TypeScript
const response = await fetch(
`https://api.inference.net/v1/generation/${generationId}`,
{
headers: {
Authorization: `Bearer ${process.env.INFERENCE_API_KEY}`,
},
},
);
const result = await response.json();
```
```python Python
import os
import requests
response = requests.get(
f"https://api.inference.net/v1/generation/{generation_id}",
headers={"Authorization": f"Bearer {os.environ['INFERENCE_API_KEY']}"},
)
result = response.json()
```
```bash cURL
curl https://api.inference.net/v1/generation/N2mZQjrvh-k_m8nMMN7Jn \
-H "Authorization: Bearer $INFERENCE_API_KEY"
```
The completed response includes both the original request and the generation result:
```json JSON
{
"request": {
"messages": [
{"content": "You are a helpful assistant.", "role": "system"},
{"content": "What is the meaning of life?", "role": "user"}
],
"model": "google/gemma-3-27b-instruct/bf-16",
"stream": false,
"max_tokens": 8,
"metadata": {"webhook_id": "mPufxRcrw"}
},
"response": {
"id": "N2mZQjrvh-k_m8nMMN7Jn",
"object": "chat.completion",
"created": 1749061362,
"model": "google/gemma-3-27b-instruct/bf-16",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The meaning of life is a complex and",
"reasoning_content": null,
"tool_calls": null
},
"logprobs": null,
"finish_reason": "length",
"matched_stop": null
}
],
"usage": {
"prompt_tokens": 48,
"total_tokens": 56,
"completion_tokens": 8,
"prompt_tokens_details": null
},
"system_fingerprint": ""
},
"state": "Success",
"stateMessage": "Generation successful",
"finishedAt": "2025-06-04T18:22:42.912Z"
}
```
## Request States [#request-states]
Asynchronous requests can have the following states:
| Status | Description |
| ----------- | ------------------------------------------------- |
| Queued | Request received and queued for processing |
| In Progress | Request is currently being processed |
| Success | Request completed successfully, results available |
| Failed | Request failed due to an error |
## Best Practices [#best-practices]
1. **Store Request IDs:** Always save the returned request ID for later retrieval.
2. **Use Webhooks:** Instead of polling, set up webhooks for real-time notifications when requests complete. See our [Getting Started with Webhooks](/api/async-inference/webhooks/getting-started-with-webhooks) guide.
3. **Handle Failures:** Have a fallback plan for requests that fail during processing.
4. **Batch When Possible:** For multiple requests, consider using our [Batch API](/api/async-inference/batch-api) for better organization.
## Supported Endpoints [#supported-endpoints]
The Asynchronous Inference API supports the following endpoints:
* `/v1/slow/chat/completions`
* `/v1/slow/completions`
* `/v1/slow/embeddings`
Simply replace `/v1/` with `/v1/slow/` in your existing code to use asynchronous processing.
## Pricing and Limits [#pricing-and-limits]
* **Pricing:** Significantly reduced compared to synchronous requests (contact sales for specific rates)
* **Completion Time:** 24 hours
* **Rate Limits:** More generous than synchronous endpoints
* **Request Expiration:** Requests expire after 24 hours if not completed
For specific pricing information and higher rate limits, please contact [support@inference.net](mailto:support@inference.net).
# Getting Started With Webhooks (https://docs.inference.net/api/async-inference/webhooks/getting-started-with-webhooks)
Webhook support is available for all slow endpoints: `/chat/completions`, `/completions`, and `/embeddings` calls.
## Overview [#overview]
Webhooks provide an efficient push-based notification system for tracking generation completions in real-time. Rather than repeatedly polling the API to check generation status, webhooks automatically notify your application when generations complete, enabling streamlined workflows and better resource utilization.
## Key Benefits [#key-benefits]
* **Resource Efficiency**: Eliminate unnecessary API calls for status checks
* **Real-time Updates**: Receive notifications within milliseconds of generation completion
* **Scalability**: Handle thousands of concurrent generations efficiently
* **Improved User Experience**: Update your UI instantly when results are ready
## Getting Started [#getting-started]
### Step 1: Create a Webhook Endpoint [#step-1-create-a-webhook-endpoint]
Your application needs an HTTPS endpoint capable of receiving POST requests. The endpoint should:
1. Accept JSON payloads
2. Respond with HTTP 200 status immediately
3. Process the webhook data asynchronously
```typescript TypeScript
import express from "express";
const app = express();
app.post("/webhooks/inference", express.json(), (req, res) => {
const { event, data } = req.body;
// Verify webhook source via headers
const webhookId = req.headers["x-inference-webhook-id"];
if (event === "generation.completed") {
console.log(`Generation ${data.id} completed with status: ${data.state}`);
// Process asynchronously
setImmediate(() => {
processGenerationResult(data);
});
} else if (event === "async-embedding.completed") {
console.log(`Embedding ${data.id} completed with status: ${data.state}`);
setImmediate(() => {
processEmbeddingResult(data);
});
}
// Always respond immediately
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log("Webhook receiver listening on port 3000");
});
```
```python Python
from fastapi import FastAPI, Request, BackgroundTasks
from pydantic import BaseModel
from typing import Optional, Dict, Any
app = FastAPI()
class WebhookPayload(BaseModel):
event: str
timestamp: str
webhookId: str
data: Dict[str, Any]
def process_generation(payload: WebhookPayload):
"""Process generation result asynchronously"""
if payload.event == "generation.completed":
print(f"Processing generation {payload.data['id']}")
# Your processing logic here
elif payload.event == "async-embedding.completed":
print(f"Processing embedding {payload.data['id']}")
# Your embedding processing logic here
@app.post("/webhooks/inference")
async def handle_webhook(
payload: WebhookPayload,
request: Request,
background_tasks: BackgroundTasks,
):
# Verify webhook source
webhook_id = request.headers.get("x-inference-webhook-id")
# Queue for background processing
background_tasks.add_task(process_generation, payload)
return {"received": True}
```
Go Example
```go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type WebhookPayload struct {
Event string `json:"event"`
Timestamp string `json:"timestamp"`
WebhookID string `json:"webhookId"`
Data map[string]any `json:"data"`
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
var payload WebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Verify webhook source
webhookID := r.Header.Get("X-Inference-Webhook-ID")
// Process asynchronously
go func() {
switch payload.Event {
case "generation.completed":
fmt.Printf("Processing generation %s\n", payload.Data["id"])
// Your processing logic here
case "async-embedding.completed":
fmt.Printf("Processing embedding %s\n", payload.Data["id"])
// Your embedding processing logic here
}
}()
// Respond immediately
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhooks/inference", handleWebhook)
fmt.Println("Webhook server listening on :3000")
http.ListenAndServe(":3000", nil)
}
```
### Step 2: Deploy Your Endpoint [#step-2-deploy-your-endpoint]
Your webhook endpoint must be publicly accessible via HTTPS. For development environments, consider using:
* **ngrok**: `ngrok http 3000`
* **Cloudflare Tunnel**: Provides a stable URL
* **localtunnel**: `lt --port 3000`
### Step 3: Register Your Webhook [#step-3-register-your-webhook]
1. Navigate to the inference.net dashboard
2. Go to **Settings** → **Integrations** in the sidebar
3. Click **Create Webhook**
4. Enter a descriptive name and your HTTPS endpoint URL
5. Save your webhook
You'll receive a webhook identifier (e.g., `AhALzdz8S`) that you'll use when creating generations.
### Step 4: Link Webhook to Generations [#step-4-link-webhook-to-generations]
Include the webhook identifier in the metadata when creating a generation:
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1/slow",
apiKey: process.env.INFERENCE_API_KEY,
});
const response = await client.chat.completions.create({
model: "gemma-3-27b-it",
messages: [{ role: "user", content: "Explain quantum computing" }],
// @ts-expect-error metadata is not in the OpenAI SDK types
metadata: {
webhook_id: "AhALzdz8S",
},
});
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1/slow",
api_key=os.environ["INFERENCE_API_KEY"],
)
response = client.chat.completions.create(
model="gemma-3-27b-it",
messages=[{"role": "user", "content": "Explain quantum computing"}],
extra_body={
"metadata": {"webhook_id": "AhALzdz8S"},
},
)
```
```bash cURL
curl https://api.inference.net/v1/slow/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-3-27b-it",
"messages": [
{"role": "user", "content": "Explain quantum computing"}
],
"metadata": {
"webhook_id": "AhALzdz8S"
}
}'
```
When the generation completes, your webhook endpoint will receive a notification.
## Webhook Events [#webhook-events]
### generation.completed [#generationcompleted]
Sent when a generation finishes processing (successfully or with failure):
```json JSON
{
"event": "generation.completed",
"timestamp": "2025-01-03T06:46:22.838Z",
"webhookId": "AhALzdz8S",
"data": {
"id": "XBKcs7F1s2oJ_AHiLMbF4",
"state": "Success",
"stateMessage": "Generation successful",
"request": {
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum computing"}
],
"model": "gemma-3-27b-it",
"stream": false,
"max_tokens": 100,
"metadata": {
"webhook_id": "AhALzdz8S"
}
},
"response": {
"id": "XBKcs7F1s2oJ_AHiLMbF4",
"object": "chat.completion",
"created": 1748933182,
"model": "gemma-3-27b-it",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Quantum computing is a revolutionary approach..."
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 42,
"total_tokens": 70
}
},
"model": "gemma-3-27b-it",
"createdAt": "2025-01-03T06:46:18.000Z",
"dispatchedAt": "2025-01-03T06:46:19.100Z",
"firstChunkAt": "2025-01-03T06:46:20.200Z",
"finishedAt": "2025-01-03T06:46:22.307Z",
"usage": {
"prompt_tokens": 28,
"completion_tokens": 42,
"total_tokens": 70
}
}
}
```
The generation ID is the `data.id` field. The `response` object is compatible with the types exported from the official OpenAI SDKs.
```typescript TypeScript
import type { OpenAI } from "openai";
const response = responseJsonObject as OpenAI.Chat.Completions.ChatCompletion;
```
```python Python
from openai.types.chat.chat_completion import ChatCompletion
response: ChatCompletion = webhook_payload.data["response"]
```
### async-embedding.completed [#async-embeddingcompleted]
Sent when an async embedding request finishes processing:
```json JSON
{
"event": "async-embedding.completed",
"timestamp": "2025-01-15T10:30:00Z",
"webhookId": "AhALzdz8S",
"data": {
"id": "EMB_abc123",
"state": "Success",
"stateMessage": "Embeddings generated successfully",
"request": {
"model": "qwen/qwen3-embedding-4b",
"input": ["text1", "text2"],
"metadata": { "webhook_id": "AhALzdz8S" }
},
"response": {
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0023064255, -0.009327292]
}
],
"model": "qwen/qwen3-embedding-4b",
"usage": {
"prompt_tokens": 100,
"total_tokens": 100
}
},
"model": "qwen/qwen3-embedding-4b",
"createdAt": "2025-01-15T10:29:55Z",
"dispatchedAt": "2025-01-15T10:29:56Z",
"firstChunkAt": "2025-01-15T10:29:58Z",
"finishedAt": "2025-01-15T10:30:00Z",
"usage": {
"prompt_tokens": 100,
"total_tokens": 100
}
}
}
```
The generation ID is the `data.id` field. The `response` object follows the standard OpenAI embeddings format.
## Headers [#headers]
All webhook requests include the following headers:
| Header | Description | Example |
| --------------------------- | ------------------------------------- | ----------------------- |
| `X-Inference-Event` | Event type | `generation.completed` |
| `X-Inference-Webhook-ID` | Webhook identifier | `AhALzdz8S` |
| `X-Inference-Generation-ID` | Generation ID (for generation events) | `XBKcs7F1s2oJ_AHiLMbF4` |
| `X-Inference-Embedding-ID` | Generation ID (for embedding events) | `EMB_abc123` |
| `User-Agent` | inference.net webhook agent | `Kuzco-Webhook/1.0` |
| `Content-Type` | Always `application/json` | `application/json` |
Before each delivery, the service also sends an `OPTIONS` preflight request to your endpoint with the same `X-Inference-*` headers. The preflight must return a 2xx status or the delivery is retried.
### (Security) Verifying the request source [#security-verifying-the-request-source]
The `X-Inference-Webhook-ID` is a good way to verify that the payload you're receiving is officially coming from our API.
This ID is unique to your webhook, and is completely private to you and your team.
If the ID does not match what you see in the dashboard, your endpoint has most likely been discovered by a malicious actor.
## Testing Webhooks [#testing-webhooks]
You can test your webhook endpoint from the dashboard:
1. Navigate to **Settings** → **Integrations** in the dashboard
2. Find your webhook in the list
3. Click the menu and select **Test**
4. Check your endpoint logs for the test payload
A successful test will show a green success indicator in the dashboard.
## Best Practices [#best-practices]
### 1. Respond Immediately [#1-respond-immediately]
Your endpoint must respond within 60 seconds. Always return a 200 status immediately and process the webhook asynchronously:
```typescript TypeScript
// Correct approach
app.post("/webhook", (req, res) => {
res.status(200).send("OK");
processWebhookAsync(req.body);
});
// Incorrect approach — may timeout
app.post("/webhook", async (req, res) => {
await heavyProcessing(req.body); // Risk of timeout
res.status(200).send("OK");
});
```
```python Python
# Correct approach — process in background
@app.post("/webhook")
async def handle_webhook(
payload: WebhookPayload,
background_tasks: BackgroundTasks,
):
background_tasks.add_task(process_webhook, payload)
return {"received": True}
# Incorrect approach — may timeout
@app.post("/webhook")
async def handle_webhook(payload: WebhookPayload):
await heavy_processing(payload) # Risk of timeout
return {"received": True}
```
### 2. Implement Idempotency [#2-implement-idempotency]
Failed webhooks may be retried. Use `data.id` to ensure you don't process the same event twice:
```typescript TypeScript
const processedGenerations = new Set();
async function processWebhook(payload: any) {
const generationId = payload.data.id;
if (processedGenerations.has(generationId)) {
return; // Already processed
}
processedGenerations.add(generationId);
// Process the generation
}
```
```python Python
processed_generations: set[str] = set()
def process_webhook(payload: WebhookPayload):
generation_id = payload.data["id"]
if generation_id in processed_generations:
return # Already processed
processed_generations.add(generation_id)
# Process the generation
```
For `slow-group.completed` events, use the top-level `groupId` field for idempotency.
### 3. Validate Webhook Source [#3-validate-webhook-source]
Always verify that webhooks originate from inference.net by checking the presence of expected headers:
```typescript TypeScript
function validateWebhookSource(headers: Record): boolean {
const requiredHeaders = ["x-inference-webhook-id", "x-inference-event"];
return requiredHeaders.every((header) => headers[header]);
}
```
```python Python
def validate_webhook_source(headers: dict) -> bool:
required_headers = ["x-inference-webhook-id", "x-inference-event"]
return all(headers.get(h) for h in required_headers)
```
### 4. Handle Errors Gracefully [#4-handle-errors-gracefully]
Implement proper error handling to prevent individual failures from affecting your entire system:
```typescript TypeScript
async function handleWebhook(payload: any) {
try {
await processWebhook(payload);
} catch (error) {
console.error("Webhook processing failed:", error);
// Log to monitoring service
// Return 200 to prevent unnecessary retries
}
}
```
```python Python
async def handle_webhook(payload: WebhookPayload):
try:
await process_webhook(payload)
except Exception as error:
print(f"Webhook processing failed: {error}")
# Log to monitoring service
# Return 200 to prevent unnecessary retries
```
### 5. Monitor Webhook Processing [#5-monitor-webhook-processing]
Track key metrics to ensure reliable webhook processing:
* Webhook receipt rate
* Processing success/failure rates
* Average processing time
* Queue depth (if using queues)
## Troubleshooting [#troubleshooting]
### Not Receiving Webhooks [#not-receiving-webhooks]
1. **Check webhook status**: Ensure your webhook is not disabled in the dashboard
2. **Test connectivity**: Use the test feature in the dashboard
3. **Verify URL**: Confirm your endpoint is publicly accessible via HTTPS
4. **Check logs**: Review both your server logs and any reverse proxy logs
5. **Validate metadata**: Ensure you're including the correct `webhook_id` in generation requests
### Webhooks Arriving Late [#webhooks-arriving-late]
* Verify your endpoint responds quickly (\< 1 second ideally)
* Check that you're not performing heavy processing before responding
* Monitor your server load and resource usage
### Duplicate Webhook Deliveries [#duplicate-webhook-deliveries]
* Implement idempotency using `data.id` (or `groupId` for group events)
* Ensure your endpoint always returns 200 OK for successful receipt
* Check for any errors in your webhook processing that might trigger retries
## Frequently Asked Questions [#frequently-asked-questions]
**Q: What happens if my endpoint is down?**
A: Failed webhook deliveries are retried up to 1200 times at fixed intervals — 30 seconds for generation events and 5 minutes for group events. After all attempts are exhausted, the delivery is marked as failed.
**Q: What's the webhook timeout?**
A: Webhook endpoints must respond within 60 seconds. A timeout is treated as a failure and will trigger retries.
**Q: Can I filter which events I receive?**
A: Currently, webhooks receive all event types. Event filtering is planned for a future update.
**Q: How secure are webhooks?**
A: All webhooks are sent over HTTPS. You should validate the webhook source using the provided headers. HMAC signature verification is planned for additional security.
**Q: What's the maximum payload size?**
A: Payloads scale with the size of the generation they describe — a payload is larger for generations with long requests or responses. There is no fixed size limit.
**Q: Can I replay missed webhooks?**
A: Webhook replay functionality is not currently available. As a fallback, you can poll the generation status endpoint.
## Support [#support]
For assistance with webhooks:
* Email: [support@inference.net](mailto:support@inference.net)
* Discord: Join our developer community
* Documentation: [https://docs.inference.net](https://docs.inference.net)
* Issues: Report bugs via our support portal
# Webhooks: Quick Reference (https://docs.inference.net/api/async-inference/webhooks/quick-reference)
## Dashboard Management [#dashboard-management]
Webhooks are managed through the inference.net dashboard:
1. Navigate to **Settings** → **Integrations**
2. Create, test, archive, or restore webhooks through the UI
3. Copy your webhook identifier for use in generation requests
## Payload Structures [#payload-structures]
### generation.completed [#generationcompleted]
```json JSON
{
"event": "generation.completed",
"timestamp": "ISO 8601 timestamp",
"webhookId": "webhook identifier",
"data": {
"id": "generation ID",
"state": "Success|Failed|Queued|In Progress",
"stateMessage": "Human readable status",
"request": { /* Original request or null */ },
"response": { /* OpenAI format response or null */ },
"model": "model ID",
"createdAt": "ISO 8601 timestamp",
"dispatchedAt": "ISO 8601 timestamp or null",
"firstChunkAt": "ISO 8601 timestamp or null",
"finishedAt": "ISO 8601 timestamp or null",
"usage": { /* Token usage */ }
}
}
```
The generation ID is `data.id`. The `response` object is compatible with the OpenAI SDK types.
### async-embedding.completed [#async-embeddingcompleted]
```json JSON
{
"event": "async-embedding.completed",
"timestamp": "ISO 8601 timestamp",
"webhookId": "webhook identifier",
"data": {
"id": "generation ID",
"state": "Success|Failed|Queued|In Progress",
"stateMessage": "Human readable status",
"request": { /* Original embeddings request or null */ },
"response": { /* OpenAI format embeddings response or null */ },
"model": "model ID",
"createdAt": "ISO 8601 timestamp",
"dispatchedAt": "ISO 8601 timestamp or null",
"firstChunkAt": "ISO 8601 timestamp or null",
"finishedAt": "ISO 8601 timestamp or null",
"usage": { /* Token usage */ }
}
}
```
### slow-group.completed [#slow-groupcompleted]
```json JSON
{
"event": "slow-group.completed",
"timestamp": "ISO 8601 timestamp",
"groupId": "group ID",
"data": {
"groupSize": 2,
"status": "completed|failed",
"generations": [
{
"id": "generation ID",
"state": "Success|Failed|Queued|In Progress",
"stateMessage": "Human readable status",
"request": { /* Original request or null */ },
"response": { /* OpenAI format response or null */ },
"model": "model ID",
"createdAt": "ISO 8601 timestamp",
"dispatchedAt": "ISO 8601 timestamp or null",
"firstChunkAt": "ISO 8601 timestamp or null",
"finishedAt": "ISO 8601 timestamp or null",
"usage": { /* Token usage */ }
}
]
}
}
```
### webhook.test (dashboard Test button) [#webhooktest-dashboard-test-button]
```json JSON
{
"event": "webhook.test",
"timestamp": "ISO 8601 timestamp",
"webhook_id": "webhook identifier",
"data": {
"message": "test message"
}
}
```
Note that this test payload uses the legacy `webhook_id` field (snake\_case), unlike delivery payloads which use `webhookId`.
## Headers [#headers]
| Header | Description | Example |
| --------------------------- | --------------------------------- | ------------------------------------------------------------------------------ |
| `X-Inference-Event` | Event type | `generation.completed`, `async-embedding.completed`, or `slow-group.completed` |
| `X-Inference-Webhook-ID` | Webhook identifier | `AhALzdz8S` |
| `X-Inference-Generation-ID` | Generation ID (generation events) | `XBKcs7F1s2oJ_AHiLMbF4` |
| `X-Inference-Embedding-ID` | Generation ID (embedding events) | `XBKcs7F1s2oJ_AHiLMbF4` |
| `X-Inference-Group-ID` | Group ID (group events) | `GRP_XYZ123` |
| `User-Agent` | inference.net webhook agent | `Kuzco-Webhook/1.0` |
| `Content-Type` | Always `application/json` | `application/json` |
## Using Webhooks in Generations [#using-webhooks-in-generations]
Include the webhook identifier in your generation request metadata:
### Chat Completions [#chat-completions]
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1/slow",
apiKey: process.env.INFERENCE_API_KEY,
});
const response = await client.chat.completions.create({
model: "gemma-3-27b-it",
messages: [{ role: "user", content: "Hello!" }],
// @ts-expect-error metadata is not in the OpenAI SDK types
metadata: { webhook_id: "YOUR_WEBHOOK_IDENTIFIER" },
});
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1/slow",
api_key=os.environ["INFERENCE_API_KEY"],
)
response = client.chat.completions.create(
model="gemma-3-27b-it",
messages=[{"role": "user", "content": "Hello!"}],
extra_body={"metadata": {"webhook_id": "YOUR_WEBHOOK_IDENTIFIER"}},
)
```
```bash cURL
curl https://api.inference.net/v1/slow/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-3-27b-it",
"messages": [{"role": "user", "content": "Hello!"}],
"metadata": {"webhook_id": "YOUR_WEBHOOK_IDENTIFIER"}
}'
```
### Embeddings [#embeddings]
```typescript TypeScript
const embeddingResponse = await fetch(
"https://api.inference.net/v1/async/embeddings",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFERENCE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "qwen/qwen3-embedding-4b",
input: ["Text to embed", "Another text to embed"],
metadata: { webhook_id: "YOUR_WEBHOOK_IDENTIFIER" },
}),
},
);
```
```python Python
import os
import requests
response = requests.post(
"https://api.inference.net/v1/async/embeddings",
headers={
"Authorization": f"Bearer {os.environ['INFERENCE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "qwen/qwen3-embedding-4b",
"input": ["Text to embed", "Another text to embed"],
"metadata": {"webhook_id": "YOUR_WEBHOOK_IDENTIFIER"},
},
)
```
```bash cURL
curl https://api.inference.net/v1/async/embeddings \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen3-embedding-4b",
"input": ["Text to embed", "Another text to embed"],
"metadata": {"webhook_id": "YOUR_WEBHOOK_IDENTIFIER"}
}'
```
## Minimal Webhook Handler Examples [#minimal-webhook-handler-examples]
```typescript TypeScript
app.post("/webhook", express.json(), (req, res) => {
res.status(200).json({ received: true });
if (req.body.event === "generation.completed") {
setImmediate(() => {
console.log("Generation completed:", req.body.data.id);
// Your processing logic here
});
} else if (req.body.event === "async-embedding.completed") {
setImmediate(() => {
console.log("Embedding completed:", req.body.data.id);
console.log("Number of embeddings:", req.body.data.response.data.length);
});
} else if (req.body.event === "slow-group.completed") {
setImmediate(() => {
console.log("Group completed:", req.body.groupId);
console.log("Group size:", req.body.data.groupSize);
req.body.data.generations.forEach((gen: any) => {
console.log(`Generation ${gen.id}: ${gen.state}`);
});
});
}
});
```
```python Python
@app.post("/webhook")
async def handle_webhook(payload: dict, background_tasks: BackgroundTasks):
background_tasks.add_task(process_webhook, payload)
return {"received": True}
def process_webhook(payload):
if payload["event"] == "generation.completed":
print(f"Processing generation {payload['data']['id']}")
# Your processing logic here
elif payload["event"] == "async-embedding.completed":
print(f"Processing embedding {payload['data']['id']}")
print(f"Number of embeddings: {len(payload['data']['response']['data'])}")
elif payload["event"] == "slow-group.completed":
print(f"Processing group {payload['groupId']}")
print(f"Group size: {payload['data']['groupSize']}")
for gen in payload["data"]["generations"]:
print(f"Generation {gen['id']}: {gen['state']}")
```
## Timing & Limits [#timing--limits]
| Metric | Value | Notes |
| ------------------ | ------------------------ | ---------------------------------------- |
| Response timeout | 60 seconds | POST and OPTIONS must respond in time |
| Generation retries | Up to 1200 | Fixed 30-second intervals |
| Group retries | Up to 1200 | Fixed 5-minute intervals |
| Max payload size | Large payloads supported | Payload size scales with generation size |
Retries are triggered on a non-2xx response or a timeout. Each delivery is preceded by an `OPTIONS` preflight request; if the preflight does not return a 2xx, the delivery is retried rather than skipped.
## Response Codes [#response-codes]
| Code | Meaning | Retry? |
| ------- | ------------------ | ------ |
| 200-299 | Success | No |
| 400-599 | Non-success status | Yes |
| Timeout | No response in 60s | Yes |
Any response outside the 200-299 range is treated as a failed delivery and retried.
## Best Practices Checklist [#best-practices-checklist]
* [ ] Respond with 200 OK immediately
* [ ] Process webhook data asynchronously
* [ ] Implement idempotency with `data.id` (generation events) or `groupId` (group events)
* [ ] Validate webhook source via headers
* [ ] Handle errors gracefully
* [ ] Monitor webhook processing
* [ ] Use HTTPS endpoint
* [ ] Set up proper error logging
* [ ] Test webhook with dashboard test feature
* [ ] Implement timeout handling
* [ ] Handle both individual and group completions
## Common Issues & Solutions [#common-issues--solutions]
| Issue | Solution |
| ---------------------- | ---------------------------------------------------------------------------- |
| Not receiving webhooks | Check webhook not disabled in dashboard, test connectivity, verify HTTPS URL |
| Duplicate webhooks | Implement idempotency, ensure 200 OK response |
| Webhooks timing out | Respond immediately, process asynchronously |
| Invalid payload | Validate against documented schema |
| Test webhook fails | Check endpoint is publicly accessible, returns 200 OK |
## Support Resources [#support-resources]
* [Full Documentation](https://docs.inference.net)
* [API Reference](https://docs.inference.net/api)
* [Support](mailto:support@inference.net)
* Discord Community
# Data Retention (https://docs.inference.net/api/data-retention)
Inference.net is designed to support production workloads without treating captured request data casually.
## Core principles [#core-principles]
* Request data is not used for model training by default
* Secrets and similar sensitive values are stripped where possible
* Platform data is encrypted in transit and at rest
* Retention should match the operational need of the workflow
## Direct API vs Gateway [#direct-api-vs-gateway]
The direct API and Gateway are different product paths, but the same general rule applies: only keep what is operationally useful, and use project-level controls and data curation intentionally.
For the workflow-first entry point into traffic capture, start with [Integrate with Your LLM Provider](/platform/gateway/integrate).
## Recommended operational pattern [#recommended-operational-pattern]
* Use environments and task IDs to segment traffic
* Create long-lived datasets only for the examples you want to preserve
* Review retention expectations before broad production rollout
## Retention and asynchronous inference [#retention-and-asynchronous-inference]
The [Asynchronous](/api/async-inference/overview) and Batch APIs store requests and results until they are delivered, so they are only available to teams with inference results retention enabled. Teams that have disabled retention receive a `403 Forbidden` on `/v1/slow`, `/v1/batches`, and `/v1/files` upload requests, and should use the synchronous `/v1` endpoints.
## Need a specific retention policy? [#need-a-specific-retention-policy]
If you need a specific policy, no-retention handling, or help mapping the platform into your internal compliance requirements, [meet with our team](https://inference.net/meet-with-us/).
# Function Calling (https://docs.inference.net/api/function-calling)
## Introduction [#introduction]
**Function calling** provides a powerful and flexible way for models to interface with your code or external services, and has two primary use cases:
| | |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fetching Data | Retrieve up-to-date information to incorporate into the model's response (RAG). Useful for searching knowledge bases and retrieving specific data from APIs (e.g. current weather data). |
| Taking Action | Perform actions like submitting a form, calling APIs, modifying application state (UI/frontend or backend), or taking agentic workflow actions (like handing off the conversation). |
If you only want the model to produce JSON, see our docs on [structured outputs](/api/structured-outputs).
## Getting Started [#getting-started]
You'll need an Inference.net account and API key to use Function Calling. See our [Quick Start Guide](/api/api-quickstart) for instructions on how to create an account and get an API key.
Install the [OpenAI SDK](https://platform.openai.com/docs/libraries) for your language of choice.
To connect to Inference.net using the OpenAI SDK, you will need to set the base URL to `https://api.inference.net/v1`.
In this example, we are reading the API key from the environment variable `INFERENCE_API_KEY`.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL
export INFERENCE_API_KEY=
# All requests use:
# -H "Authorization: Bearer $INFERENCE_API_KEY"
# -H "Content-Type: application/json"
```
## Overview [#overview]
You can extend the capabilities of models by giving them access to functions that you define called `tools`. This is also called "function calling".
With function calling, you'll tell the model what tools are available to it, and it will decide which one to use.
You'll then execute the function code, send back the results, and the model will incorporate them into its final response.
### Sample function [#sample-function]
Let's look at the steps to allow a model to use a real `get_weather` function defined below:
```typescript TypeScript
async function getWeather(latitude: number, longitude: number) {
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m`
);
const data = await response.json();
return data.current.temperature_2m;
}
```
```python Python
import requests
def get_weather(latitude, longitude):
response = requests.get(
f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}"
f"¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m"
)
data = response.json()
return data["current"]["temperature_2m"]
```
All functions must return strings. You can format the string as JSON or another format if you like, but the return type itself must be a string.
Unlike the diagram earlier, this function expects precise `latitude` and `longitude` instead of a general `location` parameter.
### Step By Step Example [#step-by-step-example]
#### Step 1: Call model with get\_weather tool defined [#step-1-call-model-with-get_weather-tool-defined]
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const tools: OpenAI.ChatCompletionTool[] = [{
type: "function",
function: {
name: "get_weather",
description: "Get current temperature for provided coordinates in celsius.",
parameters: {
type: "object",
properties: {
lat: { type: "number" },
lon: { type: "number" },
},
required: ["lat", "lon"],
additionalProperties: false,
},
strict: true,
},
}];
const messages: OpenAI.ChatCompletionMessageParam[] = [
{
role: "system",
content: "You are a helpful assistant that can answer questions and uses tools to get information.",
},
{
role: "user",
content: "What's the weather like in Paris today?",
},
];
const completion = await client.chat.completions.create({
model: "gemma-3-27b-it",
messages,
tools,
});
```
```python Python
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current temperature for provided coordinates in celsius.",
"parameters": {
"type": "object",
"properties": {
"lat": {"type": "number"},
"lon": {"type": "number"},
},
"required": ["lat", "lon"],
"additionalProperties": False,
},
"strict": True,
},
}]
messages = [
{"role": "system", "content": "You are a helpful assistant that can answer questions and uses tools to get information."},
{"role": "user", "content": "What's the weather like in Paris today?"},
]
completion = client.chat.completions.create(
model="gemma-3-27b-it",
messages=messages,
tools=tools,
)
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-3-27b-it",
"messages": [
{"role": "system", "content": "You are a helpful assistant that can answer questions and uses tools to get information."},
{"role": "user", "content": "What'\''s the weather like in Paris today?"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current temperature for provided coordinates in celsius.",
"parameters": {
"type": "object",
"properties": {
"lat": { "type": "number" },
"lon": { "type": "number" }
},
"required": ["lat", "lon"],
"additionalProperties": false
},
"strict": true
}
}]
}'
```
Less powerful models may not reliably respond with tool calls, and may not provide all requested parameters.
Experiment with system prompts and other models to find the best results.
#### Step 2: Pull the selected function call from the model's response [#step-2-pull-the-selected-function-call-from-the-models-response]
```json JSON
[
{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"lat\":48.8566,\"lon\":2.3522}"
}
}
]
```
#### Step 3: Execute the `get_weather` function [#step-3-execute-the-get_weather-function]
```typescript TypeScript
const toolCall = completion.choices[0].message.tool_calls[0];
const args = JSON.parse(toolCall.function.arguments);
const result = await getWeather(args.lat, args.lon);
```
```python Python
tool_call = completion.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = get_weather(args["lat"], args["lon"])
```
#### Step 4: Supply result and call model again [#step-4-supply-result-and-call-model-again]
```typescript TypeScript
messages.push(completion.choices[0].message); // append model's function call message
messages.push({ // append result message
role: "tool",
tool_call_id: toolCall.id,
content: result.toString(),
});
const completion2 = await client.chat.completions.create({
model: "gemma-3-27b-it",
messages,
tools,
});
console.log(completion2.choices[0].message.content);
```
```python Python
messages.append(completion.choices[0].message) # append model's function call message
messages.append({ # append result message
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result),
})
completion_2 = client.chat.completions.create(
model="gemma-3-27b-it",
messages=messages,
tools=tools,
)
print(completion_2.choices[0].message.content)
```
#### Output [#output]
```json JSON
"The current temperature in Paris is 14°C (57.2°F)."
```
## Defining functions [#defining-functions]
Functions can be set in the `tools` parameter of each API request.
A function is defined by its schema, which informs the model what it does and what input arguments it expects. It comprises the following fields:
| Field | Description |
| ----------- | --------------------------------------------------- |
| name | The function's name (e.g. get\_weather) |
| description | Details on when and how to use the function |
| parameters | JSON schema defining the function's input arguments |
Take a look at this example:
```json JSON
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Retrieves current weather for the given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country e.g. Bogotá, Colombia"
},
"units": {
"type": "string",
"enum": [
"celsius",
"fahrenheit"
],
"description": "Units the temperature will be returned in."
}
},
"required": [
"location",
"units"
],
"additionalProperties": false
},
"strict": true
}
}
```
Because the `parameters` are defined by a [JSON schema](https://json-schema.org/), you can leverage many of its rich features like property types, enums, and descriptions.
### SDK Helpers [#sdk-helpers]
While you can define function schemas directly, [OpenAI's SDKs](https://platform.openai.com/docs/libraries) have helpers to convert `pydantic` and `zod` objects into schemas.
Not all `pydantic` and `zod` features are currently supported by Function Calling, but simple, flat schemas are supported.
Here is an example of how to use the SDK to define a schema.
```typescript TypeScript
import OpenAI from "openai";
import { z } from "zod";
import { zodFunction } from "openai/helpers/zod";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const GetWeatherParameters = z.object({
location: z.string().describe("City and country e.g. Bogotá, Colombia"),
});
const tools = [
zodFunction({ name: "getWeather", parameters: GetWeatherParameters }),
];
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: "user", content: "What's the weather like in Paris today?" },
];
const response = await client.chat.completions.create({
model: "gemma-3-27b-it",
messages,
tools,
});
console.log(response.choices[0].message.tool_calls);
```
```python Python
import os
from openai import OpenAI, pydantic_function_tool
from pydantic import BaseModel, Field
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
class GetWeather(BaseModel):
location: str = Field(
...,
description="City and country e.g. Bogotá, Colombia",
)
tools = [pydantic_function_tool(GetWeather)]
completion = client.chat.completions.create(
model="gemma-3-27b-it",
messages=[
{"role": "system", "content": "You are a helpful assistant that can answer questions and help with tasks."},
{"role": "user", "content": "What's the weather like in Paris today?"},
],
tools=tools,
)
print(completion.choices[0].message.tool_calls)
```
### Best practices for defining functions [#best-practices-for-defining-functions]
1. **Write clear and detailed function names, parameter descriptions, and instructions.**
* **Explicitly describe the purpose of the function and each parameter** (and its format), and what the output represents.
* **Use the system prompt to describe when (and when not) to use each function.** Generally, tell the model *exactly* what to do.
* **Include examples and edge cases**, especially to rectify any recurring failures.
2. **Apply software engineering best practices.**
* **Make the functions obvious and intuitive**. ([principle of least surprise](https://en.wikipedia.org/wiki/Principle_of_least_astonishment))
* **Use enums** and object structure to make invalid states unrepresentable. (e.g. `toggle_light(on: bool, off: bool)` allows for invalid calls)
* **Pass the intern test.** Can an intern/human correctly use the function given nothing but what you gave the model? (If not, what questions do they ask you? Add the answers to the prompt.)
3. **Offload the burden from the model and use code where possible.**
* **Don't make the model fill arguments you already know.** For example, if you already have an `order_id` based on a previous menu, don't have an `order_id` param – instead, have no params `submit_refund()` and pass the `order_id` with code.
* **Combine functions that are always called in sequence.** For example, if you always call `mark_location()` after `query_location()`, just move the marking logic into the query function call.
4. **Keep the number of functions small for higher accuracy.**
* **Evaluate your performance** with different numbers of functions.
* **Aim for fewer than 20 functions** at any one time, though this is just a soft suggestion.
## Streaming [#streaming]
Streaming can be used to surface progress by showing which function is called as the model fills its arguments, and even displaying the arguments in real time.
Streaming function calls is very similar to streaming regular responses: you set `stream` to `true` and get chunks with `delta` objects.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const tools: OpenAI.ChatCompletionTool[] = [{
type: "function",
function: {
name: "get_weather",
description: "Get current temperature for a given location.",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City and country e.g. Bogotá, Colombia",
},
},
required: ["location"],
additionalProperties: false,
},
strict: true,
},
}];
const stream = await client.chat.completions.create({
model: "gemma-3-27b-it",
messages: [{ role: "user", content: "What's the weather like in Paris today?" }],
tools,
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0].delta;
console.log(delta.tool_calls);
}
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current temperature for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country e.g. Bogotá, Colombia",
},
},
"required": ["location"],
"additionalProperties": False,
},
"strict": True,
},
}]
stream = client.chat.completions.create(
model="gemma-3-27b-it",
messages=[{"role": "user", "content": "What's the weather like in Paris today?"}],
tools=tools,
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta
print(delta.tool_calls)
```
```bash cURL
curl -N https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-3-27b-it",
"stream": true,
"messages": [
{"role": "user", "content": "What'\''s the weather like in Paris today?"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current temperature for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country e.g. Bogotá, Colombia"
}
},
"required": ["location"],
"additionalProperties": false
},
"strict": true
}
}]
}'
```
Output of `delta.tool_calls`:
```txt TXT
[{"index": 0, "id": "call_DdmO9pD3xa9XTPNJ32zg2hcA", "function": {"arguments": "", "name": "get_weather"}, "type": "function"}]
[{"index": 0, "id": null, "function": {"arguments": "{\"", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "location", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "\":\"", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "Paris", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": ",", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": " France", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "\"}", "name": null}, "type": null}]
null
```
Instead of aggregating chunks into a single `content` string, however, you're aggregating chunks into an encoded `arguments` JSON object.
When the model calls one or more functions the `tool_calls` field of each `delta` will be populated. Each `tool_call` contains the following fields:
| Field | Description |
| -------- | ------------------------------------------------------- |
| index | Identifies which function call the delta is for |
| id | Tool call id. |
| function | Function call delta (name and arguments) |
| type | Type of tool\_call (always function for function calls) |
Many of these fields are only set for the first `delta` of each tool call, like `id`, `function.name`, and `type`.
Below is a code snippet demonstrating how to aggregate the `delta` objects into a final `tool_calls` object.
```typescript TypeScript
const finalToolCalls: Record = {};
for await (const chunk of stream) {
const toolCalls = chunk.choices[0].delta.tool_calls || [];
for (const toolCall of toolCalls) {
const { index } = toolCall;
if (!finalToolCalls[index]) {
finalToolCalls[index] = toolCall;
}
finalToolCalls[index].function.arguments += toolCall.function.arguments;
}
}
```
```python Python
final_tool_calls = {}
for chunk in stream:
for tool_call in chunk.choices[0].delta.tool_calls or []:
index = tool_call.index
if index not in final_tool_calls:
final_tool_calls[index] = tool_call
final_tool_calls[index].function.arguments += tool_call.function.arguments
```
Accumulated final\_tool\_calls\[0]
```json JSON
{
"index": 0,
"id": "call_RzfkBpJgzeR0S242qfvjadNe",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}
```
# Rate Limits (https://docs.inference.net/api/rate-limits)
## Generation Rate Limits [#generation-rate-limits]
Rate limits for model inference requests are based on your account tier:
| Tier | Requests per minute (RPM) |
| ------------------------- | ----------------------------------- |
| Free | 30 |
| Paid (Growth, Enterprise) | 1,000 |
| Custom teams | Operator-managed per-team overrides |
Free-tier teams granted free credits by an operator are raised to **200 RPM** for the duration of the grant. The pay-as-you-go credit-purchase floor is **250 RPM** — a real purchase always buys more headroom than a free grant. Per-team overrides for custom teams are configured by an operator; contact us to request one.
## Batch API Rate Limits [#batch-api-rate-limits]
* **Batch file upload:** 1 per minute
* Batch processing rate limits are separate from generation rate limits. See the [Batch API](/api/async-inference/batch-api) docs for details.
Deployed models share the team's serverless inference RPM bucket rather than having their own per-instance limit.
## Increasing Your Limits [#increasing-your-limits]
If you need higher rate limits, [contact us](mailto:support@inference.net) or use the support chat to request a custom tier.
# Reasoning (https://docs.inference.net/api/reasoning)
Reasoning models think through a problem before they answer. The `reasoning_effort` parameter controls how much thinking a model does. Higher effort improves quality on hard problems and uses more tokens and increases latency.
## Set a reasoning effort [#set-a-reasoning-effort]
Pass `reasoning_effort` on a chat completion request:
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const response = await client.chat.completions.create({
model: "claude-opus-5",
max_tokens: 20000,
reasoning_effort: "high",
messages: [{ role: "user", content: "Prove that sqrt(2) is irrational." }],
});
console.log(response.choices[0].message.content);
```
```python Python
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
response = client.chat.completions.create(
model="claude-opus-5",
max_tokens=20000,
reasoning_effort="high",
messages=[{"role": "user", "content": "Prove that sqrt(2) is irrational."}],
)
print(response.choices[0].message.content)
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 20000,
"reasoning_effort": "high",
"messages": [{"role": "user", "content": "Prove that sqrt(2) is irrational."}]
}'
```
## Effort levels [#effort-levels]
The full set of levels is `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. `none` disables reasoning. Each model supports a subset:
| Model | Supported levels |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `gpt-5.2`, `gpt-5.4`, `gpt-5.5`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5-mini`, `gpt-5-nano` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` |
| `claude-fable-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-haiku-4-5` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` |
| `claude-opus-4-6`, `claude-sonnet-4-6` | `none`, `minimal`, `low`, `medium`, `high`, `max` |
| `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-3.6-flash`, `glm-5`, `glm-5.2`, `glm-5.2-fast` | `none`, `low`, `medium`, `high` |
| `glm-5.3-flash`, `kimi-k3` | `none`, `low`, `high`, `max` |
| `gemini-2.5-pro` | `minimal`, `low`, `medium`, `high` |
| `gemini-3.1-pro-preview`, `gemini-3.7-flash`, `kimi-k2.5`, `kimi-k2.6`, `kimi-k3-fast`, `nemotron-3-super`, `qwen3-8b`, `qwen3-14b`, `qwen3-30b-a3b`, `qwen3-max-thinking`, `qwen3.7-plus` | `low`, `medium`, `high` |
| `glm-5.3` | `low`, `high`, `max` |
| `deepseek-v4-flash`, `deepseek-v4-flash-0731`, `deepseek-v4-pro`, `deepseek-v4-pro-0813` | `low`, `high`, `max` |
| `grok-4.5`, `grok-4.6` | `low`, `medium`, `high` |
A model absent from this table (or from `reasoning_efforts` in `GET /v1/models`) has no first-party effort dial: the gateway forwards `reasoning_effort` for it, but the platform does not validate or guarantee behavior — several models accept the parameter and silently ignore it (`gpt-4o`, `gpt-4.1`).
You can also discover support programmatically. `GET /v1/models` returns `context_length` and `max_completion_tokens` when the platform knows them, and a `reasoning_efforts` array when the catalog declares the model's levels:
```json
{
"id": "claude-opus-5",
"object": "model",
"owned_by": "system",
"context_length": 1000000,
"max_completion_tokens": 128000,
"reasoning_efforts": ["none", "minimal", "low", "medium", "high", "xhigh", "max"]
}
```
On OpenAI and Anthropic models, the API rejects an unsupported level before dispatch with a 400 that names the valid values. Other models forward the request, and the serving engine decides:
```json
{
"error": {
"message": "reasoning_effort 'xhigh' is not supported by claude-sonnet-4-6. Supported: none, minimal, low, medium, high, max.",
"type": "invalid_request_error",
"param": "reasoning_effort",
"code": "unsupported_value"
}
}
```
`gpt-4o` and `gpt-4.1` accept `reasoning_effort` and ignore it. They are not reasoning models, so `usage.completion_tokens_details.reasoning_tokens` is always 0.
## Anthropic models: max\_tokens must exceed the thinking budget [#anthropic-models-max_tokens-must-exceed-the-thinking-budget]
On Anthropic models, each effort level reserves a thinking budget, and that budget counts toward `max_tokens`. Your `max_tokens` (or `max_completion_tokens`) must be strictly greater than the budget for the level you request:
| Effort | Thinking budget (tokens) |
| --------- | ------------------------ |
| `none` | 0 |
| `minimal` | 1,024 |
| `low` | 1,024 |
| `medium` | 2,048 |
| `high` | 4,096 |
| `xhigh` | 8,192 |
| `max` | 16,384 |
For example, `reasoning_effort: "xhigh"` with `max_tokens: 2000` fails, because the 8,192-token budget does not fit. The API rejects it with a 400 that names both numbers:
```json
{
"error": {
"message": "max_tokens (2000) must be greater than the 8192-token thinking budget that reasoning_effort 'xhigh' enables on claude-haiku-4-5. Increase max_tokens or lower reasoning_effort.",
"type": "invalid_request_error",
"param": "max_tokens",
"code": "invalid_value"
}
}
```
Set `max_tokens` to the budget plus the visible output you want. `max_tokens: 20000` leaves room for every level.
Do not send Anthropic's native `thinking` parameter to `/v1/chat/completions`. This endpoint does not map or validate it. Use `reasoning_effort` instead; the gateway maps it to a thinking budget for you.
## Where reasoning appears in the response [#where-reasoning-appears-in-the-response]
Models differ in whether they return the reasoning text:
* Anthropic models and `glm-5.2` return the reasoning text in `reasoning_content` on the message.
* OpenAI models do not return reasoning text. You only see the count in `usage.completion_tokens_details.reasoning_tokens`.
```json
{
"choices": [
{
"message": {
"role": "assistant",
"content": "Assume sqrt(2) = a/b in lowest terms...",
"reasoning_content": "The user wants a proof by contradiction..."
}
}
],
"usage": {
"completion_tokens_details": { "reasoning_tokens": 412 }
}
}
```
Reasoning tokens bill at the model's reasoning rate when one is listed, otherwise at the model's output rate. See each model's page for rates.
## Verbosity [#verbosity]
The `verbosity` parameter (`low`, `medium`, or `high`) controls how long the visible answer is, independent of how much the model thinks. Only the gpt-5 family supports it.
```json
{
"model": "gpt-5.2",
"reasoning_effort": "high",
"verbosity": "low",
"messages": [{ "role": "user", "content": "Summarize this contract." }]
}
```
## Notes [#notes]
* `reasoning: {"enabled": false}` (the OpenRouter-style object) and `reasoning_effort: "none"` are accepted and translated to the model's native reasoning toggle before dispatch — for models that support one they disable reasoning. Callers that name their own **external** provider or bring their own key (`x-inference-provider` / provider API key headers) keep full control of the request vocabulary: their `reasoning` object is forwarded untouched. Pinning one of our own origins via `x-inference-provider` (e.g. `inference-net`) only selects routing and does not opt out of translation. An explicit `reasoning_effort` or `chat_template_kwargs.enable_thinking` always wins over the `reasoning` object.
* `reasoning_effort` is validated first-party: an unsupported value returns a 400 naming the valid values instead of reaching the provider. Other keys outside the documented schema pass through to the upstream provider without validation — a typo in one of those fails at the provider, not at the gateway.
# Structured Outputs (https://docs.inference.net/api/structured-outputs)
When using Structured Outputs, always include instructions in the system prompt to respond in JSON format.
For example: "You are a helpful assistant. Respond in JSON format."
## Introduction [#introduction]
JSON is one of the most widely used formats in the world for applications to exchange data.
Structured Outputs is a feature that ensures the model will always generate responses that adhere to your supplied [JSON Schema](https://json-schema.org/overview/what-is-jsonschema), so you don't need to worry about the model omitting a required key, or hallucinating an invalid enum value.
Some benefits of Structured Outputs include:
1. **Reliable type-safety:** No need to validate or retry incorrectly formatted responses
2. **Simpler prompting:** No need for strongly worded prompts to achieve consistent formatting
## Getting Started [#getting-started]
You'll need an Inference.net account and API key to use Structured Outputs. See our [Quick Start Guide](/api/api-quickstart) for instructions on how to create an account and get an API key.
Install the [OpenAI SDK](https://platform.openai.com/docs/libraries) for your language of choice.
To connect to Inference.net using the OpenAI SDK, you will need to set the base URL to `https://api.inference.net/v1`.
In this example, we are reading the API key from the environment variable `INFERENCE_API_KEY`.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL
export INFERENCE_API_KEY=
# All requests use:
# -H "Authorization: Bearer $INFERENCE_API_KEY"
# -H "Content-Type: application/json"
```
## When to use Structured Outputs [#when-to-use-structured-outputs]
Structured Outputs are suitable when you want to indicate a structured schema for use when the model responds to the user.
For example, if you are building a math tutoring application, you might want the assistant to respond to your user using a specific JSON Schema so that you can generate a UI that displays different parts of the model's output in distinct ways.
Put simply:
* If you are connecting the model to tools, functions, data, etc. in your system, then you should use function calling
* If you want to structure the model's output when it responds to the user, then you should use a structured `response_format`
### Structured Outputs vs JSON mode [#structured-outputs-vs-json-mode]
Structured Outputs is the evolution of [JSON mode](#json-mode). While both ensure valid JSON is produced, only Structured Outputs ensure schema adherance. Both Structured Outputs and JSON mode are supported in the Chat Completions API and Batch API.
We recommend always using Structured Outputs instead of JSON mode when possible.
| | Structured Outputs | JSON Mode |
| ------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------ |
| Outputs valid JSON | Yes | Yes |
| Adheres to schema | Yes (see supported schemas) | No |
| Enabling | `response_format: { type: "json_schema", json_schema: {"strict": true, "schema": ... } }` | `response_format: { type: "json_object" }` |
## Example [#example]
### Chain of thought [#chain-of-thought]
You can ask the model to output an answer in a structured, step-by-step way, to guide the user through the solution.
```typescript TypeScript
import OpenAI from "openai";
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const Step = z.object({
explanation: z.string(),
output: z.string(),
});
const MathReasoning = z.object({
steps: z.array(Step),
final_answer: z.string(),
});
const completion = await client.chat.completions.parse({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format." },
{ role: "user", content: "how can I solve 8x + 7 = -23" },
],
response_format: zodResponseFormat(MathReasoning, "math_reasoning"),
});
const math_reasoning = completion.choices[0].message.parsed;
console.log(math_reasoning);
```
```python Python
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
completion = client.chat.completions.create(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{"role": "system", "content": "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format."},
{"role": "user", "content": "how can I solve 8x + 7 = -23"},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "math_reasoning",
"strict": True,
"schema": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {"type": "string"},
"output": {"type": "string"},
},
"required": ["explanation", "output"],
"additionalProperties": False,
},
},
"final_answer": {"type": "string"},
},
"required": ["steps", "final_answer"],
"additionalProperties": False,
},
},
},
)
math_reasoning = json.loads(completion.choices[0].message.content)
print(json.dumps(math_reasoning, indent=2))
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{
"role": "system",
"content": "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format."
},
{
"role": "user",
"content": "how can I solve 8x + 7 = -23"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "math_reasoning",
"schema": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": { "type": "string" },
"output": { "type": "string" }
},
"required": ["explanation", "output"],
"additionalProperties": false
}
},
"final_answer": { "type": "string" }
},
"required": ["steps", "final_answer"],
"additionalProperties": false
},
"strict": true
}
}
}'
```
#### Example response [#example-response]
```json JSON
{
"steps": [
{
"explanation": "Start with the equation 8x + 7 = -23.",
"output": "8x + 7 = -23"
},
{
"explanation": "Subtract 7 from both sides to isolate the term with the variable.",
"output": "8x = -23 - 7"
},
{
"explanation": "Simplify the right side of the equation.",
"output": "8x = -30"
},
{
"explanation": "Divide both sides by 8 to solve for x.",
"output": "x = -30 / 8"
},
{
"explanation": "Simplify the fraction.",
"output": "x = -15 / 4"
}
],
"final_answer": "x = -15 / 4"
}
```
### Defining Schemas with the SDK [#defining-schemas-with-the-sdk]
The OpenAI SDK makes it easy to define object schemas using [Zod](https://zod.dev/) (TypeScript) or [Pydantic](https://docs.pydantic.dev/) (Python). Below, you can see how to extract information from unstructured text that conforms to a schema defined in code.
```typescript TypeScript
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const CalendarEvent = z.object({
name: z.string(),
date: z.string(),
participants: z.array(z.string()),
});
const completion = await client.chat.completions.parse({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "Extract the event information. Respond in JSON format." },
{ role: "user", content: "Alice and Bob are going to a science fair on Friday." },
],
response_format: zodResponseFormat(CalendarEvent, "event"),
});
const event = completion.choices[0].message.parsed;
console.log(event);
```
```python Python
import os
from pydantic import BaseModel
from openai import OpenAI
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
completion = client.beta.chat.completions.parse(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{"role": "system", "content": "Extract the event information. Respond in JSON format."},
{"role": "user", "content": "Alice and Bob are going to a science fair on Friday."},
],
response_format=CalendarEvent,
)
event = completion.choices[0].message.parsed
print(event.model_dump_json(indent=2))
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "Extract the event information. Respond in JSON format."},
{"role": "user", "content": "Alice and Bob are going to a science fair on Friday."}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "event",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"date": { "type": "string" },
"participants": { "type": "array", "items": { "type": "string" } }
},
"required": ["name", "date", "participants"],
"additionalProperties": false
}
}
}
}'
```
Some tips:
* Name keys clearly and intuitively
* Create clear titles and descriptions for important keys in your structure
* Create and use evals to determine the structure that works best for your use case
## Step By Step Example — Parsing The Model's Output [#step-by-step-example--parsing-the-models-output]
You can use the OpenAI SDK to parse the model's output into a typed object automatically.
### Step 1: Define your object [#step-1-define-your-object]
First you must define an object or data structure to represent the JSON Schema that the model should be constrained to follow.
```typescript TypeScript
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";
const Step = z.object({
explanation: z.string(),
output: z.string(),
});
const MathResponse = z.object({
steps: z.array(Step),
final_answer: z.string(),
});
```
```python Python
from pydantic import BaseModel
class Step(BaseModel):
explanation: str
output: str
class MathResponse(BaseModel):
steps: list[Step]
final_answer: str
```
### Step 2: Supply your object in the API call [#step-2-supply-your-object-in-the-api-call]
You can use the `parse` method to automatically parse the JSON response into the object you defined.
Under the hood, the SDK takes care of supplying the JSON schema corresponding to your data structure, and then parsing the response as an object.
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const completion = await client.chat.completions.parse({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format." },
{ role: "user", content: "how can I solve 8x + 7 = -23" },
],
response_format: zodResponseFormat(MathResponse, "math_response"),
});
console.log(completion.choices[0].message.parsed);
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
completion = client.beta.chat.completions.parse(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{"role": "system", "content": "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format."},
{"role": "user", "content": "how can I solve 8x + 7 = -23"},
],
response_format=MathResponse,
)
print(completion.choices[0].message.parsed.model_dump_json(indent=2))
```
### Handling edge cases [#handling-edge-cases]
In some cases, the model might not generate a valid response that matches the provided JSON schema.
This can happen if for example you reach a max tokens limit and the response is incomplete.
```typescript TypeScript
import OpenAI from "openai";
import { z } from "zod";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const Step = z.object({
explanation: z.string(),
output: z.string(),
});
const MathResponse = z.object({
steps: z.array(Step),
final_answer: z.string(),
});
try {
const completion = await client.chat.completions.create({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{
role: "system",
content: "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format.",
},
{
role: "user",
content: "how can I solve 8x + 7 = -23",
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "math_response",
schema: {
type: "object",
properties: {
steps: {
type: "array",
items: {
type: "object",
properties: {
explanation: { type: "string" },
output: { type: "string" },
},
required: ["explanation", "output"],
additionalProperties: false,
},
},
final_answer: { type: "string" },
},
required: ["steps", "final_answer"],
additionalProperties: false,
},
strict: true,
},
},
max_tokens: 20, // intentionally low to demonstrate truncation handling
});
const message = completion.choices[0].message;
if (completion.choices[0].finish_reason === "length") {
console.log("Model did not return a complete response — parsing may fail.");
throw new Error("Incomplete response");
}
try {
const parsed = MathResponse.parse(JSON.parse(message.content));
console.log("Parsed math response:", parsed);
} catch (zodError) {
console.error("Response does not match the expected schema:", message.content);
}
} catch (e) {
console.error(e);
}
```
```python Python
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
try:
completion = client.chat.completions.create(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{
"role": "system",
"content": "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format.",
},
{"role": "user", "content": "how can I solve 8x + 7 = -23"},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "math_response",
"strict": True,
"schema": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": {"type": "string"},
"output": {"type": "string"},
},
"required": ["explanation", "output"],
"additionalProperties": False,
},
},
"final_answer": {"type": "string"},
},
"required": ["steps", "final_answer"],
"additionalProperties": False,
},
},
},
max_tokens=20, # intentionally low to demonstrate truncation handling
)
if completion.choices[0].finish_reason == "length":
raise Exception("Incomplete response")
math_response = json.loads(completion.choices[0].message.content)
print(json.dumps(math_response, indent=2))
except Exception as e:
print(str(e))
```
```bash cURL
# Use max_tokens to demonstrate truncation handling.
# Check finish_reason in the response — "length" means the output was truncated.
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{
"role": "system",
"content": "You are a helpful math tutor. Guide the user through the solution step by step. Respond in JSON format."
},
{
"role": "user",
"content": "how can I solve 8x + 7 = -23"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "math_response",
"schema": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": { "type": "string" },
"output": { "type": "string" }
},
"required": ["explanation", "output"],
"additionalProperties": false
}
},
"final_answer": { "type": "string" }
},
"required": ["steps", "final_answer"],
"additionalProperties": false
},
"strict": true
}
},
"max_tokens": 20
}'
```
## Streaming [#streaming]
You can use streaming to process model responses as they are being generated, and parse them as structured data.
That way, you don't have to wait for the entire response to complete before handling it. This is particularly useful if you would like to display JSON fields one by one, or handle function call arguments as soon as they are available.
```typescript TypeScript
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const EntitiesSchema = z.object({
attributes: z.array(z.string()),
colors: z.array(z.string()),
animals: z.array(z.string()),
});
const stream = client.chat.completions
.stream({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{ role: "system", content: "Extract entities from the input text. Respond in JSON format." },
{
role: "user",
content: "The quick brown fox jumps over the lazy dog with piercing blue eyes",
},
],
response_format: zodResponseFormat(EntitiesSchema, "entities"),
})
.on("content.delta", ({ snapshot, parsed }) => {
console.log("content:", snapshot);
console.log("parsed:", parsed);
console.log();
})
.on("content.done", (props) => {
console.log(props);
});
await stream.done();
const finalCompletion = await stream.finalChatCompletion();
console.log(finalCompletion);
```
```python Python
import os
from typing import List
from pydantic import BaseModel
from openai import OpenAI
class EntitiesModel(BaseModel):
attributes: List[str]
colors: List[str]
animals: List[str]
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
with client.chat.completions.stream(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{
"role": "system",
"content": "Extract entities from the input text. Respond in JSON format.",
},
{
"role": "user",
"content": "The quick brown fox jumps over the lazy dog with piercing blue eyes",
},
],
response_format=EntitiesModel,
) as stream:
for event in stream:
if event.type == "content.delta":
if event.parsed is not None:
print("content.delta parsed:", event.parsed)
elif event.type == "content.done":
print("content.done")
elif event.type == "error":
print("Error in stream:", event.error)
final_completion = stream.get_final_completion()
print("Final completion:", final_completion)
```
```bash cURL
# Streaming structured outputs works the same as regular streaming.
# Add "stream": true and parse SSE chunks as they arrive.
curl -N https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-instruct/bf-16",
"stream": true,
"messages": [
{"role": "system", "content": "Extract entities from the input text. Respond in JSON format."},
{"role": "user", "content": "The quick brown fox jumps over the lazy dog with piercing blue eyes"}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "entities",
"strict": true,
"schema": {
"type": "object",
"properties": {
"attributes": { "type": "array", "items": { "type": "string" } },
"colors": { "type": "array", "items": { "type": "string" } },
"animals": { "type": "array", "items": { "type": "string" } }
},
"required": ["attributes", "colors", "animals"],
"additionalProperties": false
}
}
}
}'
```
## Supported schemas [#supported-schemas]
Structured Outputs supports a subset of the [JSON Schema](https://json-schema.org/docs) language.
#### Supported types [#supported-types]
The following types are supported for Structured Outputs:
* String
* Number
* Boolean
* Integer
* Object
* Array
* Enum
* anyOf
#### Required Fields And Additional Properties [#required-fields-and-additional-properties]
To use Structured Outputs, all properties on all objects must be specified as `required`.
Also, `additionalProperties` must be set to `false`.
In the following example, note how both `location` and `unit` are listed as required properties.
```json JSON
{
"name": "get_weather",
"description": "Fetches the weather in the given location",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The location to get the weather for"
},
"unit": {
"type": "string",
"description": "The unit to return the temperature in",
"enum": ["F", "C"]
}
},
"additionalProperties": false,
"required": ["location", "unit"]
}
}
```
#### Schema Limitations Depend on the Model [#schema-limitations-depend-on-the-model]
Limitations on the number of properties, enum values, and total string size may vary depending on the model you are using.
#### Key ordering [#key-ordering]
When using Structured Outputs, outputs will be produced in the same order as the ordering of keys in the schema.
## JSON mode [#json-mode]
When using JSON mode, always instruct the model to produce JSON in the system prompt.
For example: "You are a helpful assistant. Respond in JSON format."
JSON mode is a more basic version of the Structured Outputs feature. While JSON mode ensures that model output is valid JSON, Structured Outputs reliably matches the model's output to the schema you specify. We recommend you use Structured Outputs if it is supported for your use case.
When JSON mode is turned on, the model's output is ensured to be valid JSON, except for in some edge cases that you should detect and handle appropriately.
To turn on JSON mode with the Chat Completions API you can set the `response_format` to `{ "type": "json_object" }`.
Important notes:
* When using JSON mode, you must always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate non-JSON or an unending stream of whitespace.
* JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors. You should use Structured Outputs to ensure it matches your schema, or if that is not possible, you should use a validation library and potentially retries to ensure that the output matches your desired schema.
* Your application must detect and handle the edge cases that can result in the model output not being a complete JSON object (see below)
* Some models will include a triple backtick / JSON code format block around the JSON response. This should be detected and handled appropriately.
### Handling JSON Mode edge cases [#handling-json-mode-edge-cases]
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
try {
const response = await client.chat.completions.create({
model: "google/gemma-3-27b-instruct/bf-16",
messages: [
{
role: "system",
content: "You are a helpful assistant designed to output JSON.",
},
{ role: "user", content: "Who won the world series in 2020? Please respond in the format {winner: ...}" },
],
response_format: { type: "json_object" },
});
// Check if the response was truncated due to context length
if (response.choices[0].finish_reason === "length") {
// Handle incomplete JSON
}
// Check if the model refused the request
if (response.choices[0].message.refusal) {
console.log(response.choices[0].message.refusal);
}
// Check if content was filtered
if (response.choices[0].finish_reason === "content_filter") {
// Handle filtered content
}
if (response.choices[0].finish_reason === "stop") {
console.log(JSON.parse(response.choices[0].message.content));
}
} catch (e) {
console.error(e);
}
```
```python Python
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
try:
response = client.chat.completions.create(
model="google/gemma-3-27b-instruct/bf-16",
messages=[
{"role": "system", "content": "You are a helpful assistant designed to output JSON."},
{"role": "user", "content": "Who won the world series in 2020? Please respond in the format {winner: ...}"},
],
response_format={"type": "json_object"},
)
message = response.choices[0]
# Check if the response was truncated due to context length
if message.finish_reason == "length":
raise Exception("Incomplete response — output was truncated")
# Check if the model refused the request
if hasattr(message.message, "refusal") and message.message.refusal:
print(message.message.refusal)
# Check if content was filtered
if message.finish_reason == "content_filter":
raise Exception("Response filtered")
if message.finish_reason == "stop":
print(json.loads(message.message.content))
except Exception as e:
print(str(e))
```
```bash cURL
# JSON mode is enabled with response_format type "json_object".
# Check finish_reason in the response to handle edge cases:
# "stop" — complete JSON returned
# "length" — output truncated, JSON may be incomplete
# "content_filter" — content was filtered
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [
{"role": "system", "content": "You are a helpful assistant designed to output JSON."},
{"role": "user", "content": "Who won the world series in 2020? Please respond in the format {winner: ...}"}
],
"response_format": { "type": "json_object" }
}'
```
# Vision (https://docs.inference.net/api/vision)
## Introduction [#introduction]
**Vision Models** are *multi-modal models* that accept both text and images as input.
You can use vision models to extract information from images (for example, by asking the model to describe the image).
This guide explains how to use Vision Models with the Inference API.
## Getting Started [#getting-started]
You'll need an Inference.net account and API key. See our [Quick Start Guide](/api/api-quickstart) for instructions on how to create an account and get an API key.
Install the [OpenAI SDK](https://platform.openai.com/docs/libraries) for your language of choice.
To connect to Inference.net using the OpenAI SDK, you will need to set the base URL to `https://api.inference.net/v1`.
In the following examples, we are reading the API key from the environment variable `INFERENCE_API_KEY`.
## Step By Step Example [#step-by-step-example]
To use image inputs with the Inference API:
1. Encode your image as a base64 string
2. Include the base64 string in a [Data URI](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data) with an image mimetype (e.g. `image/png`)
3. Include the Data URI in the `content` array of a user message
4. Send the request to the Inference API and inspect the response
### Step 1: Encode your image as a Data URI [#step-1-encode-your-image-as-a-data-uri]
```typescript TypeScript
const url = "https://upload.wikimedia.org/wikipedia/commons/3/3f/Crystal_Project_bug.png";
const response = await fetch(url);
const buffer = Buffer.from(await response.arrayBuffer());
const base64 = buffer.toString("base64");
const dataUri = `data:image/png;base64,${base64}`;
```
```python Python
import base64
import requests
url = "https://upload.wikimedia.org/wikipedia/commons/3/3f/Crystal_Project_bug.png"
response = requests.get(url)
image_data = response.content
encoded_string = base64.b64encode(image_data).decode("utf-8")
data_uri = f"data:image/png;base64,{encoded_string}"
```
```bash cURL
# Encode an image file to a base64 data URI:
DATA_URI="data:image/png;base64,$(base64 -i image.png)"
# Or fetch and encode from a URL:
DATA_URI="data:image/png;base64,$(curl -s https://upload.wikimedia.org/wikipedia/commons/3/3f/Crystal_Project_bug.png | base64)"
```
### Step 2: Structure and send your request [#step-2-structure-and-send-your-request]
```typescript TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
const completion = await client.chat.completions.create({
model: "google/gemma-3-27b-it",
messages: [
{
role: "system",
content: "You are a helpful assistant that can answer questions about the image.",
},
{
role: "user",
content: [
{
type: "image_url",
image_url: { url: dataUri },
},
{
type: "text",
text: "What is in this image?",
},
],
},
],
});
console.log(completion.choices[0].message.content);
```
```python Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
completion = client.chat.completions.create(
model="google/gemma-3-27b-it",
messages=[
{
"role": "system",
"content": "You are a helpful assistant that can answer questions about the image.",
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": data_uri},
},
{
"type": "text",
"text": "What is in this image?",
},
],
},
],
)
print(completion.choices[0].message.content)
```
```bash cURL
curl https://api.inference.net/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-27b-it",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant that can answer questions about the image."
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": { "url": "'"$DATA_URI"'" }
},
{
"type": "text",
"text": "What is in this image?"
}
]
}
]
}'
```
## Limitations [#limitations]
* We do not support sending images from a URL directly into the request body.
* Supported image formats include webp, png, gif and jpg/jpeg.
* The total request body can be up to 12 MiB (12,582,912 bytes), including inline images.
## Token Usage [#token-usage]
Using images in a request counts towards the total token usage for a request.
The exact token count will vary, but a handy approximation of the number of tokens used by an image is the following formula:
```
h = max(1, min(2, HEIGHT / 560))
w = max(1, min(2, WIDTH / 560))
tokens = h * w * 1601
```
In plain English:
1. The image height and width in pixels are both divided by 560
2. The resulting height and width are clamped between 1 and 2
3. Finally, the height and width are multiplied together and then multiplied by 1,601
Here is a table of image dimensions and their corresponding estimated token counts:
| Height | Width | Tokens | Note |
| ------ | ------ | ------ | -------------------------------------------------------------- |
| 32px | 32px | 1,601 | Images smaller than 560x560 are still considered 560x560 |
| 560px | 560px | 1,601 | |
| 1120px | 1120px | 6,404 | 6,404 is the approximate maximum token usage of a single image |
The above formula and table is an approximation. We suggest that you:
* Explicitly check your image dimensions before submitting them to the API to avoid high token usage.
* Monitor your token usage and adjust your requests if necessary.
See the [Models](https://inference.net/models) page for current pricing per token for Vision Models.
# Changelog (https://docs.inference.net/changelog/overview)
# Changelog [#changelog]
Updates and changes to Inference are published on the [Inference blog](https://inference.net/blog/), with the latest releases tracked on [GitHub](https://github.com/context-labs). New product features also ship in the dashboard and are announced in the blog.
# Account Deletion (https://docs.inference.net/cli/account-deletion)
Schedule permanent deletion of your account, a project, or a team. These commands preview what will be deleted first and schedule the deletion inside a grace window so you can cancel it.
Deleting an account, project, or team is **permanent and irreversible**. Data is not recoverable once the grace window elapses. These commands are destructive — use them only when you are certain.
## How deletion works [#how-deletion-works]
Deletion is **scheduled** first, giving you a grace window. Inside that window you can `cancel-project` to abort it. Once the window elapses, the deletion proceeds and the data is permanently gone.
## Preview before you delete [#preview-before-you-delete]
```bash
# Preview blockers and affected teams for account deletion
inf account-deletion preview
# Preview blockers and members for one team's deletion
inf account-deletion get-team-preview --team-id
# Check whether the account-deletion flow is enabled for the caller
inf account-deletion enabled
```
## Scheduling deletions [#scheduling-deletions]
```bash
# Schedule deletion of the caller's account
inf account-deletion delete-account
# Schedule deletion of a project and all its data
inf account-deletion delete-project --project-id
# Schedule deletion of a team
inf account-deletion delete-team --team-id
```
## Cancel a scheduled deletion [#cancel-a-scheduled-deletion]
```bash
# Cancel a scheduled project deletion inside its grace window
inf account-deletion cancel-project --project-id
```
## Transfer ownership instead [#transfer-ownership-instead]
If you're leaving a team, prefer transferring ownership to another member rather than deleting the team.
```bash
inf account-deletion transfer-ownership --team-id --to-user-id
```
Each verb's full flag list is shown by `inf account-deletion --help`.
# Agents (https://docs.inference.net/cli/agent)
Inspect the agents auto-discovered from your spans and their execution stats. Agents are read-only — there are no agent mutations.
## Commands [#commands]
Run `inf agent --help` for each verb's options. The generated surface covers:
| Verb | Description |
| ------------------------ | ------------------------------------------------------------ |
| `list` | List a project's agents with rolled-up stats |
| `summary-cards` | List a project's agents as summary cards with sparklines |
| `index-cards` | List the paginated agent index with rolled-up stats |
| `index-initial-data` | The first agent-index page plus its execution series |
| `get` | Get an agent's identity by id |
| `stats` | Get an agent's rolled-up stats over the read window |
| `overview` | Get an agent's overview: summary, series, model/tool profile |
| `timeseries` | Get an agent's per-day metric time series |
| `index-execution-series` | Per-agent execution-count series for the index |
## Related commands [#related-commands]
* [`inf trace`](/cli/traces) and [`inf span`](/cli/spans) inspect the underlying data agents are derived from.
* [`inf signals`](/cli/signals) attach classifiers to an agent's spans/traces/sessions.
* [`inf analyze-harness agent list`](/cli/analyze-harness) lists the agents HALO sees for trace analysis.
# Analyze Harness (https://docs.inference.net/cli/analyze-harness)
HALO analyzes your agent's traces and produces a markdown report that flags anomalies, errors, inefficiencies, and opportunities to improve reliability, latency, cost, and tool usage. The report is written to read like a brief you can paste straight into a coding agent to apply the fixes.
Use `inf analyze-harness` to kick off a one-off analysis, schedule recurring ones, watch a run reach completion, and read the resulting report. The old name `inf halo` remains as an alias.
## How HALO is organized [#how-halo-is-organized]
* A **run** is one analysis of one agent over one time window. Each completed run produces exactly one report.
* A **conversation** is the thread a run lives in. The first assistant message is the original report; any follow-up questions you ask add more runs and more assistant messages to the same conversation.
* A **schedule** fires recurring runs for one agent on a cadence (hourly, daily, weekly, or monthly).
The report markdown is the assistant message content inside a conversation. There is no separate "report" artifact to download, so reading a report means reading its conversation.
## Quickstart: run an analysis and read the report [#quickstart-run-an-analysis-and-read-the-report]
```bash
# 1. Find the agent you want to analyze
inf analyze-harness agent list
# 2. Start a run for that agent over the last 24h
inf analyze-harness run create --agent-uuid
# 3. Wait for it to finish (prints the run id from step 2)
inf analyze-harness run poll
# 4. Read the report (the assistant message in the conversation)
inf analyze-harness conversation get
```
`inf analyze-harness run create` prints both the `run-id` and the `conversation-id`. The report prints in full from `inf analyze-harness conversation get` — no `--json` required. Add `--json` only when you want the raw structured payload for scripting.
## `inf analyze-harness agent` [#inf-analyze-harness-agent]
List the agents HALO sees in your project's recent traces. You need an agent's UUID to start a run or create a schedule.
```bash
inf analyze-harness agent list
```
**Alias:** `inf analyze-harness agent ls`
### Options [#options]
| Flag | Required | Description | Default |
| ---------------------- | -------- | ----------------------------------------------------------------------------------------- | ------- |
| `--time-range ` | No | Trace window to scan for agents: `1h`, `6h`, `12h`, `1d`, `3d`, `7d`, `14d`, `30d`, `90d` | `30d` |
## `inf analyze-harness run` [#inf-analyze-harness-run]
Create and inspect HALO runs.
### `inf analyze-harness run create` [#inf-analyze-harness-run-create]
Start a manual analysis for one agent over a time window.
```bash
inf analyze-harness run create --agent-uuid
```
In an interactive terminal you can omit `--agent-uuid` to pick from recent agents. In non-interactive or `--json` mode, `--agent-uuid` is required.
#### Options [#options-1]
| Flag | Required | Description | Default |
| ---------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------- |
| `--agent-uuid ` | No (required non-interactively) | Agent UUID from `inf analyze-harness agent list` | Interactive picker |
| `--prompt ` | No | Prompt steering the analysis | Default HALO prompt |
| `--window-start-at ` | No | Window start, ISO datetime | Derived from `--lookback-hours` |
| `--window-end-at ` | No | Window end, ISO datetime | Now |
| `--lookback-hours ` | No | Window length when `--window-start-at` is omitted | `24` |
| `--model-id ` | No | Catalog HALO model id | Catalog default |
| `--span-limit ` | No | Per-run span cap — omit to use the platform default | Platform default |
| `--max-subagent-depth ` | No | Engine recursion ceiling (1–5) | `1` |
| `--max-turns ` | No | Per-agent turn ceiling (1–100) | `20` |
| `--reasoning-effort ` | No | Reasoning effort for the analysis model: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh` | `medium` |
| `--subagent-model-id ` | No | Catalog model id for subagent executions — omit to run subagents on the analysis model | Analysis model |
| `--debug` | No | Enable debug logging for the HALO runtime | Off |
| `--agent-time-range ` | No | Trace window preset used when picking an agent interactively | `30d` |
### `inf analyze-harness run poll` [#inf-analyze-harness-run-poll]
Poll a run until it reaches a terminal state (`completed`, `failed`, `cancelled`, `timed_out`, or `no_traces`). Exits `0` only on `completed`.
```bash
inf analyze-harness run poll
```
#### Options [#options-2]
| Flag | Required | Description | Default |
| ---------------------- | -------- | ------------- | --------------- |
| `--interval ` | No | Poll interval | `5` |
| `--timeout ` | No | Total timeout | `1800` (30 min) |
### `inf analyze-harness run get` [#inf-analyze-harness-run-get]
Fetch a run's status and its referenced trace dataset. This returns run metadata, not the report text — read the conversation for the report.
```bash
inf analyze-harness run get
```
### `inf analyze-harness run events` [#inf-analyze-harness-run-events]
List the structured event timeline for a run (started, heartbeat, agent steps, completed, failed).
```bash
inf analyze-harness run events
```
#### Options [#options-3]
| Flag | Required | Description | Default |
| ------------- | -------- | -------------------- | ------- |
| `--limit ` | No | Max events to return | `100` |
### `inf analyze-harness run cancel` [#inf-analyze-harness-run-cancel]
Request cancellation of an in-flight run.
```bash
inf analyze-harness run cancel --reason "no longer needed"
```
#### Options [#options-4]
| Flag | Required | Description | Default |
| ----------------- | -------- | ------------------------------------ | ---------------- |
| `--reason ` | No | Reason recorded for the cancellation | `user-cancelled` |
## `inf analyze-harness conversation` [#inf-analyze-harness-conversation]
Inspect HALO conversations and read their reports.
**Alias:** `inf analyze-harness conv`
### `inf analyze-harness conversation list` [#inf-analyze-harness-conversation-list]
List conversations in the active project, newest first.
```bash
inf analyze-harness conversation list
```
**Alias:** `inf analyze-harness conversation ls`
#### Options [#options-5]
| Flag | Required | Description | Default |
| ------------- | -------- | --------------------------- | ------- |
| `--limit ` | No | Max conversations to return | `50` |
### `inf analyze-harness conversation get` [#inf-analyze-harness-conversation-get]
Print a conversation with its messages, runs, and trace datasets. The assistant messages are the report markdown — the first assistant message is the original report; later ones answer follow-up questions in the same thread. Message content prints in full.
```bash
inf analyze-harness conversation get
```
Add `--json` to get the raw payload (every message, run, and trace dataset) for scripting.
## `inf analyze-harness schedule` [#inf-analyze-harness-schedule]
Manage recurring HALO analyses. Each schedule targets one agent and fires runs on a cadence.
### `inf analyze-harness schedule list` [#inf-analyze-harness-schedule-list]
```bash
inf analyze-harness schedule list
```
**Alias:** `inf analyze-harness schedule ls`
| Flag | Required | Description | Default |
| -------------------- | -------- | -------------------------- | ------- |
| `--include-archived` | No | Include archived schedules | `false` |
### `inf analyze-harness schedule get` [#inf-analyze-harness-schedule-get]
```bash
inf analyze-harness schedule get
```
### `inf analyze-harness schedule runs` [#inf-analyze-harness-schedule-runs]
List recent runs fired by a schedule.
```bash
inf analyze-harness schedule runs
```
| Flag | Required | Description | Default |
| ------------- | -------- | ------------------ | ------- |
| `--limit ` | No | Max runs to return | `50` |
### `inf analyze-harness schedule create` [#inf-analyze-harness-schedule-create]
```bash
inf analyze-harness schedule create \
--title "Daily checkout-agent review" \
--agent-uuid \
--frequency daily \
--hours 9 \
--minutes 0 \
--timezone America/Los_Angeles
```
| Flag | Required | Description | Default |
| -------------------------- | ------------------------ | ------------------------------------------------------------------- | ------------------- |
| `--title ` | Yes | Human-readable schedule title | - |
| `--agent-uuid ` | Yes | Agent the schedule analyzes (from `inf analyze-harness agent list`) | - |
| `--frequency ` | Yes | `hourly`, `daily`, `weekly`, or `monthly` | - |
| `--prompt ` | No | Prompt steering each run | Default HALO prompt |
| `--hours ` | For daily/weekly/monthly | Comma-separated hours (0–23) | - |
| `--minutes ` | Yes | Comma-separated minutes (0–59) | - |
| `--days-of-week ` | For weekly | Comma-separated days (0=Sun…6=Sat) | - |
| `--days-of-month ` | For monthly | Comma-separated days (1–31) | - |
| `--timezone ` | No | IANA timezone | `UTC` |
| `--model-id ` | No | Catalog HALO model id | Catalog default |
| `--span-limit ` | No | Per-run span cap | `10000` |
| `--lookback-hours ` | No | How far back each run looks, independent of cadence | `24` |
| `--max-subagent-depth ` | No | Engine recursion ceiling (1–5) | `2` |
| `--max-turns ` | No | Per-agent turn ceiling (1–100) | `20` |
| `--enabled ` | No | Whether the schedule fires | `true` |
### `inf analyze-harness schedule update` [#inf-analyze-harness-schedule-update]
Update mutable fields on a schedule. Accepts the same flags as `create` (all optional), keyed by ``.
```bash
# Pause a schedule
inf analyze-harness schedule update --enabled false
# Repoint it at a different agent and widen the window
inf analyze-harness schedule update --agent-uuid --lookback-hours 168
```
### `inf analyze-harness schedule archive` / `unarchive` [#inf-analyze-harness-schedule-archive--unarchive]
Archive a schedule so it stops firing, or restore it later.
```bash
inf analyze-harness schedule archive
inf analyze-harness schedule unarchive
```
`archive` prompts for confirmation. Pass `-y` / `--yes` to skip the prompt.
## Common workflows [#common-workflows]
```bash
# Run an analysis end-to-end and capture the report markdown to a file
RUN=$(inf analyze-harness run create --agent-uuid --json)
RUN_ID=$(echo "$RUN" | jq -r '.runId')
CONV_ID=$(echo "$RUN" | jq -r '.conversationId')
inf analyze-harness run poll "$RUN_ID"
# Extract just the original report (first assistant message) as markdown
inf analyze-harness conversation get "$CONV_ID" --json \
| jq -r '.messages | map(select(.role == "assistant"))[0].content' \
> halo-report.md
```
## Related commands [#related-commands]
* [`inf trace`](/cli/traces) and [`inf span`](/cli/spans) inspect the underlying traces HALO analyzes.
* The [MCP server](/integrations/mcp-server) exposes the same reports to AI coding assistants via `list_halo_conversations` and `get_halo_conversation`.
# API Keys (https://docs.inference.net/cli/api-key)
Project API keys let you authenticate CI pipelines and other headless environments. See [API Keys and Authentication](/reference/api-keys) for how keys work across the platform. These commands manage the same keys from the terminal.
API key management requires session login (`inf auth login`). A project API key cannot create, list, or revoke other keys.
## `inf api-key create` [#inf-api-key-create]
Create a project-scoped API key. The raw key value is shown once, at creation. Save it immediately, since it cannot be retrieved again.
```bash
inf api-key create --project
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------------------- |
| `name` | Yes | Human-readable name for the key, e.g. `production-ci` |
### Options [#options]
| Option | Required | Description | Default |
| ---------------------- | -------- | ----------------------------------------------------- | -------------- |
| `--project ` | Yes\* | Project the key is scoped to | Active project |
| `--team ` | No | Team that owns the key | Active team |
| `--permissions ` | No | Comma-separated permissions: `read`, `write`, or both | `read,write` |
\* Required unless `INF_PROJECT_ID` is set or an active project is already set (see [Projects](/cli/projects)).
Creating an API key may require a verified phone number on your account. If you see a verification error, add a phone number from the dashboard and try again.
### Examples [#examples]
```bash
# Read and write key for the active project
inf api-key create production-ci --project proj_789ghi012jkl
# Read-only key
inf api-key create readonly-metrics --project proj_789ghi012jkl --permissions read
# Key for a specific team
inf api-key create ci-key --project proj_789ghi012jkl --team team_abc123
```
## `inf api-key list` [#inf-api-key-list]
List API keys for a team. The raw key value is never shown again after creation, only its ID, name, and scopes.
```bash
inf api-key list
```
**Alias:** `inf api-key ls`
### Options [#options-1]
| Option | Required | Description | Default |
| ------------- | -------- | --------------------- | ----------- |
| `--team ` | No | Team to list keys for | Active team |
### Example [#example]
```bash
inf api-key list --team team_abc123
```
## `inf api-key show` [#inf-api-key-show]
Describe the default API key for the active (or specified) project. The value is masked — plaintext is shown only at creation.
```bash
inf api-key show
```
### Options [#options-2]
| Option | Required | Description | Default |
| ---------------- | -------- | --------------------------------- | -------------- |
| `--project ` | No | Project whose default key to show | Active project |
| `--team ` | No | Team that owns the project | Active team |
Prints the key's ID, name, masked preview, project, team, scopes, and creation date. Because stored keys are hashed, the full key cannot be printed — create a new one with `inf api-key create `.
### Example [#example-1]
```bash
inf api-key show --project proj_789ghi012jkl
```
## `inf api-key revoke` [#inf-api-key-revoke]
Revoke an API key so it can no longer authenticate requests. This cannot be undone.
```bash
inf api-key revoke
```
**Alias:** `inf api-key rm`
### Arguments [#arguments-1]
| Argument | Required | Description |
| -------- | -------- | -------------------------------------------------------------------------- |
| `id` | No | API key ID to revoke (from `inf api-key list`); omit to pick interactively |
### Options [#options-3]
| Option | Required | Description | Default |
| ------------- | -------- | ---------------------- | ----------- |
| `--team ` | No | Team that owns the key | Active team |
### Example [#example-2]
```bash
inf api-key revoke key_abc123
# Omit the id to pick a key to revoke interactively
inf api-key revoke
```
# Authentication (https://docs.inference.net/cli/authentication)
The CLI supports two auth modes: browser-based **session login** for interactive use and a **project API key** for CI and headless environments.
## `inf auth login` [#inf-auth-login]
Sign in through your browser using the OAuth 2.0 device authorization flow. Opens a verification URL, displays a user code, and polls until you approve in the browser.
```bash
inf auth login
```
After sign-in, the CLI stores a session token in `~/.inf/config.json` and activates an organization (team). If your account belongs to multiple teams, `inf auth login` prompts you to choose one in an interactive terminal.
### Options [#options]
| Option | Required | Description |
| --------------------- | -------- | -------------------------------------------------------- |
| `--team ` | No | Activate a specific team by ID, slug, or exact team name |
### Team selection [#team-selection]
Use `--team` when you know which team you want to activate, or when running in a non-interactive shell.
```bash
inf auth login --team acme
```
You can pass a team ID, slug, or exact team name:
```bash
inf auth login --team team_abc123
inf auth login --team acme
inf auth login --team "Acme Research"
```
If the selected team is different from the previously active team, the CLI clears the stored active project and then tries to auto-select a project from the newly active team. You can always run `inf project list` and `inf project switch ` to pick a different project.
`inf auth login --team` only sets the team activated at sign-in. Once signed in, you have two ways to work with a different team without signing in again: `inf team switch ` stores a new active team in your config, while the global `-t, --team ` flag (or `INF_TEAM_ID`) overrides the team for a single command only and does not change your stored active team. See [Teams](/cli/team).
In non-interactive environments, `inf auth login` cannot prompt for a team. If you belong to multiple teams and omit `--team`, the CLI falls back to the first team returned by the auth API. Pass `--team ` to make the selected team deterministic.
Session login requires a browser, so `inf auth login` is not suitable for CI or other headless environments. Use `inf auth set-key` or the `INF_API_KEY` env var there instead.
## `inf auth set-key` [#inf-auth-set-key]
Store a project API key on disk for headless or CI authentication.
```bash
inf auth set-key
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------------- |
| `key` | Yes | A project API key (starts with `sk-inference-`) |
After saving, the CLI validates the key by fetching the project list. A successful fetch auto-selects the first project as active.
### Example [#example]
```bash
inf auth set-key sk-inference-...
```
## `inf auth status` [#inf-auth-status]
Show who you're signed in as, which auth method the CLI is using, the active team and project, and the API URL.
```bash
inf auth status
```
## `inf auth whoami` [#inf-auth-whoami]
Show your user ID, email, name, and the team the next command will target.
```bash
inf auth whoami
```
Use `inf auth status` for a broader view that also includes the auth method and active project. Use `whoami` when you only need identity and team, for example in a script.
With a project API key, `whoami` shows your user ID and team but not your email or name, since a project API key is not tied to a specific person.
## `inf auth logout` [#inf-auth-logout]
Sign out, clear the session token / API key from `~/.inf/config.json`, and forget the active project and team.
```bash
inf auth logout
```
## Credential resolution order [#credential-resolution-order]
When multiple credentials are present, the CLI picks the first match:
1. `INF_API_KEY` environment variable
2. API key stored via `inf auth set-key`
3. Session token stored via `inf auth login`
Coding-agent setup lives in the [`fast` CLI](https://fast.inference.net/coding-agents), which mints its own machine-scoped key at `fast on`.
`inf instrument` is the one exception — it **rejects** `INF_API_KEY` and requires a session login, because it mints a fresh API key for your project on your behalf. Unset `INF_API_KEY` and run `inf auth login` before running `inf instrument`.
## Configuration [#configuration]
The CLI stores configuration at `~/.inf/config.json`, created automatically on first login. Tokens are stored with `0600` permissions.
### Environment variables [#environment-variables]
| Variable | Description | Default |
| ---------------- | -------------------------------------------------------------------------------------- | ----------------------------- |
| `INF_API_KEY` | API key for authentication. Takes precedence over stored credentials | — |
| `INF_API_URL` | Override the API base URL | `https://relay.inference.net` |
| `INF_PROJECT_ID` | Override the active project for any invocation (equivalent to `--project ` global) | — |
| `INF_TEAM_ID` | Override the active team for any invocation (equivalent to `--team ` global) | — |
`--team` and `INF_TEAM_ID` take a team ID, not a slug or name. Use `inf team switch ` once to resolve a slug or name to an ID, or run `inf team list` to look one up.
# Compare (https://docs.inference.net/cli/compare)
AutoEvals runs automated evaluations over an agent's recent traffic. Use `inf compare` to inspect an analysis run's status and outputs, list the eval run groups it launched, and poll a run to completion. The old name `inf autoevals` remains as an alias.
## Quickstart [#quickstart]
```bash
# Inspect a run and its eval run groups
inf compare get
inf compare groups
# Poll a run to completion (exits 0 on success)
inf compare poll
```
## `inf compare get` [#inf-compare-get]
Show one analysis run's status, stage, and outputs.
```bash
inf compare get
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | ------------------------- |
| `runId` | Yes | AutoEvals analysis run ID |
## `inf compare groups` [#inf-compare-groups]
List a run's eval run groups and their eval runs — one group per rubric.
```bash
inf compare groups
```
### Arguments [#arguments-1]
| Argument | Required | Description |
| -------- | -------- | ------------------------- |
| `runId` | Yes | AutoEvals analysis run ID |
## `inf compare poll` [#inf-compare-poll]
Poll an analysis run until it reaches a terminal status (`success` or `error`).
```bash
inf compare poll
```
### Arguments [#arguments-2]
| Argument | Required | Description |
| -------- | -------- | ------------------------- |
| `runId` | Yes | AutoEvals analysis run ID |
### Options [#options]
| Flag | Required | Description | Default |
| ---------------------- | -------- | ------------------------------- | ------- |
| `--interval ` | No | Poll interval | `15` |
| `--timeout ` | No | Give up after this many minutes | `45` |
## Generated REST verbs [#generated-rest-verbs]
`inf compare` also exposes the broader AutoEvals API as generated commands — `runs-list`, `runs-start-user`, `runs-cancel`, `insights`, `get-insight`, `usage`, `settings`, `update-settings`, `eval-usage-estimate`, and more. Run `inf compare --help` to list them and their flags.
## Related commands [#related-commands]
* [`inf eval`](/cli/evals) runs manual eval runs against rubrics.
# Dashboard (https://docs.inference.net/cli/dashboard)
`inf dashboard` launches a full-screen interactive terminal UI that gives you a live overview of training runs, evaluations, datasets, and inferences in your active project.
**Alias:** `inf dash`
## `inf dashboard` [#inf-dashboard]
Launch the TUI. The dashboard reads your active project from `~/.inf/config.json`, or use the global `--project ` flag to target a different project for this session.
```bash
inf dashboard
```
### Keyboard Shortcuts [#keyboard-shortcuts]
| Key | Action |
| ------------------- | ----------------------------- |
| `1` – `4` | Switch between tabs directly |
| `Tab` / `Shift+Tab` | Cycle through tabs |
| `j` / `k` | Navigate up and down in lists |
| `Enter` | Drill into the selected item |
| `r` | Refresh the current view |
| `/` | Open the command palette |
| `q` or `Ctrl+C` | Quit the dashboard |
Paged list views (inside a detail panel) add navigation keys beyond `j`/`k`: `h`/`l`/`p`/`n` change pages, `g` jumps to the first page, `s` cycles the sort key, `d` toggles sort direction, and `/` starts an in-pager search.
### Tabs [#tabs]
The dashboard provides four tabs for navigating your project data:
| Tab | Key | Description |
| ---------- | --- | ---------------------------------------------------- |
| Training | `1` | View training runs with status, progress, and loss |
| Evals | `2` | Browse eval run groups and individual run results |
| Datasets | `3` | List filtered datasets with export status |
| Inferences | `4` | View recent inferences with token counts and latency |
Select any item in a list and press `Enter` to open a detail panel with more information.
### Examples [#examples]
```bash
# Launch for the active project
inf dashboard
# Launch for a specific project without changing your stored config
inf dashboard --project proj_789ghi012jkl
# Use the alias
inf dash
```
The dashboard requires a terminal that supports modern rendering. Most standard terminal emulators (Terminal.app, iTerm2, Alacritty, Windows Terminal) work well.
# Datasets (https://docs.inference.net/cli/datasets)
Use `inf dataset` to upload JSONL inference data and manage datasets created from captured traffic, existing uploads, or JSONL files on disk. Materialized datasets feed into [`inf eval run`](/cli/evals) for evals and into training jobs.
**Alias:** `inf datasets`
## `inf dataset upload` [#inf-dataset-upload]
Import a JSONL file into the active project as an upload entry. An upload is the raw material you can then materialize into an eval or training dataset. The CLI validates the file locally, uploads it in parts, waits for processing to finish, and prints the detected format plus the processed line count.
```bash
inf dataset upload
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | -------------------------------- |
| `file` | Yes | Path to the JSONL file to upload |
### Options [#options]
| Flag | Required | Description | Default |
| ------------------- | -------- | ---------------------------------------------------------------- | -------------------------- |
| `-n, --name ` | No | Upload name shown in the Inference platform | Filename without extension |
| `--no-wait` | No | Return after the transfer finishes instead of polling processing | Off |
Uploaded data appears in **Datasets → Uploads** in the dashboard. Once processing completes, create an eval or training dataset from that upload — either with [`inf dataset create --upload-id`](#inf-dataset-create) below or in the dashboard.
### Examples [#examples]
```bash
# Use the filename as the upload name and wait for processing
inf dataset upload ./data/support-summaries.jsonl
# Set a custom upload name
inf dataset upload ./data/support-summaries.jsonl --name support-summaries-v2
# Return after the transfer completes, without waiting for processing
inf dataset upload ./data/support-summaries.jsonl --no-wait
```
## `inf dataset create` [#inf-dataset-create]
Materialize an eval or training dataset from captured traffic, an existing upload, or a JSONL file on disk. The file-backed path uploads, waits for processing, and materializes in one command.
```bash
inf dataset create -n -t [source-flags…]
```
### Options [#options-1]
| Flag | Required | Description | Default |
| ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `-n, --name ` | Yes | Dataset name | — |
| `-t, --type ` | Yes | `eval` or `training` | — |
| `-f, --file ` | No | JSONL file on disk — uploads, waits for processing, then materializes from that upload | — |
| `--upload-id ` | No | Materialize from an existing upload | — |
| `--task ` | No | Filter captured traffic by task ID | — |
| `--model ` | No | Filter captured traffic by model ID | — |
| `--since ` | No | Start of the time window for traffic filters (ISO 8601 or `YYYY-MM-DD HH:MM:SS`) | 30 days ago |
| `--until ` | No | End of the time window for traffic filters | 1 minute from now |
| `--limit ` | No | Cap on the number of inferences included | — |
| `--status ` | No | Status filter: `success` (default), `2xx`, or a specific code like `200` — datasets reject non-success traffic unless you override | `success` |
| `--description ` | No | Free-text dataset description | — |
`--file` and `--upload-id` are mutually exclusive — `--file` creates a new upload automatically. Date values accept ISO 8601 (`2026-04-01T00:00:00Z`) or ClickHouse format (`2026-04-01 00:00:00`).
Dataset materialization runs asynchronously. The command prints the dataset ID and points at `inf dataset get ` to check progress.
### Examples [#examples-1]
```bash
# One-command: upload a JSONL file and materialize an eval dataset
inf dataset create -n demo-eval -t eval --file ./samples.jsonl
# Materialize from an existing upload
inf dataset create -n training-v1 -t training --upload-id up_abc123
# Filter captured traffic by task + time window
inf dataset create -n support-eval -t eval \
--task support-tickets \
--since 2026-04-01 \
--until 2026-04-14
# Cap to 1,000 rows (only successful traffic is included by default)
inf dataset create -n small-eval -t eval --task support-tickets --limit 1000
```
## `inf dataset list` [#inf-dataset-list]
Display datasets in the active project.
```bash
inf dataset list
```
**Alias:** `inf dataset ls`
### Options [#options-2]
| Flag | Required | Description | Default |
| ----------------- | -------- | ------------------------- | ------- |
| `-l, --limit ` | No | Maximum number of results | `20` |
The table shows the dataset ID (8-char prefix), name, type, inference count, export status, and creation date. Use `--json` to get full UUIDs for scripting.
### Examples [#examples-2]
```bash
# Default table view
inf dataset list
# More results
inf dataset list --limit 100
# Pipe full UUIDs into another command
inf dataset list --json | jq -r '.[].id'
```
## `inf dataset get` [#inf-dataset-get]
View detailed information about a specific dataset — ID, name, type, inference count, export status, source project, and creation date.
```bash
inf dataset get
```
### Arguments [#arguments-1]
| Argument | Required | Description |
| -------- | -------- | ------------------------------------------------- |
| `id` | Yes | Dataset ID, UUID prefix (4+ chars), or exact name |
## `inf dataset download` [#inf-dataset-download]
Download a dataset as a JSONL file. If the server-side export isn't ready yet, the CLI requests it and polls until it's ready before downloading.
```bash
inf dataset download [id]
```
### Arguments [#arguments-2]
| Argument | Required | Description |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `id` | No | Dataset ID, UUID prefix (4+ chars), or exact name. If omitted in an interactive terminal, the CLI prompts you to choose. |
### Options [#options-3]
| Flag | Required | Description | Default |
| ----------------------- | -------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `-o, --output ` | No | Output file path | `.jsonl` for Hugging Face, `.source-backed.jsonl` for source-backed |
| `-f, --format ` | No | Download format: `huggingface` or `source-backed` | Prompted in a TTY; otherwise `huggingface` |
The CLI resolves dataset IDs by exact ID, UUID prefix, or exact name.
### Examples [#examples-3]
```bash
# Download to the default filename
inf dataset download ds_abc123
# Download to a specific file
inf dataset download ds_abc123 --output ./data/my-dataset.jsonl
# Download source-backed JSONL (request/response objects)
inf dataset download customer-support-eval --format source-backed
# Pick interactively when no id is provided
inf dataset download
```
## `inf dataset delete` [#inf-dataset-delete]
Archive a dataset — a soft delete that hides it from normal listings. Accepted IDs are full UUIDs, for example `a1b2c3d4-...`.
```bash
inf dataset delete
```
**Alias:** `inf dataset archive`
### Arguments [#arguments-3]
| Argument | Required | Description |
| -------- | -------- | --------------------------------------- |
| `id` | Yes | Full dataset UUID (e.g. `a1b2c3d4-...`) |
### Options [#options-4]
| Flag | Required | Description | Default |
| ----------- | -------- | --------------------------------------------- | ------- |
| `-y, --yes` | No | Skip the confirmation prompt (for scripts/CI) | Off |
The command prompts for confirmation unless `-y` is passed.
### Examples [#examples-4]
```bash
# Confirm interactively
inf dataset delete a1b2c3d4-1234-5678-9abc-def012345678
# Archive without prompting (scripts/CI)
inf dataset delete a1b2c3d4-1234-5678-9abc-def012345678 --yes
```
# Deployment Instances (https://docs.inference.net/cli/deployment-instance)
Drill into one instance of a deployment — its runtime detail, live GPU/CPU/memory gauges, and logs. Instance mutations are super-admin-only and stay in the web UI.
## `inf deployment-instance get` [#inf-deployment-instance-get]
Get a deployment instance's detail (redacted for non-admins).
```bash
inf deployment-instance get --instance-id
```
Run `inf deployment instances ` first to find the instance ID.
## `inf deployment-instance metrics` [#inf-deployment-instance-metrics]
Get an instance's sparkline gauges (GPU, CPU, memory).
## `inf deployment-instance logs` [#inf-deployment-instance-logs]
Query an instance's logs (paginated).
## Generated REST verbs [#generated-rest-verbs]
`inf deployment-instance` exposes its full read surface as generated commands. Run `inf deployment-instance --help` to list them and their flags.
## Related commands [#related-commands]
* [`inf deployment instances `](/cli/deployment#inf-deployment-instances) lists an instance's siblings and status.
# Deployments (https://docs.inference.net/cli/deployment)
Model deployments serve a fine-tuned or catalog model within a project, backed by allocated GPU instances. Use `inf deployment` to create a deployment, inspect its effective config and instances, and manage its lifecycle — all without opening the dashboard.
**Alias:** `inf deployments`
## Quickstart [#quickstart]
```bash
# See what a model may be deployed on (GPU families and counts)
inf deployment allowed-instance-types --model-id
# Create a deployment and watch the rollout converge
inf deployment create \
--name my-deployment \
--model-id \
--public-model-identifier /
inf deployment watch
# Inspect the deployment and the engine config it serves with
inf deployment get
inf deployment config
# Stop serving (reversible) or archive to free a slot
inf deployment stop --yes
inf deployment start
```
## Super-admin pricing rules [#super-admin-pricing-rules]
Super-admins can set scheduled effective prices for a public serverless deployment. Prices are entered in USD per 1M tokens and stored as fixed per-token rates. Omitting `--customer-team` creates an all-team rule; a matching team rule takes precedence over an all-team rule.
```bash
inf admin deployments pricing-rules list
inf admin deployments pricing-rules create \
--customer-team \
--input 1.05 \
--cached-input 0.115 \
--output 5.475 \
--starts-at 2026-09-20T00:00:00Z \
--ends-at 2026-09-27T00:00:00Z \
--reason "Partner launch pricing"
inf admin deployments pricing-rules remove \
--rule \
--yes
```
`--cached-input` defaults to the input price. Higher `--priority` wins among otherwise matching rules; disabled, future, and expired rules do not apply. The deployment's standard prices remain the fallback.
## `inf deployment list` [#inf-deployment-list]
List deployments for a team with their status, lifecycle, and served model alias.
```bash
inf deployment list
```
**Alias:** `inf deployment ls`
### Options [#options]
| Flag | Required | Description | Default |
| ----------------- | -------- | ---------------------------------------- | --------------------------- |
| `--team ` | No | Team ID | `INF_TEAM_ID` / active team |
| `--project ` | No | Filter results to a single project ID | — |
| `-l, --limit ` | No | Max results to return (positive integer) | `50` |
Shows the deployment ID (8-char prefix), name, status, lifecycle state, public model identifier, and creation date.
## `inf deployment get` [#inf-deployment-get]
Show a single deployment's full details — status, lifecycle, model alias, and timestamps. Omit the `id` to pick interactively.
```bash
inf deployment get
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------- |
| `id` | No | Deployment ID; omit to pick interactively |
### Options [#options-1]
| Flag | Required | Description |
| ------------- | -------- | ----------- |
| `--team ` | No | Team ID |
## `inf deployment config` [#inf-deployment-config]
Show the effective engine configuration a deployment serves with: the resolved flags, environment variables, and which layer set each.
```bash
inf deployment config
```
### Arguments [#arguments-1]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------- |
| `id` | No | Deployment ID; omit to pick interactively |
### Options [#options-2]
| Flag | Required | Description |
| ------------- | -------- | ----------- |
| `--team ` | No | Team ID |
## `inf deployment instances` [#inf-deployment-instances]
List a deployment's running and terminated instances with their status, version, and stop reason.
```bash
inf deployment instances
```
**Alias:** `inf deployment inst`
### Arguments [#arguments-2]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------- |
| `id` | No | Deployment ID; omit to pick interactively |
### Options [#options-3]
| Flag | Required | Description | Default |
| ----------------- | -------- | ---------------------------------------- | ----------- |
| `--team ` | No | Team ID | Active team |
| `-l, --limit ` | No | Max results to return (positive integer) | `100` |
## `inf deployment watch` [#inf-deployment-watch]
Poll a deployment until its rollout converges — status `Active` and the target version `Running`.
```bash
inf deployment watch
```
### Arguments [#arguments-3]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------- |
| `id` | No | Deployment ID; omit to pick interactively |
### Options [#options-4]
| Flag | Required | Description | Default |
| ----------------------- | -------- | -------------------------------------------------- | ----------------------- |
| `--team ` | No | Team ID | Active team |
| `--target-version ` | No | Require this version to be the live, `Running` one | Current desired version |
| `--timeout ` | No | Max seconds to watch before giving up | `1800` |
| `--interval ` | No | Seconds between polls | `10` |
Exits `0` on convergence; fails if the timeout elapses.
## `inf deployment stop` / `start` [#inf-deployment-stop--start]
`stop` archives a deployment so it stops serving (alias of `archive`). `start` restarts a previously archived deployment so it resumes serving. Both are reversible.
```bash
inf deployment stop --yes
inf deployment start
```
### Options [#options-5]
| Flag | Required | Description | Default |
| ------------- | ----------------------------------------- | ---------------------------- | ----------- |
| `--team ` | No | Team ID | Active team |
| `-y, --yes` | Yes in non-TTY environments (`stop` only) | Skip the confirmation prompt | Off |
`stop` prompts for confirmation in an interactive terminal; in non-TTY environments (CI, scripts) it refuses to run without `-y`.
## `inf deployment archive` [#inf-deployment-archive]
Archive a deployment so it stops serving and frees a slot against the team cap. Reversible with `start`.
```bash
inf deployment archive --yes
```
### Arguments [#arguments-4]
| Argument | Required | Description |
| -------- | -------- | ----------------------------------------- |
| `id` | No | Deployment ID; omit to pick interactively |
### Options [#options-6]
| Flag | Required | Description | Default |
| ------------- | --------------------------- | ---------------------------- | ----------- |
| `--team ` | No | Team ID | Active team |
| `-y, --yes` | Yes in non-TTY environments | Skip the confirmation prompt | Off |
## `inf deployment create` [#inf-deployment-create]
Create a deployment with its full config version.
```bash
inf deployment create \
--name \
--model-id \
--public-model-identifier /
```
### Options [#options-7]
| Flag | Required | Description | Default |
| ------------------------------------- | -------- | --------------------------------------------------------------------- | -------------- |
| `--name ` | Yes | Deployment name | — |
| `--model-id ` | Yes | Model to deploy | — |
| `--public-model-identifier ` | Yes | Public model slug, e.g. `/` | — |
| `--project-id ` | No | Project to create the deployment in | Active project |
| `--team-id ` | No | Team to create the deployment in | Active team |
| `--desired-instance-count ` | No | Instance count to scale to | — |
| `--instance-requirements ` | No | GPU instance requirements (see `allowed-instance-types`) | — |
| `--is-serverless-deployment ` | No | Deploy as a serverless model | `false` |
| `--model-config-flag-overrides ` | No | Engine flag overrides | — |
| `--model-config-version-override ` | No | Pin a specific model-config version | — |
| `--billing-exempt ` | No | Exempt the deployment from billing | `false` |
| `--queue-timeout-ms ` | No | Max wait for a free slot before a `429`; use `-1` to disable queueing | Engine default |
| `--total-req-timeout-ms ` | No | Positive wall-clock cap on the whole stream | Engine default |
| `--time-to-next-token-timeout-ms ` | No | Max gap between generation progress | Shared default |
| `--accepted-control-tower-ids ` | No | Control towers this deployment's instances may be placed on | Any tower |
These three timeout flags require super-admin access. They also work with `inf deployment update --id `. Pass `null` to restore the inherited default.
`--accepted-control-tower-ids` also requires super-admin access and takes a
comma-separated list. An empty list means any eligible tower, which is the
default. The set constrains where *new* instances are placed: instances already
running are never moved when it changes. The super-admin CLI spells the same
setting as a repeatable `inf admin deployment create|update --accepted-control-tower `, with `--clear-control-towers` to empty it.
Prints the new deployment ID. Run `inf deployment watch ` to track its rollout.
### Examples [#examples]
```bash
# Deploy on 1 GPU instance and watch the rollout converge
inf deployment create \
--name support-model \
--model-id \
--public-model-identifier acme/support-v1 \
--desired-instance-count 1
inf deployment watch
# Override an engine flag to lower max tokens served
inf deployment create \
--name support-model \
--model-id \
--public-model-identifier acme/support-v1 \
--model-config-flag-overrides '{"max_tokens": 4096}'
```
## Generated REST verbs [#generated-rest-verbs]
`inf deployment` also exposes the full deployment API surface as generated commands — `versions`, `update`, `engine-config`, `engine-configs`, `inferences`, `inference-filter-options`, `public-identifier-available`, `training-job-for-model`, `open-source-models`, and `allowed-instance-types`. Run `inf deployment --help` to list them and their flags.
# Evals (https://docs.inference.net/cli/evals)
Run and inspect model evaluations from the command line. Manage rubrics (the judge prompts evals run against), list and inspect run groups, launch new runs, and browse eval-ready datasets.
**Alias:** `inf evals`
The full eval loop is paste-able from the terminal:
```bash
# 1. Create a rubric from a markdown file
inf eval rubric create -n support-tickets-v1 -f ./rubric.md
# → Rubric rub_abc12 / version rv_xyz45 created.
# 2. Materialize an eval dataset (traffic-backed, upload-backed, or from a file)
inf dataset create -n demo-eval -t eval --file ./samples.jsonl
# → Dataset ds_def78 created.
# 3. Launch an eval run group
inf eval run \
--rubric-id rub_abc12 \
--dataset-id ds_def78 \
--models openai:gpt-5.2,anthropic:claude-sonnet-4-6 \
--judge-model anthropic:claude-sonnet-4-6
# → Run group rg_20260415_152340 created.
# 4. Track progress
inf eval get rg_20260415_152340
```
Route IDs look like `:` (e.g. `openai:gpt-5.2`). Use [`inf models list`](/cli/models#inf-models-list) to discover every route ID available to your team — see [Route IDs](/cli/models#route-ids) for the full format.
## `inf eval rubric create` [#inf-eval-rubric-create]
Create a rubric — the judge prompt an eval run scores responses against. Rubrics live in the active project, carry versioned prompt content, and are passed to `inf eval run` by ID. The template must contain the placeholder `{{ eval_model_response }}` where the model's response will be injected for scoring.
```bash
inf eval rubric create -n -f
```
### Options [#options]
| Flag | Required | Description | Default |
| ------------------- | -------- | ------------------------------------------------------------ | -------------- |
| `-n, --name ` | Yes | Rubric name | — |
| `-f, --file ` | Yes | Path to a markdown file containing the judge prompt template | — |
| `--max-score ` | No | Maximum score for the rubric (2–100) | `10` |
| `--project-id ` | No | Project to create the rubric in | Active project |
Prints the rubric ID and the first version ID. Use them directly with `inf eval run`.
### Examples [#examples]
```bash
# Create a rubric with the default 0–10 scoring scale
inf eval rubric create -n support-tickets-v1 -f ./rubric.md
# Create a rubric with a 0–100 scale
inf eval rubric create -n quality-v2 -f ./quality.md --max-score 100
```
## `inf eval rubric get` [#inf-eval-rubric-get]
Get details of a rubric — ID, name, latest version number, version count, score range, and a preview of the template.
```bash
inf eval rubric get
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | ---------------------------------------------------- |
| `id` | Yes | Full UUID, 4+ character prefix, or exact rubric name |
Ambiguous prefixes print the candidate list and abort.
## `inf eval rubric delete` [#inf-eval-rubric-delete]
Archive (soft-delete) a rubric. Rubrics cannot be hard-deleted — archiving hides them from `inf eval rubrics` but preserves their eval history. Restore from the dashboard if needed.
```bash
inf eval rubric delete
```
**Alias:** `inf eval rubric archive ` — both names do the same thing; use whichever reads clearer in your script.
### Arguments [#arguments-1]
| Argument | Required | Description |
| -------- | -------- | ---------------------------------------------------- |
| `id` | Yes | Full UUID, 4+ character prefix, or exact rubric name |
### Options [#options-1]
| Flag | Required | Description | Default |
| ----------- | --------------------------- | ---------------------------- | ------- |
| `-y, --yes` | Yes in non-TTY environments | Skip the confirmation prompt | Off |
In an interactive terminal, the CLI asks for confirmation unless `-y` is passed. In non-TTY environments (CI, scripts) the command refuses to run without `-y`.
### Examples [#examples-1]
```bash
# Archive interactively (prompts for confirmation)
inf eval rubric delete support-tickets-v1
# Archive non-interactively
inf eval rubric archive rub_abc12 --yes
```
## `inf eval rubrics` [#inf-eval-rubrics]
List rubrics in the active project.
```bash
inf eval rubrics
```
**Alias:** `inf eval defs`
### Options [#options-2]
| Flag | Required | Description | Default |
| -------------------- | -------- | ------------------------ | ------- |
| `--include-archived` | No | Include archived rubrics | Off |
Shows the rubric ID (8-char prefix), name, latest version, total version count, and creation date. Use `--json` for full UUIDs.
## `inf eval run` [#inf-eval-run]
Launch a new eval run group against one or more models, scored by a judge model.
```bash
inf eval run \
--rubric-id \
--dataset-id \
--models \
--judge-model
```
### Options [#options-3]
| Flag | Required | Description | Default |
| -------------------------- | -------- | -------------------------------------------------------------------------- | -------------- |
| `--rubric-id ` | Yes | Rubric ID | — |
| `--dataset-id ` | Yes | Eval-type dataset ID (create one with `inf dataset create -t eval`) | — |
| `--models ` | Yes | Comma-separated model route IDs — run `inf models list` to discover them | — |
| `--judge-model ` | Yes | Route ID of the judge model — run `inf models list --judge-only` to filter | — |
| `--rubric-version-id ` | No | Pin to a specific rubric version | Latest version |
| `--sample-size ` | No | Samples drawn from the dataset per model (1–100) | `100` |
| `-n, --name ` | No | Display name for the run group | Auto-generated |
Prints the run group ID and an `inf eval get ` follow-up command to track progress.
### Examples [#examples-2]
```bash
# Launch a run against two models with a third as judge
inf eval run \
--rubric-id rub_abc12 \
--dataset-id ds_def78 \
--models openai:gpt-5.2,anthropic:claude-sonnet-4-6 \
--judge-model anthropic:claude-sonnet-4-6
# Pin to a specific rubric version
inf eval run \
--rubric-id rub_abc12 \
--rubric-version-id rv_xyz45 \
--dataset-id ds_def78 \
--models openai:gpt-5.2 \
--judge-model anthropic:claude-sonnet-4-6
```
## `inf eval list` [#inf-eval-list]
List eval run groups for a given rubric.
```bash
inf eval list --rubric-id
```
**Alias:** `inf eval ls`
### Options [#options-4]
| Flag | Required | Description | Default |
| -------------------------- | -------- | ----------------------------------- | ------- |
| `--rubric-id ` | Yes | Rubric ID to list runs for | — |
| `--rubric-version-id ` | No | Filter by a specific rubric version | — |
Shows the run group ID (8-char prefix), rubric version, model count, derived status (`pending`, `running`, `failed`, or `completed`), and creation date.
## `inf eval get` [#inf-eval-get]
View detailed information about a specific eval run group.
```bash
inf eval get
```
### Arguments [#arguments-2]
| Argument | Required | Description |
| -------- | -------- | --------------------- |
| `id` | Yes | The eval run group ID |
### Output [#output]
The detail view covers the run group itself, followed by a sub-table of individual runs:
| Field | Description |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Run group ID |
| `rubricId` / `rubricVersionId` | Rubric and pinned version |
| `evalDatasetId` | Dataset the run group scored |
| `judgeProvider` / `judgeModelId` | Judge model scoring the responses |
| `models` | How many models were evaluated in this run group |
| `created` | Run group creation timestamp |
| Runs sub-table | One row per model: run ID, provider, model, status, average score, failed sample count, `completed/total` samples. When avg score is `—`, the adjacent `N failed` hint shows how many samples the judge couldn't score. |
## `inf eval datasets` [#inf-eval-datasets]
List datasets available for evaluations (type = `eval`).
```bash
inf eval datasets
```
### Options [#options-5]
| Flag | Required | Description | Default |
| -------------------- | -------- | ------------------------- | ------- |
| `-l, --limit ` | No | Maximum number of results | `50` |
| `--include-archived` | No | Include archived datasets | Off |
Eval datasets are materialized via [`inf dataset create -t eval …`](/cli/datasets#inf-dataset-create) or the dashboard. The output shows the dataset ID (8-char prefix), name, inference count, and creation date.
# GitHub (https://docs.inference.net/cli/github)
Inspect a team's GitHub App integration: connection status, synced repositories, and branches. Connect/disconnect flows are browser-based and stay in the web UI.
## Commands [#commands]
Run `inf github --help` for each verb's options. The generated surface covers:
| Verb | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------------ |
| `installation` | Get a team's GitHub App connection status |
| `install-url` | Get the GitHub App install URL for a team |
| `repositories` | List a team's synced GitHub repositories |
| `branches` | List branches for a connected GitHub repository |
| `repo-binding-get` / `repo-binding-set` / `repo-binding-clear` | Get, bind, or clear a GitHub repo + branch to an agent or task |
| `connect-url` | Get the GitHub OAuth connect URL for a team |
| `installation-selection` / `installation-selection-attach` | List and attach selectable GitHub installations for a connect flow |
| `disconnect` | Disconnect a team's GitHub App installation |
## Related commands [#related-commands]
* [`inf agent`](/cli/agent) — repo bindings attach a GitHub repo + branch to an agent or task.
# Inferences (https://docs.inference.net/cli/inferences)
Inspect inference requests and responses captured by Gateway. List and filter inferences in the active project, sort by tokens / cost / latency, discover the available filter values, and fetch the complete stored request and response bodies.
**Alias:** `inf inferences`
## `inf inference list` [#inf-inference-list]
List and filter inferences in the active project.
```bash
inf inference list
```
**Alias:** `inf inference ls`
### Options [#options]
| Flag | Required | Description | Default |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `-l, --limit ` | No | Maximum number of results (1–100) | `20` |
| `--sort ` | No | Sort key: `sent_at`, `http_code`, `cost`, `duration`, `tokens`, `input_tokens`, `output_tokens`, `input_size`, `output_size` | `sent_at` |
| `--order ` | No | Sort order | `desc` |
| `--filter ` | No | Numeric filter `` (repeatable). Fields: `inputTokens`, `outputTokens`, `inputSizeBytes`, `outputSizeBytes`, `durationMs`, `totalCost`. Ops: `> >= < <= = !=` | — |
| `--metadata ` | No | Metadata filter `` (repeatable). Ops: `= != ~ !~`, plus ` is_empty` / ` is_not_empty` | — |
| `-m, --model ` | No | Filter by model (repeatable or comma-separated) | All models |
| `--provider ` | No | Filter by downstream provider (repeatable or comma-separated) | All |
| `--environment ` | No | Filter by environment (repeatable or comma-separated) | All |
| `--status ` | No | Filter by HTTP status code, e.g. `200,500` (repeatable or comma-separated) | All |
| `--task ` | No | Filter by task ID — matches the `x-inference-task-id` header set by [`inf instrument --task-id`](/cli/instrument) (repeatable or comma-separated) | — |
| `--upload ` | No | Filter by upload ID (repeatable or comma-separated) | — |
| `--stream ` | No | Filter by streamed responses (`true` / `false`) | — |
| `--from ` / `--to ` | No | Absolute time range (ISO-8601). Use both together. | — |
| `--range ` | No | Relative time range: `1h, 6h, 12h, 1d, 3d, 7d, 14d, 30d, 90d, all` | — |
| `--cursor ` | No | Pagination cursor (from a previous `--json` response) | — |
| `--count` | No | Also compute the total match count (slower on large projects) | Off |
Don't hardcode filter values — run [`inf inference facets`](#inf-inference-facets) to discover the valid models, providers, environments, tasks, and metadata keys for the active project, along with the full filter reference.
The human-readable table shows the inference ID, model, status code (color-coded), input/output token counts, latency, cost, and timestamp. Add the global `--json` flag to emit the **full result object** — `{ items, nextCursor, hasMore, totalCount }` — so scripts can read `.items` and paginate by passing `.nextCursor` back via `--cursor`.
### Examples [#examples]
```bash
# List the 10 most recent inferences
inf inference list --limit 10
# Largest-input requests first — e.g. find prompts over 39k tokens
inf inference list --sort input_tokens --order desc --limit 50
inf inference list --filter inputTokens>39000
# Combine numeric, array, and metadata filters over a time window
inf inference list --model meta-llama/llama-3.1-8b-instruct/fp-8 --status 200 --range 7d
inf inference list --filter durationMs>5000 --metadata user_id=abc123
# Paginate from a script — JSON output includes nextCursor
cursor=$(inf --json inference list --sort input_tokens --order desc | jq -r '.nextCursor')
inf --json inference list --sort input_tokens --order desc --cursor "$cursor"
```
## `inf inference facets` [#inf-inference-facets]
Probe the active project for the values you can filter on — models, providers, environments, task IDs, and metadata keys — plus the full numeric/metadata/sort reference. Values are discovered from your data, not hardcoded, which makes this the starting point for building a filtered `inf inference list` query (and a one-call way for an AI agent to learn the filter surface).
**Alias:** `inf inference filters`
```bash
inf inference facets
```
Add the global `--json` flag to emit a machine-readable object combining the probed values with a static `reference` (numeric fields, operators, and sort keys).
## `inf inference get` [#inf-inference-get]
Fetch the **complete stored request and response** for a specific inference — method, path, headers, and the full request/response bodies. The sides come back `null` when payload storage was disabled for the request, and a side can also come back `null` when the blob exceeded the ingest size limit (32 MiB request bodies are refused at the edge with `413`; oversized streamed responses are recorded as metadata-only rows) — in that case `payloadTooLarge` is `true` and usage, cost, and metadata remain complete. Useful for debugging specific calls, inspecting model behavior, or archiving payloads.
```bash
inf inference get
```
### Arguments [#arguments]
| Argument | Required | Description |
| -------- | -------- | -------------------------------------------- |
| `id` | Yes | Full inference UUID or a 4+ character prefix |
### Options [#options-1]
| Flag | Required | Description | Default |
| --------------------- | -------- | ----------------------------------------------------------------------------------------------- | ------- |
| `--request` | No | Output only the request (omit the response) | Both |
| `--response` | No | Output only the response (omit the request) | Both |
| `--body` | No | Output only the raw body content (no method/path/headers). Requires `--request` or `--response` | — |
| `-o, --output ` | No | Write the output to a file instead of stdout | stdout |
Add the global `--json` flag to print the full `{ request, response, payloadTooLarge }` payload (method, path, headers, and complete bodies) as clean JSON for piping into `jq` or saving. `payloadTooLarge` is `true` when a payload blob exceeded the ingest size limit and was not stored — the affected sides come back `null`.
### Examples [#examples-1]
```bash
# Using a 4+ character UUID prefix (human-readable view)
inf inference get inf_abc1
# Pull a full UUID from list output, then fetch it
inf inference list --json | jq -r '.items[0].id' | xargs inf inference get
# Full request + response as clean JSON, then extract the response body
inf --json inference get | jq '.response.body'
# Dump just the raw response body to a file
inf inference get --response --body -o response.json
```
# Instrument (https://docs.inference.net/cli/instrument)
`inf instrument` hands your codebase to an AI coding agent (Claude Code, OpenCode, or Codex) to wire up Inference platform observability for you. It scans your project, finds your LLM clients — OpenAI, Anthropic, LangChain, Amazon Bedrock, Gemini, Groq, Cerebras, OpenRouter, and others — and rewrites them to route through the Inference.net gateway so every call is captured in Gateway.
This is the fastest way to connect an existing app to the Inference platform. You don't need to know the SDK changes in advance — the agent figures it out from your code.
## Prerequisites [#prerequisites]
* A signed-in CLI session. `inf instrument` requires a session login — API-key auth is rejected. Run `inf auth login` first.
* A supported AI coding agent on your `PATH`. Install one of:
| Agent | Binary | Install |
| ----------- | ---------- | ------------------------------------------------ |
| Claude Code | `claude` | [docs.anthropic.com/en/docs/claude-code][claude] |
| OpenCode | `opencode` | [opencode.ai][opencode] |
| Codex | `codex` | [github.com/openai/codex][codex] |
[claude]: https://docs.anthropic.com/en/docs/claude-code
[opencode]: https://opencode.ai
[codex]: https://github.com/openai/codex
## `inf instrument` [#inf-instrument]
Instrument the current working directory. The command walks you through:
1. Confirming the target project (auto-selected if you only have one).
2. Detecting installed coding agents and picking one (prompted if you have more than one).
3. Downloading the instrumentation skill and building the agent prompt.
4. Creating a fresh project-scoped API key for this run. The key is passed to the agent through the `INFERENCE_API_KEY` environment variable, never in the prompt, so it is not sent to the agent's model provider.
5. Launching the agent interactively so you can review and approve the changes.
```bash
cd /path/to/your/project
inf instrument
```
### Options [#options]
| Flag | Required | Description | Default |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `--dry-run` | No | Ask the agent to preview changes without modifying files | Off |
| `--api-key ` | No | Plaintext API key to wire into the project, passed to the agent via environment variables (never in the prompt). Omit to create a fresh project-scoped key — stored keys are hashed, so a key's plaintext can never be read back | New key |
| `--agent ` | No | Preselect the coding agent by binary name: `claude`, `opencode`, or `codex`. Skips the interactive picker — useful in CI, scripts, or when driving `inf instrument` from another agent | Prompt |
| `-y, --yes` | No | Run non-interactively. When `--mode` is omitted, defaults it to `both` instead of prompting | Off |
| `--print-prompt` | No | Fetch the instrumentation skill, build the full agent prompt, print it to stdout, and exit without spawning an agent. Lets another orchestrator (your own agent harness, a CI job) drive the agent itself | Off |
| `--task-id ` | No | Default task id the agent tags every call with via the `x-inference-task-id` header. `inf inference list --task …` and `inf dataset create --task …` filter on this. 1–64 alphanumeric chars plus `._:-` | `default` |
| `--mode ` | No | Which product(s) to instrument: `gateway` (proxy routing only), `tracing` (span collection only), or `both`. Omit to pick interactively | Interactive |
### What the agent does [#what-the-agent-does]
Once the agent launches, it:
* Scans your codebase for LLM clients (OpenAI, Anthropic, LangChain, Amazon Bedrock, Gemini, Groq, Cerebras, OpenRouter, and others).
* Redirects base URLs to the Inference.net gateway.
* Adds routing headers so requests are authenticated, forwarded, and traced.
* Tags every call with the default `x-inference-task-id` so call sites group automatically in Gateway.
* Shows you a diff before applying changes.
Your app keeps using the same provider SDKs it already has — `inf instrument` rewrites the client construction, not your call sites.
### How the API key is handled [#how-the-api-key-is-handled]
* Each run creates a fresh project-scoped API key. The command prints the revoke command (`inf api-key revoke `) so you can invalidate it later.
* The key reaches the agent as `INFERENCE_API_KEY` in every mode (gateway, tracing, and both). It is never placed in the agent prompt, so it is never sent to the agent's model provider.
* The agent writes the key into your env file with shell expansion (`"$INFERENCE_API_KEY"`). The plaintext value never appears in the agent transcript or in `inf instrument` output.
* Keys are stored hashed and cannot be shown again. If the agent leaves a placeholder in your env file, create a new key with `inf api-key create `.
### Examples [#examples]
```bash
# Preview what would change without touching any files
inf instrument --dry-run
# Use Claude Code non-interactively (skip the agent picker and the confirmation)
inf instrument --agent claude --yes
# Tag every instrumented call with a custom task id for filtering later
inf instrument --task-id support-chatbot
# Print the prompt for your own agent orchestrator instead of spawning one
inf instrument --print-prompt > instrument-prompt.md
```
### Verify instrumentation [#verify-instrumentation]
After the agent finishes, run your app to generate a few LLM calls, then confirm they're captured:
```bash
inf inference list
```
Or open the observability dashboard link the command prints on completion.
## Supported providers [#supported-providers]
**Built-in:** OpenAI, Anthropic.
**OpenAI-compatible via `x-inference-provider-url`:** Amazon Bedrock, Google Gemini, Together AI, Groq, Fireworks AI, Mistral AI, Cerebras, Perplexity, DeepSeek, OpenRouter, Azure OpenAI, and any OpenAI-compatible endpoint.
## Troubleshooting [#troubleshooting]
**`inf instrument now requires a session login. Unset INF_API_KEY and run inf auth login.`**
The command refuses to run with `INF_API_KEY` set in your environment. Unset it and sign in with `inf auth login` — instrumentation needs your session to create a project-scoped API key on your behalf.
**`No supported AI coding agent found.`**
Install one of Claude Code, OpenCode, or Codex from the links in [Prerequisites](#prerequisites). `inf instrument` detects the binary on your `PATH`.
**`--agent is not installed (or not supported).`**
The binary name must match exactly: `claude`, `opencode`, or `codex`. Check `which claude` (or the name you passed) resolves to an executable.
**`Failed to create a project API key`**
Your session may lack permission to create keys for this project, or the project requires extra verification. Create a key in the dashboard (the relevant project → **API Keys**) and pass it with `--api-key `. The key is handed to the agent through environment variables, not the prompt.
**The agent exited with an error.**
`inf instrument` prints the dashboard URL for manual integration on failure. You can also rerun with `-v` to see debug output, including the skill URL and prompt length.
# Integration (https://docs.inference.net/cli/integration)
Inspect external integrations connected to your team. Today that's Slack — check whether Slack is connected, which workspace it's linked to, and which channels the integration can post to.
## `inf integration slack status` [#inf-integration-slack-status]
Show whether Slack is connected, the linked workspace, bot scopes, and install date.
```bash
inf integration slack status
```
### Options [#options]
| Flag | Required | Description | Default |
| ------------- | -------- | -------------------- | ----------- |
| `--team ` | No | Team UUID to inspect | Active team |
## `inf integration slack channels` [#inf-integration-slack-channels]
List the Slack channels the integration can see, with their IDs and private/public status.
```bash
inf integration slack channels
```
### Options [#options-1]
| Flag | Required | Description | Default |
| ------------- | -------- | -------------------- | ----------- |
| `--team ` | No | Team UUID to inspect | Active team |
### Examples [#examples]
```bash
# Check whether Slack is connected and which workspace it's linked to
inf integration slack status
# List the Slack channels the integration can post to
inf integration slack channels
# Target a specific team instead of the active one
inf integration slack channels --team 123e4567-e89b-12d3-a456-426614174000
```
## Related commands [#related-commands]
* [`inf slack`](/cli/slack) manages the Slack connection itself (install URL, disconnect).
* [`inf notifications`](/cli/notifications) manages Slack notification subscriptions.
# Models (https://docs.inference.net/cli/models)
`inf models` lets you browse every model available to your active team — both platform-provided models and any BYOK (bring-your-own-key) routes your team has configured.
**Alias:** `inf model`
`inf eval run` takes model route IDs via `--models` and `--judge-model`. `inf models list` is where you discover those route IDs.
## Route IDs [#route-ids]
Route IDs look like `:` — for example `openai:gpt-5.2`, `anthropic:claude-sonnet-4-6`, `cerebras:llama-3.3-70b`. They are the canonical identifier the CLI and API use to address a specific model route, and they're what [`inf eval run`](/cli/evals#inf-eval-run) expects for `--models` and `--judge-model`. Use `inf models list --json` to dump every route ID available to your team.
## `inf models list` [#inf-models-list]
Display every callable model visible to the active team, with provider, scope, capability flags, context window, and per-million-token pricing.
```bash
inf models list
```
**Alias:** `inf models ls`
### Options [#options]
| Flag | Required | Description | Default |
| ------------------- | -------- | --------------------------------------------------------------------------------------------------------- | ------------- |
| `--provider ` | No | Filter by provider name (case-insensitive exact match) — e.g. `openai`, `anthropic`, `google`, `cerebras` | All providers |
| `--scope ` | No | Filter by scope: `platform` (inf-public catalog) or `byok` (your team's own provider keys) | Both |
| `--judge-only` | No | Show only models that can act as a judge in evals | Off |
### Output [#output]
In table mode (default), each row shows:
| Column | Description |
| ---------- | ------------------------------------------------------------------------- |
| `Model` | Canonical alias (e.g. `gpt-5.2`, `claude-sonnet-4-6`, `gemini-2.5-flash`) |
| `Provider` | Provider brand (OpenAI, Anthropic, Google, Cerebras, …) |
| `Scope` | `platform` (inf-public) or `byok` (team-owned route) |
| `Context` | Max context window, rounded to the nearest 1k tokens |
| `Struct.` | Whether the model supports structured outputs (`yes` / `no`) |
| `Tools` | Whether the model supports tool / function calling |
| `Reason.` | Whether the model has a reasoning mode |
| `Judge` | Whether the model is allow-listed as an eval judge |
| `$/1M In` | Input price per million tokens |
| `$/1M Out` | Output price per million tokens |
### Examples [#examples]
```bash
# All callable models, table view
inf models list
# Only OpenAI routes
inf models list --provider openai
# Only models allow-listed as judges (for inf eval run --judge-model)
inf models list --judge-only
# Only BYOK routes
inf models list --scope byok
# Machine-readable — full routeId per row, good for piping into inf eval run
inf models list --json | jq -r '.[] | select(.judgeCapable == true) | .routeId'
```
### JSON mode [#json-mode]
`inf models list --json` emits the full enriched record per model, including the `routeId` string that `inf eval run --models` and `--judge-model` expect:
```json
[
{
"routeId": "openai:gpt-5.2-2025-12-11",
"canonicalAlias": "gpt-5.2",
"displayName": "GPT 5.2",
"providerName": "OpenAI",
"providerSlug": "openai",
"scope": "platform",
"maxContextSize": 128000,
"structuredOutputs": true,
"tools": true,
"reasoning": false,
"judgeCapable": true,
"costInputPerMToken": 2.5,
"costOutputPerMToken": 10
}
]
```
`inf models list --json | jq '.[] | select(.scope == "platform")'` is a quick way to prune your eval model set to just platform routes.
# Notifications (https://docs.inference.net/cli/notifications)
Inspect the notification types Inference emits and your email/Slack preferences for receiving them.
## Commands [#commands]
Run `inf notifications --help` for each verb's options. The generated surface covers:
| Verb | Description |
| --------------------------------------------------------- | --------------------------------------------------- |
| `types` | List every notification type and its channels |
| `email-preferences` | Get the caller's email notification preferences |
| `email-preferences-set` | Set one of the caller's email notification toggles |
| `slack-subscriptions` | List a team's Slack notification subscriptions |
| `slack-subscriptions-create` | Create a Slack notification subscription for a team |
| `update-slack-subscription` / `delete-slack-subscription` | Update or delete a Slack notification subscription |
## Related commands [#related-commands]
* [`inf slack`](/cli/slack) manages the underlying Slack channel connection.
# Install CLI (https://docs.inference.net/cli/overview)
The Inference CLI (`inf`) drives Inference platform from the terminal. It serves two main paths:
1. **Instrument your codebase** so every LLM call your app makes is captured in Gateway — `inf instrument` hands the job to an AI coding agent (Claude Code, OpenCode, or Codex) and walks you through the diff.
2. **Operate the platform programmatically** — browse models, manage rubrics and eval runs, upload and materialize datasets, queue and monitor training runs, and inspect captured inferences, traces, and spans without opening the dashboard.
`inf instrument` rewrites an app's LLM clients for observability. Routing a coding agent's own model traffic through the Inference.net gateway is the [`fast` CLI's](https://fast.inference.net/coding-agents) job, not `inf`'s.
Sign up for an account at [Inference.net](https://inference.net/register) to get started.
The CLI is currently in beta. Please report any issues you find.
## Quick Start [#quick-start]
```bash npm
npm install -g @inference/cli
```
```bash pnpm
pnpm add -g @inference/cli
```
```bash bun
bun add -g @inference/cli
```
```bash yarn
yarn global add @inference/cli
```
```bash
inf auth login
```
```bash
inf auth status
```
Run `inf --help` at any time to see every command. Having trouble? [Send us a message](mailto:support@inference.net) or tag us at [x.com/@inference\_net](https://x.com/inference_net).
Every command group below also exposes the full REST surface of its API resource as generated `inf ` commands, rendered directly from the Inference API. These aren't documented one-by-one here — discover them with `inf --help`. For example, `inf deployment --help` lists generated verbs like `versions`, `update`, and `public-identifier-available` alongside the hand-written ones.
## Global Options [#global-options]
These flags work on every command.
| Flag | Description |
| -------------------- | --------------------------------------------------- |
| `--json` | Output as JSON (preserves full UUIDs for scripting) |
| `-v, --verbose` | Verbose debug output |
| `-p, --project ` | Override the active project for this invocation |
| `-t, --team ` | Override the active team for this invocation |
| `-V, --version` | Show CLI version |
| `-h, --help` | Show help |
Tables show UUIDs as readable 8-character prefixes. For scripting, always use `--json` — it preserves full UUIDs so you can round-trip values between commands (e.g. `inf dataset list --json | jq -r '.[0].id' | xargs inf dataset get`).
## Commands [#commands]
| Command | Description |
| ----------------------------------------------- | ------------------------------------------------------------------------------- |
| [`inf instrument`](/cli/instrument) | Instrument your codebase for Inference platform observability using an AI agent |
| [`inf auth`](/cli/authentication) | Sign in, sign out, check authentication status, and show who you are |
| [`inf update`](/cli/update) | Upgrade a globally installed CLI to the latest version |
| [`inf team`](/cli/team) | List, switch between, create teams, and invite members |
| [`inf project`](/cli/projects) | List, switch between, and inspect projects, across teams |
| [`inf api-key`](/cli/api-key) | Create, list, and revoke project API keys |
| [`inf models`](/cli/models) | Browse callable models with capabilities and pricing |
| [`inf eval`](/cli/evals) | Manage rubrics, launch eval runs, inspect results |
| [`inf rubric`](/cli/rubric) | Inspect eval rubrics and their versions |
| [`inf compare`](/cli/compare) | Inspect AutoEvals analysis runs, list eval groups, poll runs |
| [`inf dataset`](/cli/datasets) | Upload JSONL data, create eval/training datasets, download |
| [`inf training`](/cli/training) | Queue training runs, monitor progress, view logs, and poll status |
| [`inf training-eval`](/cli/training-eval) | Inspect mid-training evals and manage reusable training recipes |
| [`inf deployment`](/cli/deployment) | Create, inspect, watch, and archive model deployments |
| [`inf agent`](/cli/agent) | Inspect auto-discovered agents and their execution stats |
| [`inf task`](/cli/task) | Inspect and manage a project's tasks |
| [`inf signals`](/cli/signals) | Create LLM classifiers that label spans/traces/sessions, plus alerts |
| [`inf notifications`](/cli/notifications) | Inspect notification types and email/Slack preferences |
| [`inf integration`](/cli/integration) | Inspect external integrations connected to your team (Slack) |
| [`inf slack`](/cli/slack) | Inspect and manage a team's Slack integration |
| [`inf github`](/cli/github) | Inspect a team's GitHub integration |
| [`inf search`](/cli/search) | Search across agents, deployments, datasets, and rubrics |
| [`inf starter-template`](/cli/starter-template) | Browse starter templates and track provisioning |
| [`inf upload`](/cli/upload) | Inspect inference uploads (status, list, download URL) |
| [`inf worker`](/cli/worker) | Inspect v1 worker instances and their logs |
| [`inf account-deletion`](/cli/account-deletion) | Schedule deletion of your account, a project, or a team |
| [`inf inference`](/cli/inferences) | View inference requests and responses captured by Gateway |
| [`inf trace`](/cli/traces) | Browse trace trees, timelines, facets, and exports |
| [`inf span`](/cli/spans) | Search spans and inspect span IO, attributes, events, and links |
| [`inf analyze-harness`](/cli/analyze-harness) | Run HALO agent-trace analyses and read the resulting reports |
| [`inf dashboard`](/cli/dashboard) | Launch the interactive terminal dashboard |
This table covers the most-used command groups. More exist — including `deployments`, `signals`, `tasks`, `compare`, and others — so run `inf --help` for the full list.
## Explore the CLI [#explore-the-cli]
Hand your project to an AI coding agent that wires up Inference platform for you.
Route Claude Code, Codex, Grok, OpenCode, and Pi through the Inference.net gateway with the fast CLI.
Browser and headless authentication, env vars, and config.
List, switch, create teams, and invite members.
Switch between projects and inspect the active project, across teams.
Create, list, and revoke project API keys.
Browse callable models, capabilities, and pricing.
Manage rubrics, launch eval runs, and inspect results.
Upload JSONL files and materialize eval or training datasets.
Queue training runs, monitor progress, view logs, and poll for completion.
Inspect request and response payloads captured by Gateway.
Browse trace trees, timelines, facets, and exports from the terminal.
Search spans and inspect captured IO, attributes, events, and links.
Run HALO agent-trace analyses, schedule recurring reports, and read the reports.
Interactive terminal UI for training runs, evals, datasets, and inferences.
Create, inspect, watch, and archive model deployments.
LLM classifiers that label spans, traces, and sessions — plus alerts.
Inspect AutoEvals runs, eval groups, and poll runs to completion.
## Usage telemetry [#usage-telemetry]
The CLI reports anonymized usage events so we can prioritize the commands our
customers actually rely on.
* **What we capture:** the command name, CLI version, OS and CPU architecture,
the JavaScript runtime, and the flag names (never flag *values*) you used.
When you run the CLI inside a git repository we also capture the repo
`owner/name` and current branch from your `origin` remote — this is most
useful for `inf instrument`, where we want to understand which codebases
Inference platform gets wired into.
* **What we never capture:** argument values, environment variables, file
contents, API keys, or any data you pass to a command.
* **When we don't capture anything:** events are only sent once you are
authenticated. Commands run before `inf auth login` / `inf auth set-key`
emit no events.
* **How it works:** events are sent fire-and-forget over tRPC with a short
timeout, so telemetry never slows down or blocks your command. Failures are
silent.
# Projects (https://docs.inference.net/cli/projects)
Projects organize your training runs, evaluations, datasets, and inferences. Most CLI commands operate on the active project, which is stored in `~/.inf/config.json` and settable per-invocation with the global `--project ` flag.
## `inf project list` [#inf-project-list]
Display every project you have access to in the active team. Switch teams first with `inf team switch ` (see [Teams](/cli/team)) to see a different team's projects. The active project is flagged with a green dot (`●`).
```bash
inf project list
```
**Alias:** `inf project ls`
### Example [#example]
```bash
inf project list
```
## `inf project switch` [#inf-project-switch]
Set a different project as the active project. The CLI validates that the project exists and that you have access before saving.
To switch to a project in a different team, activate that team first with `inf team switch ` (see [Teams](/cli/team)), or pass the global `--team ` flag on the same command so the CLI can find the project. On success, the CLI also stores that project's team as your new active team, so you do not need a separate `team switch` afterward.