# API Quickstart
Source: https://docs.inference.net/api/api-quickstart
Get started with the Inference.net API
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 an open-source model** — run models hosted on Inference.net directly.
2. **Proxy through Catalyst** — route requests to any provider (OpenAI, Anthropic, etc.) through the Catalyst gateway for observability, evals, and cost tracking.
3. **Call your custom model** — hit a model you've fine-tuned and deployed on the platform.
## 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 theme={"system"}
export INFERENCE_API_KEY=
```
***
## 1. Call an Open-Source Model
Run open-source models hosted on Inference.net. No provider API key needed — just your Inference API key. Browse available models at [inference.net/models](https://inference.net/models).
```typescript TypeScript theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
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 theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
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)
```
```bash cURL theme={"system"}
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",
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
],
"stream": true
}'
```
This works with any model on the platform, including our purpose-built [Schematron](/workhorse-models/schematron) models for structured data extraction.
***
## 2. Proxy Through Catalyst
Route requests to any LLM provider (OpenAI, Anthropic, Groq, etc.) through the Catalyst gateway. You keep your existing provider API key — the gateway adds observability, cost tracking, and eval-readiness with roughly 10ms of added latency.
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
| 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`, `groq`, `cerebras`, etc. |
| `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
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
Set up Catalyst 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
Source: https://docs.inference.net/api/async-inference/batch-api
Process jobs asynchronously with 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
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
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 theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://batch.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
base_url="https://batch.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL theme={"system"}
export INFERENCE_API_KEY=
# All Batch API requests use https://batch.inference.net/v1
```
## Running A Batch Processing Job
### 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 theme={"system"}
{"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 theme={"system"}
{"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
In order to create a Batch Processing job, you must first upload your input file.
```typescript TypeScript theme={"system"}
import fs from "fs";
const batchInputFile = await client.files.create({
file: fs.createReadStream("batchinput.jsonl"),
purpose: "batch",
});
console.log(batchInputFile);
```
```python Python theme={"system"}
batch_input_file = client.files.create(
file=open("batchinput.jsonl", "rb"),
purpose="batch",
)
print(batch_input_file)
```
```bash cURL theme={"system"}
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 theme={"system"}
{
"id": "file-abc123"
}
```
### 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`.
For now, the completion window can only be set to `24h`.
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"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 theme={"system"}
{
"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
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 theme={"system"}
const retrievedBatch = await client.batches.retrieve(batch.id);
console.log(retrievedBatch);
```
```python Python theme={"system"}
retrieved_batch = client.batches.retrieve(batch.id)
print(retrieved_batch)
```
```bash cURL theme={"system"}
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 24-hour time window |
| cancelling | the batch is being cancelled (may take up to 10 minutes) |
| cancelled | the batch was cancelled |
### 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 theme={"system"}
const fileResponse = await client.files.content(batch.output_file_id);
const fileContents = await fileResponse.text();
console.log(fileContents);
```
```python Python theme={"system"}
file_response = client.files.content(batch.output_file_id)
print(file_response.text)
```
```bash cURL theme={"system"}
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 theme={"system"}
{"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
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 theme={"system"}
const list = await client.batches.list({
limit: 10,
after: batch.id,
});
for await (const b of list) {
console.log(b);
}
```
```python Python theme={"system"}
batches = client.batches.list(limit=10, after=batch.id)
for b in batches:
print(b)
```
```bash cURL theme={"system"}
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
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 theme={"system"}
{"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
Batch API rate limits are separate from existing per-model rate limits. A single batch may include up to 50,000 requests, and a batch input file can be up to 200 MB in size. If you need higher rate limits, please contact us at [support@inference.net](mailto:support@inference.net).
## Compatibility Notes
### 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
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
Source: https://docs.inference.net/api/async-inference/group
Submit multiple asynchronous inference requests as a single group for easier tracking and webhook notifications.
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 both `/v1/slow/group/chat/completions` and `/v1/slow/group/completions` endpoints.
You should not mix completion and chat-completion requests in the same group.
## 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
| 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 | 1-72 hours | 1-72 hours |
## Getting Started
### 1. Submit a Group Request
Submit multiple requests together by sending them as an array in the request body:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"groupId": "group_xY3kL9mN2pQ",
"groupSize": 2
}
```
### 2. Retrieve Group Results
Once your group is processed, retrieve all generation results using the group ID:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"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
Attach a webhook to receive notifications when your group completes processing. Include the `webhook_id` when submitting the group:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
The Group API also supports text completions:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
* **Maximum requests per group:** 50
* **Request format:** Direct JSON (no JSONL files required)
* **Supported endpoints:**
* `/v1/slow/group/chat/completions`
* `/v1/slow/group/completions`
* **Completion time:** 24-72 hours
* **Request expiration:** Groups expire after 72 hours if not completed
## 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 theme={"system"}
{
"model": "google/gemma-3-27b-instruct/bf-16",
"messages": [{"role": "user", "content": "..."}],
"metadata": {
"custom_id": "doc_123",
"type": "summary"
}
}
```
## Error Handling
The API validates your request structure immediately. Common errors include:
```json JSON theme={"system"}
{
"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
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
Source: https://docs.inference.net/api/async-inference/overview
Make cost-effective inference requests with flexible completion times.
Learn how to use our OpenAI-compatible Asynchronous Inference API to send individual inference requests that complete within 24-72 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 only available for `/chat/completions` calls. Support for `/completions` will come later.
## 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-72 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
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
# 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
### 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"id": "N2mZQjrvh-k_m8nMMN7Jn",
"choices": [],
"created": 1749061362809,
"model": "google/gemma-3-27b-instruct/bf-16",
"object": "chat.completion"
}
```
### 2. Retrieve Results
Once your request is processed, retrieve the results using the generation endpoint:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"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
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
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
The Asynchronous Inference API supports the following endpoints:
* `/v1/slow/chat/completions`
* `/v1/slow/completions`
Simply replace `/v1/` with `/v1/slow/` in your existing code to use asynchronous processing.
## Pricing and Limits
* **Pricing:** Significantly reduced compared to synchronous requests (contact sales for specific rates)
* **Completion Time:** 24-72 hours
* **Rate Limits:** More generous than synchronous endpoints
* **Request Expiration:** Requests expire after 72 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
Source: https://docs.inference.net/api/async-inference/webhooks/getting-started-with-webhooks
Everything you need to know to get started with webhooks.
Webhook support is currently available for `/chat/completions` and `/embeddings` calls. Support for `/completions` will come later.
## 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
* **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
### 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 theme={"system"}
import express from "express";
const app = express();
app.post("/webhooks/inference", express.json(), (req, res) => {
const { event, webhook_id, generation_id, data } = req.body;
// Verify webhook source via headers
const webhookId = req.headers["x-inference-webhook-id"];
if (event === "generation.completed") {
console.log(`Generation ${generation_id} completed with status: ${data.state}`);
// Process asynchronously
setImmediate(() => {
processGenerationResult(data);
});
} else if (event === "async-embedding.completed") {
console.log(`Embedding ${generation_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 theme={"system"}
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
webhook_id: str
generation_id: Optional[str] = None
data: Dict[str, Any]
def process_generation(payload: WebhookPayload):
"""Process generation result asynchronously"""
if payload.event == "generation.completed":
print(f"Processing generation {payload.generation_id}")
# Your processing logic here
elif payload.event == "async-embedding.completed":
print(f"Processing embedding {payload.generation_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 theme={"system"}
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type WebhookPayload struct {
Event string `json:"event"`
Timestamp string `json:"timestamp"`
WebhookID string `json:"webhook_id"`
GenerationID string `json:"generation_id,omitempty"`
Data map[string]interface{} `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() {
if payload.Event == "generation.completed" {
fmt.Printf("Processing generation %s\n", payload.GenerationID)
// Your processing logic here
} else if payload.Event == "async-embedding.completed" {
fmt.Printf("Processing embedding %s\n", payload.GenerationID)
// 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
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
1. Navigate to the inference.net dashboard
2. Go to **API Keys** → **Webhooks** 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
Include the webhook identifier in the metadata when creating a generation:
```typescript TypeScript theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
messages: [{ role: "user", content: "Explain quantum computing" }],
// @ts-expect-error metadata is not in the OpenAI SDK types
metadata: {
webhook_id: "AhALzdz8S",
},
});
```
```python Python theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
messages=[{"role": "user", "content": "Explain quantum computing"}],
extra_body={
"metadata": {"webhook_id": "AhALzdz8S"},
},
)
```
```bash cURL theme={"system"}
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": "user", "content": "Explain quantum computing"}
],
"metadata": {
"webhook_id": "AhALzdz8S"
}
}'
```
When the generation completes, your webhook endpoint will receive a notification.
## Webhook Events
### generation.completed
Sent when a generation finishes processing (successfully or with failure):
```json JSON theme={"system"}
{
"event": "generation.completed",
"timestamp": "2025-01-03T06:46:22.838Z",
"webhook_id": "AhALzdz8S",
"generation_id": "XBKcs7F1s2oJ_AHiLMbF4",
"data": {
"state": "Success",
"stateMessage": "Generation successful",
"request": {
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum computing"}
],
"model": "google/gemma-3-27b-instruct/bf-16",
"stream": false,
"max_tokens": 100,
"metadata": {
"webhook_id": "AhALzdz8S"
}
},
"response": {
"id": "XBKcs7F1s2oJ_AHiLMbF4",
"object": "chat.completion",
"created": 1748933182,
"model": "google/gemma-3-27b-instruct/bf-16",
"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
}
},
"finishedAt": "2025-01-03T06:46:22.307Z"
}
}
```
The `response` object is compatible with the types exported from the official OpenAI SDKs.
```typescript TypeScript theme={"system"}
import type { OpenAI } from "openai";
const response = responseJsonObject as OpenAI.Chat.Completions.ChatCompletion;
```
```python Python theme={"system"}
from openai.types.chat.chat_completion import ChatCompletion
response: ChatCompletion = webhook_payload.data["response"]
```
### async-embedding.completed
Sent when an async embedding request finishes processing:
```json JSON theme={"system"}
{
"event": "async-embedding.completed",
"timestamp": "2025-01-15T10:30:00Z",
"webhook_id": "AhALzdz8S",
"generation_id": "EMB_abc123",
"data": {
"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
}
},
"finishedAt": "2025-01-15T10:30:00Z"
}
}
```
The `response` object follows the standard OpenAI embeddings format.
## 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 completion events) | `XBKcs7F1s2oJ_AHiLMbF4` |
| `User-Agent` | inference.net webhook agent | `inference.net-Webhook/1.0` |
| `Content-Type` | Always `application/json` | `application/json` |
### (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
You can test your webhook endpoint from the dashboard:
1. Navigate to **Webhooks** 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
### 1. Respond Immediately
Your endpoint must respond within 30 seconds. Always return a 200 status immediately and process the webhook asynchronously:
```typescript TypeScript theme={"system"}
// 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 theme={"system"}
# 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
Failed webhooks may be retried. Use the `generation_id` to ensure you don't process the same event twice:
```typescript TypeScript theme={"system"}
const processedGenerations = new Set();
async function processWebhook(payload: any) {
if (processedGenerations.has(payload.generation_id)) {
return; // Already processed
}
processedGenerations.add(payload.generation_id);
// Process the generation
}
```
```python Python theme={"system"}
processed_generations: set[str] = set()
def process_webhook(payload: WebhookPayload):
if payload.generation_id in processed_generations:
return # Already processed
processed_generations.add(payload.generation_id)
# Process the generation
```
### 3. Validate Webhook Source
Always verify that webhooks originate from inference.net by checking the presence of expected headers:
```typescript TypeScript theme={"system"}
function validateWebhookSource(headers: Record): boolean {
const requiredHeaders = ["x-inference-webhook-id", "x-inference-event"];
return requiredHeaders.every((header) => headers[header]);
}
```
```python Python theme={"system"}
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
Implement proper error handling to prevent individual failures from affecting your entire system:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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
Track key metrics to ensure reliable webhook processing:
* Webhook receipt rate
* Processing success/failure rates
* Average processing time
* Queue depth (if using queues)
## Troubleshooting
### 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
* 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
* Implement idempotency using the `generation_id`
* 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
**Q: What happens if my endpoint is down?**
A: Failed webhook deliveries are retried up to 3 times with exponential backoff. After all retries are exhausted, the delivery is marked as failed.
**Q: What's the webhook timeout?**
A: Webhook endpoints must respond within 30 seconds. Timeouts are treated as failures 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: Webhook payloads can be up to 10MB, though typical payloads range from 5-50KB.
**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
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
Source: https://docs.inference.net/api/async-inference/webhooks/quick-reference
Quick reference of webhook support for asynchronous inference
## Dashboard Management
Webhooks are managed through the inference.net dashboard:
1. Navigate to **Webhooks** on the sidebar
2. Create, test, archive, or restore webhooks through the UI
3. Copy your webhook identifier for use in generation requests
## Payload Structures
### generation.completed
```json JSON theme={"system"}
{
"event": "generation.completed",
"timestamp": "ISO 8601 timestamp",
"webhook_id": "webhook identifier",
"generation_id": "generation ID",
"data": {
"state": "Success|Failed",
"stateMessage": "Human readable status",
"request": { /* Original request with metadata */ },
"response": { /* OpenAI format response */ },
"finishedAt": "ISO 8601 timestamp"
}
}
```
### async-embedding.completed
```json JSON theme={"system"}
{
"event": "async-embedding.completed",
"timestamp": "ISO 8601 timestamp",
"webhook_id": "webhook identifier",
"generation_id": "generation ID",
"data": {
"state": "Success|Failed",
"stateMessage": "Human readable status",
"request": { /* Original embeddings request with metadata */ },
"response": { /* OpenAI format embeddings response */ },
"finishedAt": "ISO 8601 timestamp"
}
}
```
### slow-group.completed
```json JSON theme={"system"}
{
"event": "slow-group.completed",
"timestamp": "ISO 8601 timestamp",
"group_id": "group ID",
"data": {
"group_size": 2,
"status": "completed",
"generations": [
{
"generationId": "generation ID",
"state": "Success|Failed",
"stateMessage": "Human readable status",
"request": { /* Original request */ },
"response": { /* OpenAI format response */ },
"finishedAt": "ISO 8601 timestamp or null"
}
]
}
}
```
## 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 (if applicable) | `XBKcs7F1s2oJ_AHiLMbF4` |
| `X-Inference-Group-ID` | Group ID (for group events) | `GRP_XYZ123` |
| `User-Agent` | inference.net webhook agent | `inference.net-Webhook/1.0` |
| `Content-Type` | Always `application/json` | `application/json` |
## Using Webhooks in Generations
Include the webhook identifier in your generation request metadata:
### Chat Completions
```typescript TypeScript theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
messages: [{ role: "user", content: "Hello!" }],
// @ts-expect-error metadata is not in the OpenAI SDK types
metadata: { webhook_id: "YOUR_WEBHOOK_IDENTIFIER" },
});
```
```python Python theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
messages=[{"role": "user", "content": "Hello!"}],
extra_body={"metadata": {"webhook_id": "YOUR_WEBHOOK_IDENTIFIER"}},
)
```
```bash cURL theme={"system"}
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": "user", "content": "Hello!"}],
"metadata": {"webhook_id": "YOUR_WEBHOOK_IDENTIFIER"}
}'
```
### Embeddings
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
```typescript TypeScript theme={"system"}
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.generation_id);
// Your processing logic here
});
} else if (req.body.event === "async-embedding.completed") {
setImmediate(() => {
console.log("Embedding completed:", req.body.generation_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.group_id);
console.log("Group size:", req.body.data.group_size);
req.body.data.generations.forEach((gen: any) => {
console.log(`Generation ${gen.generationId}: ${gen.state}`);
});
});
}
});
```
```python Python theme={"system"}
@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['generation_id']}")
# Your processing logic here
elif payload["event"] == "async-embedding.completed":
print(f"Processing embedding {payload['generation_id']}")
print(f"Number of embeddings: {len(payload['data']['response']['data'])}")
elif payload["event"] == "slow-group.completed":
print(f"Processing group {payload['group_id']}")
print(f"Group size: {payload['data']['group_size']}")
for gen in payload["data"]["generations"]:
print(f"Generation {gen['generationId']}: {gen['state']}")
```
## Timing & Limits
| Metric | Value | Notes |
| ---------------- | ---------------- | ----------------------------- |
| Response timeout | 30 seconds | Must respond within this time |
| Retry attempts | 3 | With exponential backoff |
| Max payload size | 10MB | Typical: 5-50KB |
| Delivery time | Under 60 seconds | From completion to webhook |
## Response Codes
| Code | Meaning | Retry? |
| ------- | ------------------ | ------ |
| 200-299 | Success | No |
| 400-499 | Client error | No |
| 500-599 | Server error | Yes |
| Timeout | No response in 30s | Yes |
## Best Practices Checklist
* [ ] Respond with 200 OK immediately
* [ ] Process webhook data asynchronously
* [ ] Implement idempotency with generation\_id or group\_id
* [ ] 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
| 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
* [Full Documentation](https://docs.inference.net)
* [API Reference](https://docs.inference.net/api)
* [Support](mailto:support@inference.net)
* Discord Community
# Data Retention
Source: https://docs.inference.net/api/data-retention
Understand how Inference.net handles request data, observability records, and retention controls.
Inference.net is designed to support production workloads without treating captured request data casually.
## 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
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
* 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
## 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
Source: https://docs.inference.net/api/function-calling
Enable models to fetch data and take actions.
Support for Function Calling is in beta and will be available soon.
## 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
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 theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL theme={"system"}
export INFERENCE_API_KEY=
# All requests use:
# -H "Authorization: Bearer $INFERENCE_API_KEY"
# -H "Content-Type: application/json"
```
## 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
Let's look at the steps to allow a model to use a real `get_weather` function defined below:
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 1: Call model with get\_weather tool defined
```typescript TypeScript theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
messages,
tools,
});
```
```python Python theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
messages=messages,
tools=tools,
)
```
```bash cURL theme={"system"}
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 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
```json JSON theme={"system"}
[
{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"lat\":48.8566,\"lon\":2.3522}"
}
}
]
```
#### Step 3: Execute the `get_weather` function
```typescript TypeScript theme={"system"}
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 theme={"system"}
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
```typescript TypeScript theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
messages,
tools,
});
console.log(completion2.choices[0].message.content);
```
```python Python theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
messages=messages,
tools=tools,
)
print(completion_2.choices[0].message.content)
```
#### Output
```json JSON theme={"system"}
"The current temperature in Paris is 14°C (57.2°F)."
```
## 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 theme={"system"}
{
"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
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 theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
messages,
tools,
});
console.log(response.choices[0].message.tool_calls);
```
```python Python theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
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
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 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 theme={"system"}
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: "google/gemma-3-27b-instruct/bf-16",
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 theme={"system"}
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="google/gemma-3-27b-instruct/bf-16",
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 theme={"system"}
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": "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 theme={"system"}
[{"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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"index": 0,
"id": "call_RzfkBpJgzeR0S242qfvjadNe",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}
```
# Rate Limits
Source: https://docs.inference.net/api/rate-limits
Rate limits for the Inference.net API
## Generation Rate Limits
Rate limits for model inference requests are based on your account tier:
| Tier | Requests per minute (RPM) |
| ------------ | ------------------------------------ |
| Free | 30 |
| Paid | 250 |
| Custom teams | 500 - 1,000,000 (per-team overrides) |
## Deployed Model Rate Limits
Models you deploy on the platform have a rate limit of **300 RPM per instance**. If you need higher throughput, scale up the number of instances — total RPM equals the number of instances multiplied by 300.
## 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.
## General API Rate Limits
The Catalyst gateway enforces a general rate limit of **100 requests per second** per API key or IP address. This applies across all endpoints.
## 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.
# Structured Outputs
Source: https://docs.inference.net/api/structured-outputs
Ensure responses adhere to a JSON schema.
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
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
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 theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.inference.net/v1",
apiKey: process.env.INFERENCE_API_KEY,
});
```
```python Python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.inference.net/v1",
api_key=os.environ["INFERENCE_API_KEY"],
)
```
```bash cURL theme={"system"}
export INFERENCE_API_KEY=
# All requests use:
# -H "Authorization: Bearer $INFERENCE_API_KEY"
# -H "Content-Type: application/json"
```
## 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 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
### 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
```json JSON theme={"system"}
{
"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
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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
You can use the OpenAI SDK to parse the model's output into a typed object automatically.
### 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 theme={"system"}
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 theme={"system"}
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
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 theme={"system"}
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 theme={"system"}
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
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
# 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
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
# 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
Structured Outputs supports a subset of the [JSON Schema](https://json-schema.org/docs) language.
#### Supported types
The following types are supported for Structured Outputs:
* String
* Number
* Boolean
* Integer
* Object
* Array
* Enum
* anyOf
#### 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 theme={"system"}
{
"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
Limitations on the number of properties, enum values, and total string size may vary depending on the model you are using.
#### Key ordering
When using Structured Outputs, outputs will be produced in the same order as the ordering of keys in the schema.
## 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
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
# 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
Source: https://docs.inference.net/api/vision
Use models to extract information from images.
## 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
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
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
```typescript TypeScript theme={"system"}
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 theme={"system"}
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 theme={"system"}
# 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
```typescript TypeScript theme={"system"}
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-instruct/bf-16",
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 theme={"system"}
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-instruct/bf-16",
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 theme={"system"}
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 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
* 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 size of the request body must be less than 1MB.
* Each request can contain a maximum of 2 images.
## 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(2, min(1, HEIGHT / 560))
w = max(2, min(1, 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.
# Authentication
Source: https://docs.inference.net/cli/authentication
Sign in interactively, use an API key for CI, and manage CLI credentials.
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`
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 theme={"system"}
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
| Option | Required | Description |
| --------------------- | -------- | -------------------------------------------------------- |
| `--team ` | No | Activate a specific team by ID, slug, or exact team name |
### Team selection
Use `--team` when you know which team you want to activate, or when running in a non-interactive shell.
```bash theme={"system"}
inf auth login --team acme
```
You can pass a team ID, slug, or exact team name:
```bash theme={"system"}
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.
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`
Store a project API key on disk for headless or CI authentication.
```bash theme={"system"}
inf auth set-key
```
### 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
```bash theme={"system"}
inf auth set-key sk-inference-...
```
## `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 theme={"system"}
inf auth status
```
## `inf auth logout`
Sign out, clear the session token / API key from `~/.inf/config.json`, and forget the active project and team.
```bash theme={"system"}
inf auth logout
```
## 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`
`inf instrument` is the one exception — it **rejects** `INF_API_KEY` and requires a session login, because it needs to fetch your project's default API key on your behalf. Unset `INF_API_KEY` and run `inf auth login` before running `inf instrument`.
## Configuration
The CLI stores configuration at `~/.inf/config.json`, created automatically on first login. Tokens are stored with `0600` permissions.
### 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://observability-api.inference.net` |
| `INF_PROJECT_ID` | Override the active project for any invocation (equivalent to `--project ` global) | — |
# Dashboard
Source: https://docs.inference.net/cli/dashboard
Launch the interactive terminal dashboard for a live overview of your project.
`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`
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 theme={"system"}
inf dashboard
```
### 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 |
### 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
```bash theme={"system"}
# 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
Source: https://docs.inference.net/cli/datasets
Upload JSONL inference data, materialize eval/training datasets, and download them.
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`
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 theme={"system"}
inf dataset upload
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | -------------------------------- |
| `file` | Yes | Path to the JSONL file to upload |
### Options
| Flag | Required | Description | Default |
| ------------------- | -------- | ---------------------------------------------------------------- | -------------------------- |
| `-n, --name ` | No | Upload name shown in Catalyst | 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
```bash theme={"system"}
# 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`
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 theme={"system"}
inf dataset create -n -t [source-flags…]
```
### Options
| 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
```bash theme={"system"}
# 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`
Display datasets in the active project.
```bash theme={"system"}
inf dataset list
```
**Alias:** `inf dataset ls`
### Options
| 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
```bash theme={"system"}
# 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`
View detailed information about a specific dataset — ID, name, type, inference count, export status, source project, and creation date.
```bash theme={"system"}
inf dataset get
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ------------------------------------------------- |
| `id` | Yes | Dataset ID, UUID prefix (4+ chars), or exact name |
## `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 theme={"system"}
inf dataset download [id]
```
### Arguments
| 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
| 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
```bash theme={"system"}
# 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
```
# Evals
Source: https://docs.inference.net/cli/evals
Create rubrics, launch eval runs, and inspect results from the terminal.
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 theme={"system"}
# 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`
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 theme={"system"}
inf eval rubric create -n -f
```
### 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
```bash theme={"system"}
# 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`
Get details of a rubric — ID, name, latest version number, version count, score range, and a preview of the template.
```bash theme={"system"}
inf eval rubric get
```
### 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`
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 theme={"system"}
inf eval rubric delete
```
**Alias:** `inf eval rubric archive ` — both names do the same thing; use whichever reads clearer in your script.
### Arguments
| Argument | Required | Description |
| -------- | -------- | ---------------------------------------------------- |
| `id` | Yes | Full UUID, 4+ character prefix, or exact rubric name |
### Options
| 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
```bash theme={"system"}
# 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`
List rubrics in the active project.
```bash theme={"system"}
inf eval rubrics
```
**Alias:** `inf eval defs`
### Options
| 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`
Launch a new eval run group against one or more models, scored by a judge model.
```bash theme={"system"}
inf eval run \
--rubric-id \
--dataset-id \
--models \
--judge-model
```
### Options
| 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
```bash theme={"system"}
# 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`
List eval run groups for a given rubric.
```bash theme={"system"}
inf eval list --rubric-id
```
**Alias:** `inf eval ls`
### Options
| 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`
View detailed information about a specific eval run group.
```bash theme={"system"}
inf eval get
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | --------------------- |
| `id` | Yes | The eval run group ID |
### 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`
List datasets available for evaluations (type = `eval`).
```bash theme={"system"}
inf eval datasets
```
### Options
| 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.
# HALO
Source: https://docs.inference.net/cli/halo
Run HALO agent-trace analyses, schedule recurring reports, and pull report markdown from the CLI.
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 halo` to kick off a one-off analysis, schedule recurring ones, watch a run reach completion, and read the resulting report.
## 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
```bash theme={"system"}
# 1. Find the agent you want to analyze
inf halo agent list
# 2. Start a run for that agent over the last 24h
inf halo run create --agent-uuid
# 3. Wait for it to finish (prints the run id from step 2)
inf halo run poll
# 4. Read the report (the assistant message in the conversation)
inf halo conversation get
```
`inf halo run create` prints both the `run-id` and the `conversation-id`. The report prints in full from `inf halo conversation get` — no `--json` required. Add `--json` only when you want the raw structured payload for scripting.
## `inf halo 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 theme={"system"}
inf halo agent list
```
**Alias:** `inf halo agent ls`
### 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 halo run`
Create and inspect HALO runs.
### `inf halo run create`
Start a manual analysis for one agent over a time window.
```bash theme={"system"}
inf halo 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
| Flag | Required | Description | Default |
| ---------------------------- | ------------------------------- | ------------------------------------------------------------ | ------------------------------- |
| `--agent-uuid ` | No (required non-interactively) | Agent UUID from `inf halo 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 | `10000` |
| `--max-subagent-depth ` | No | Engine recursion ceiling (1–5) | `3` |
| `--max-turns ` | No | Per-agent turn ceiling (1–100) | `50` |
| `--agent-time-range ` | No | Trace window preset used when picking an agent interactively | `30d` |
### `inf halo 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 theme={"system"}
inf halo run poll
```
#### Options
| Flag | Required | Description | Default |
| ---------------------- | -------- | ------------- | --------------- |
| `--interval ` | No | Poll interval | `5` |
| `--timeout ` | No | Total timeout | `1800` (30 min) |
### `inf halo 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 theme={"system"}
inf halo run get
```
### `inf halo run events`
List the structured event timeline for a run (started, heartbeat, agent steps, completed, failed).
```bash theme={"system"}
inf halo run events
```
#### Options
| Flag | Required | Description | Default |
| ------------- | -------- | -------------------- | ------- |
| `--limit ` | No | Max events to return | `100` |
### `inf halo run cancel`
Request cancellation of an in-flight run.
```bash theme={"system"}
inf halo run cancel --reason "no longer needed"
```
#### Options
| Flag | Required | Description | Default |
| ----------------- | -------- | ------------------------------------ | ---------------- |
| `--reason ` | No | Reason recorded for the cancellation | `user-cancelled` |
## `inf halo conversation`
Inspect HALO conversations and read their reports.
**Alias:** `inf halo conv`
### `inf halo conversation list`
List conversations in the active project, newest first.
```bash theme={"system"}
inf halo conversation list
```
**Alias:** `inf halo conversation ls`
#### Options
| Flag | Required | Description | Default |
| ------------- | -------- | --------------------------- | ------- |
| `--limit ` | No | Max conversations to return | `50` |
### `inf halo 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 theme={"system"}
inf halo conversation get
```
Add `--json` to get the raw payload (every message, run, and trace dataset) for scripting.
## `inf halo schedule`
Manage recurring HALO analyses. Each schedule targets one agent and fires runs on a cadence.
### `inf halo schedule list`
```bash theme={"system"}
inf halo schedule list
```
**Alias:** `inf halo schedule ls`
| Flag | Required | Description | Default |
| -------------------- | -------- | -------------------------- | ------- |
| `--include-archived` | No | Include archived schedules | `false` |
### `inf halo schedule get`
```bash theme={"system"}
inf halo schedule get
```
### `inf halo schedule runs`
List recent runs fired by a schedule.
```bash theme={"system"}
inf halo schedule runs
```
| Flag | Required | Description | Default |
| ------------- | -------- | ------------------ | ------- |
| `--limit ` | No | Max runs to return | `50` |
### `inf halo schedule create`
```bash theme={"system"}
inf halo 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 halo 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) | `3` |
| `--max-turns ` | No | Per-agent turn ceiling (1–100) | `50` |
| `--enabled ` | No | Whether the schedule fires | `true` |
### `inf halo schedule update`
Update mutable fields on a schedule. Accepts the same flags as `create` (all optional), keyed by ``.
```bash theme={"system"}
# Pause a schedule
inf halo schedule update --enabled false
# Repoint it at a different agent and widen the window
inf halo schedule update --agent-uuid --lookback-hours 168
```
### `inf halo schedule archive` / `unarchive`
Archive a schedule so it stops firing, or restore it later.
```bash theme={"system"}
inf halo schedule archive
inf halo schedule unarchive
```
`archive` prompts for confirmation. Pass `-y` / `--yes` to skip the prompt.
## Common workflows
```bash theme={"system"}
# Run an analysis end-to-end and capture the report markdown to a file
RUN=$(inf halo run create --agent-uuid --json)
RUN_ID=$(echo "$RUN" | jq -r '.runId')
CONV_ID=$(echo "$RUN" | jq -r '.conversationId')
inf halo run poll "$RUN_ID"
# Extract just the original report (first assistant message) as markdown
inf halo conversation get "$CONV_ID" --json \
| jq -r '.messages | map(select(.role == "assistant"))[0].content' \
> halo-report.md
```
## 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`.
# Inferences
Source: https://docs.inference.net/cli/inferences
List, filter, sort, and export inference requests and responses captured by Gateway.
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`
List and filter inferences in the active project.
```bash theme={"system"}
inf inference list
```
**Alias:** `inf inference ls`
### 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) | — |
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
```bash theme={"system"}
# 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`
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 theme={"system"}
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`
Fetch the **complete stored request and response** for a specific inference — method, path, headers, and the full request/response bodies (returned only when payload storage was enabled for the request; otherwise the sides come back `null`). Useful for debugging specific calls, inspecting model behavior, or archiving payloads.
```bash theme={"system"}
inf inference get
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | -------------------------------------------- |
| `id` | Yes | Full inference UUID or a 4+ character prefix |
### Options
| 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 }` payload (method, path, headers, and complete bodies) as clean JSON for piping into `jq` or saving.
### Examples
```bash theme={"system"}
# 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
Source: https://docs.inference.net/cli/instrument
Automatically instrument your codebase to route LLM calls through Inference.net Catalyst using an AI coding agent.
`inf instrument` hands your codebase to an AI coding agent (Claude Code, OpenCode, or Codex) to wire up Catalyst 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 Catalyst. You don't need to know the SDK changes in advance — the agent figures it out from your code.
## 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`
Instrument the current working directory. The command walks you through:
1. Confirming the target project (auto-selected if you only have one).
2. Fetching your project's default API key.
3. Detecting installed coding agents and picking one (prompted if you have more than one).
4. Downloading the instrumentation skill and building the agent prompt.
5. Launching the agent interactively so you can review and approve the changes.
```bash theme={"system"}
cd /path/to/your/project
inf instrument
```
### Options
| Flag | Required | Description | Default |
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| `--dry-run` | No | Ask the agent to preview changes without modifying files | Off |
| `--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 | Skip the "Instrument this project?" confirmation | 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` |
### 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.
### Examples
```bash theme={"system"}
# 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
After the agent finishes, run your app to generate a few LLM calls, then confirm they're captured:
```bash theme={"system"}
inf inference list
```
Or open the observability dashboard link the command prints on completion.
## 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
**`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 fetch the project's default 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.
**`No default API key found for project …`**
Open the dashboard → the relevant project → **API Keys** and create one. `inf instrument` needs a project-scoped key to embed in the agent 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.
# Models
Source: https://docs.inference.net/cli/models
Browse callable models, providers, capabilities, and pricing from the terminal.
`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 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`
Display every callable model visible to the active team, with provider, scope, capability flags, context window, and per-million-token pricing.
```bash theme={"system"}
inf models list
```
**Alias:** `inf models ls`
### 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
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
```bash theme={"system"}
# 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
`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 theme={"system"}
[
{
"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.
# Install CLI
Source: https://docs.inference.net/cli/overview
The Inference CLI (`inf`) drives Catalyst 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.
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
```bash theme={"system"}
npm install -g @inference/cli
```
```bash theme={"system"}
inf auth login
```
```bash theme={"system"}
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).
## 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 |
| `-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
| Command | Description |
| ----------------------------------- | --------------------------------------------------------------------- |
| [`inf instrument`](/cli/instrument) | Instrument your codebase for Catalyst observability using an AI agent |
| [`inf auth`](/cli/authentication) | Sign in, sign out, and check authentication status |
| [`inf project`](/cli/projects) | List, switch between, and inspect projects |
| [`inf models`](/cli/models) | Browse callable models with capabilities and pricing |
| [`inf eval`](/cli/evals) | Manage rubrics, launch eval runs, inspect results |
| [`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 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 dashboard`](/cli/dashboard) | Launch the interactive terminal dashboard |
## Explore the CLI
Hand your project to an AI coding agent that wires up Catalyst for you.
Browser and headless authentication, env vars, and config.
Switch between projects and inspect the active project.
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.
Interactive terminal UI for training runs, evals, datasets, and inferences.
## 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
Catalyst 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
Source: https://docs.inference.net/cli/projects
List, switch, and inspect your Inference.net 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`
Display every project you have access to in the active team. The active project is flagged with a green dot (`●`).
```bash theme={"system"}
inf project list
```
**Alias:** `inf project ls`
### Example
```bash theme={"system"}
inf project list
```
## `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.
```bash theme={"system"}
inf project switch
```
**Alias:** `inf project use`
### Arguments
| Argument | Required | Description |
| ------------ | -------- | --------------------------------- |
| `project-id` | Yes | The ID of the project to activate |
### Examples
```bash theme={"system"}
# Switch to the staging project
inf project switch proj_789ghi012jkl
# Same, using the `use` alias
inf project use proj_789ghi012jkl
```
Temporarily override the active project for a single command without changing your stored config using the global `--project` flag:
```bash theme={"system"}
inf training list --project proj_789ghi012jkl
```
## `inf project current`
Show details about the currently active project — project ID, name, team ID, and creation date. If no project is active, the command errors and points you at `inf project switch `.
```bash theme={"system"}
inf project current
```
# Spans
Source: https://docs.inference.net/cli/spans
Search spans, inspect span payloads, and debug trace steps from the CLI.
Use `inf span` when you already know you need span-level detail instead of a whole trace tree. It lets you search spans across traces, focus on root or entrypoint spans, inspect captured input/output, and print OpenTelemetry attributes for a single step.
**Alias:** `inf spans`
## `inf span list`
Display spans in the active project.
```bash theme={"system"}
inf span list
```
**Alias:** `inf span ls`
### Options
| Flag | Required | Description | Default |
| --------------------------------- | -------- | ------------------------------------------------------------------------------------ | ---------------- |
| `-l, --limit ` | No | Maximum number of spans | `25` |
| `--cursor ` | No | Pagination cursor from a previous response | - |
| `--sort ` | No | Sort by `start_time`, `duration_ns`, `cost_total`, or `total_tokens` | Server default |
| `--order ` | No | Sort order | Server default |
| `--trace-id ` | No | Limit results to one trace | - |
| `--text ` | No | Search captured span input/output text | - |
| `--scope ` | No | Span scope filter | `all` |
| `--range ` | No | Relative time range: `1h`, `6h`, `12h`, `1d`, `3d`, `7d`, `14d`, `30d`, `90d`, `all` | `1d` |
| `--from ` | No | Start time. Must be used with `--to` | - |
| `--to ` | No | End time. Must be used with `--from` | - |
| `--kind ` | No | Observation kind. Repeat or comma-separate values like `LLM,TOOL,AGENT` | All kinds |
| `--provider ` | No | LLM provider filter | All providers |
| `--model ` | No | LLM model filter | All models |
| `--service ` | No | OpenTelemetry `service.name` filter | All services |
| `--environment ` | No | Deployment environment filter | All environments |
| `--user ` | No | User ID filter | All users |
| `--session ` | No | Session ID filter | All sessions |
| `--agent ` | No | Agent name filter | All agents |
| `--status ` | No | Span status filter | All statuses |
| `--filter ` | No | Advanced typed field filter. Repeatable | - |
| `--metadata ` | No | Span attribute filter. Repeatable | - |
| `--resource ` | No | Resource attribute filter. Repeatable | - |
Use `--json` to preserve full trace IDs and span IDs for scripting.
### Examples
```bash theme={"system"}
# Recent spans
inf span list
# Root spans for failed traces in the last day
inf span list --scope root --status error --range 1d
# Spans inside a specific trace
inf span list --trace-id 2f0d2b...
# Search captured IO text for a string
inf span list --text "rate limit"
# Find expensive model spans
inf span list --kind LLM --filter "cost_total>0.05" --sort cost_total --order desc
```
## `inf span get`
Open a single span by trace ID and span ID.
```bash theme={"system"}
inf span get
```
**Alias:** `inf span show`
### Arguments
| Argument | Required | Description |
| ---------- | -------- | ---------------------------- |
| `trace-id` | Yes | Trace ID containing the span |
| `span-id` | Yes | Span ID |
### Options
| Flag | Required | Description | Default |
| --------------------- | -------- | ---------------------------------------------------------- | --------- |
| `--view ` | No | `summary`, `io`, `attributes`, `events`, `links`, or `raw` | `summary` |
| `-o, --output ` | No | Write raw JSON to a file | - |
### Views
| View | Use it for |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `summary` | Span identity, timing, status, model/provider, token counts, cost, task/session metadata, and domain-specific summary fields |
| `io` | Captured input, output, input messages, output messages, and retrieval documents |
| `attributes` | Span and resource attributes across string, integer, and floating-point maps |
| `events` | Raw OpenTelemetry events JSON |
| `links` | Raw OpenTelemetry links JSON |
| `raw` | Full span JSON exactly as returned by the API |
### Examples
```bash theme={"system"}
# Human-readable summary
inf span get 2f0d2b... 91f8c4...
# Inspect captured prompt/response payloads
inf span get 2f0d2b... 91f8c4... --view io
# Inspect OpenTelemetry attributes
inf span get 2f0d2b... 91f8c4... --view attributes
# Save raw span JSON
inf span get 2f0d2b... 91f8c4... --view raw -o span.json
```
## `inf span facets`
Inspect available span filter values and counts.
```bash theme={"system"}
inf span facets
```
`inf span facets` accepts the same filters as [`inf span list`](#inf-span-list), plus:
| Flag | Required | Description | Default |
| ----------------------------- | -------- | --------------------------------------------------------- | -------------------- |
| `--facet ` | No | Facet ID. Repeat or comma-separate values | All supported facets |
| `--search ` | No | Search within a facet's values. Repeatable | - |
| `--attribute-key ` | No | Fetch values for one `span:` or `resource:` attribute key | - |
| `-l, --limit ` | No | Maximum options per facet | `50` |
### Examples
```bash theme={"system"}
# Discover span facets for recent traffic
inf span facets --range 1d
# Search model facet values
inf span facets --facet llm_model_name --search llm_model_name=claude
# List values for a span attribute
inf span facets --attribute-key span:gen_ai.operation.name
```
## Advanced filter syntax
The span filter language matches [`inf trace`](/cli/traces) except trace aggregate fields such as `span_count` and `llm_span_count` are not valid for span queries.
| Expression | Meaning |
| --------------------------------------- | ----------------------------------------------------------------------------------------- |
| `duration_ms>500` | Numeric comparison. `duration` and `duration_ms` are converted to nanoseconds for the API |
| `total_tokens between:100,500` | Numeric range |
| `span_name~retrieval` | String contains |
| `llm_provider=anthropic` | String equality |
| `service_name in:api,worker` | String set membership |
| `agent_name not-empty` | Non-empty string field |
| `--metadata gen_ai.operation.name=chat` | Span attribute equality |
| `--resource service.version~2026` | Resource attribute contains |
## Common workflows
```bash theme={"system"}
# Pick a trace, then inspect its LLM spans
TRACE_ID=$(inf trace list --json | jq -r '.traces[0].traceId')
inf span list --trace-id "$TRACE_ID" --kind LLM --json
# Open the first span from that trace
SPAN_ID=$(inf span list --trace-id "$TRACE_ID" --json | jq -r '.spans[0].spanId')
inf span get "$TRACE_ID" "$SPAN_ID" --view io
```
## Related commands
* [`inf trace`](/cli/traces) opens whole traces, trees, timelines, and trace exports.
* [`inf inference`](/cli/inferences) inspects gateway request/response records.
* [`inf dashboard`](/cli/dashboard) launches the interactive terminal dashboard.
# Traces
Source: https://docs.inference.net/cli/traces
Browse trace trees, inspect trace facets, and manage trace exports from the CLI.
Use `inf trace` to inspect multi-step LLM workflows captured by Catalyst Tracing. It mirrors the dashboard trace viewer for headless debugging: list trace summaries, open a trace tree, render a timeline, inspect captured conversation messages, discover filter facets, and queue/download trace exports.
**Alias:** `inf traces`
## `inf trace upload`
Upload a JSONL trace file into the active project. The CLI validates the format locally (OTLP or Langfuse export), uploads it in parts, waits for processing to finish, and prints the upload ID plus line counts. Same flow as the dashboard **Upload** button and [`inf dataset upload`](/cli/datasets#inf-dataset-upload).
```bash theme={"system"}
inf trace upload
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ----------------------------- |
| `file` | Yes | Path to a `.jsonl` trace file |
### Options
| Flag | Required | Description | Default |
| ------------------- | -------- | -------------------------------------------------------------- | -------------------------- |
| `-n, --name ` | No | Upload name shown in Catalyst | Filename without extension |
| `--no-wait` | No | Return after the transfer completes without polling processing | Off |
Uploaded traces appear under the **Traces** tab once processing completes. Filter them with all-time range and the upload ID (`trace_import_id` in filters):
```bash theme={"system"}
# Upload and wait for processing (default)
inf trace upload ./exports/langfuse-traces.jsonl
# Custom upload name
inf trace upload ./traces.jsonl --name prod-replay-2026-04-01
# Queue processing but do not wait
inf trace upload ./traces.jsonl --no-wait
# List traces from a completed upload
inf trace list --range all --filter "trace_import_id="
```
## `inf trace list`
Display trace summaries in the active project.
```bash theme={"system"}
inf trace list
```
**Alias:** `inf trace ls`
### Options
| Flag | Required | Description | Default |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------- | ---------------- |
| `-l, --limit ` | No | Maximum number of traces | `25` |
| `--cursor ` | No | Pagination cursor from a previous response | - |
| `--sort ` | No | Sort by `start_time`, `duration`, `total_cost`, `total_tokens`, `span_count`, or `llm_span_count` | Server default |
| `--order ` | No | Sort order | Server default |
| `--range ` | No | Relative time range: `1h`, `6h`, `12h`, `1d`, `3d`, `7d`, `14d`, `30d`, `90d`, `all` | `1d` |
| `--from ` | No | Start time. Must be used with `--to` | - |
| `--to ` | No | End time. Must be used with `--from` | - |
| `--kind ` | No | Observation kind. Repeat or comma-separate values like `LLM,TOOL,AGENT` | All kinds |
| `--provider ` | No | LLM provider filter | All providers |
| `--model ` | No | LLM model filter | All models |
| `--service ` | No | OpenTelemetry `service.name` filter | All services |
| `--environment ` | No | Deployment environment filter | All environments |
| `--user ` | No | User ID filter | All users |
| `--session ` | No | Session ID filter | All sessions |
| `--agent ` | No | Agent name filter | All agents |
| `--status ` | No | Trace status filter | All statuses |
| `--filter ` | No | Advanced typed field filter. Repeatable | - |
| `--metadata ` | No | Span attribute filter. Repeatable | - |
| `--resource ` | No | Resource attribute filter. Repeatable | - |
Use `--json` for the raw API payload with full trace IDs, pagination cursors, and aggregate counts.
### Examples
```bash theme={"system"}
# Recent traces
inf trace list
# Failed LLM traces from the last hour
inf trace list --range 1h --kind LLM --status error
# Slow traces by duration, sorted longest first
inf trace list --filter "duration_ms>5000" --sort duration --order desc
# Filter by service and model
inf trace list --service api --model claude-sonnet-4-5
```
## `inf trace get`
Open a single trace and its spans.
```bash theme={"system"}
inf trace get
```
**Alias:** `inf trace show`
### Arguments
| Argument | Required | Description |
| ---------- | -------- | ----------- |
| `trace-id` | Yes | Trace ID |
### Options
| Flag | Required | Description | Default |
| --------------------- | -------- | ---------------------------------------------------------- | --------- |
| `--view ` | No | `summary`, `tree`, `timeline`, `thread`, `spans`, or `raw` | `summary` |
| `--span ` | No | Highlight a selected span in the tree view | - |
| `--limit ` | No | Maximum spans to fetch for this trace | `1000` |
| `--cursor ` | No | Span pagination cursor | - |
| `-o, --output ` | No | Write raw JSON to a file | - |
The default summary view prints trace metadata and an ASCII span tree. Use `--view raw` or global `--json` when piping trace data into scripts.
### Examples
```bash theme={"system"}
# Summary plus span tree
inf trace get 2f0d2b...
# Text waterfall ordered by span start time
inf trace get 2f0d2b... --view timeline
# Conversation-style view from captured input/output messages
inf trace get 2f0d2b... --view thread
# Persist raw trace + spans JSON for local analysis
inf trace get 2f0d2b... --view raw -o trace.json
```
## `inf trace facets`
Inspect available trace filter values and counts. This is useful when building a repeatable query and you do not know the exact model, service, environment, or attribute values in a project.
```bash theme={"system"}
inf trace facets
```
### Options
`inf trace facets` accepts the same filter flags as [`inf trace list`](#inf-trace-list), plus:
| Flag | Required | Description | Default |
| ----------------------------- | -------- | --------------------------------------------------------- | -------------------- |
| `--facet ` | No | Facet ID. Repeat or comma-separate values | All supported facets |
| `--search ` | No | Search within a facet's values. Repeatable | - |
| `--attribute-key ` | No | Fetch values for one `span:` or `resource:` attribute key | - |
| `-l, --limit ` | No | Maximum options per facet | `50` |
### Examples
```bash theme={"system"}
# Show all default trace facets
inf trace facets --range 7d
# Find model facet values containing "claude"
inf trace facets --facet llm_model_name --search llm_model_name=claude
# Explore values for an OpenTelemetry span attribute
inf trace facets --attribute-key span:gen_ai.operation.name
```
## Advanced filter syntax
`--filter`, `--metadata`, and `--resource` are repeatable. Quote expressions so your shell does not interpret operators.
| Expression | Meaning |
| --------------------------------------- | ----------------------------------------------------------------------------------------- |
| `duration_ms>500` | Numeric comparison. `duration` and `duration_ms` are converted to nanoseconds for the API |
| `total_tokens between:100,500` | Numeric range |
| `span_count>=3` | Trace aggregate filter |
| `llm_model_name~claude` | String contains |
| `service_name in:api,worker` | String set membership |
| `agent_name not-empty` | Non-empty string field |
| `--metadata gen_ai.operation.name=chat` | Span attribute equality |
| `--resource service.version~2026` | Resource attribute contains |
## `inf trace export`
Trace exports create downloadable JSONL files for offline analysis, support escalations, or long-running review workflows.
### `inf trace export create`
Queue an export job.
```bash theme={"system"}
inf trace export create --range 7d --kind LLM
```
| Flag | Required | Description | Default |
| ---------------------------------- | -------- | ----------------------------------------- | --------------------- |
| `--from ` / `--to ` | No | Explicit export window | - |
| `--range ` | No | Relative export window | `1d` |
| `--kind ` | No | Observation kind filter | All kinds |
| `--trace-id ` | No | Trace ID. Repeat or comma-separate values | - |
| `--session ` | No | Session ID filter | - |
| `--user ` | No | User ID filter | - |
| `--agent ` | No | Agent name filter | - |
| `--model ` | No | LLM model filter | - |
| `--status-code ` | No | Span status code filter | - |
| `--attribute ` | No | Attribute equality match. Repeatable | - |
| `--wait` | No | Poll until the export finishes | Off |
| `--download` | No | Download after the export is ready | Off |
| `-o, --output ` | No | Download path | Generated from job ID |
```bash theme={"system"}
# Queue and print a job ID
inf trace export create --range 30d --kind LLM
# Queue, wait, and download in one command
inf trace export create --range 7d --status-code ERROR --wait --download -o failed-traces.jsonl
```
### `inf trace export list`
```bash theme={"system"}
inf trace export list
```
### `inf trace export status`
```bash theme={"system"}
inf trace export status
```
### `inf trace export download`
```bash theme={"system"}
inf trace export download -o traces.jsonl
```
## Related commands
* [`inf span`](/cli/spans) lists and opens individual spans.
* [`inf inference`](/cli/inferences) inspects gateway request/response records.
* [`inf dashboard`](/cli/dashboard) launches the interactive terminal dashboard.
# Training
Source: https://docs.inference.net/cli/training
Queue training runs, discover recipes and base models, monitor progress, and surface failures.
Kick off and manage model training runs from the command line. Discover recipes and trainable base models, queue new runs (with override flags for the trickier `trainingConfig` knobs), cancel in-flight jobs, and zoom in on errors without scrolling through raw logs.
**Alias:** `inf train`
The full training loop is paste-able from the terminal:
```bash theme={"system"}
# 1. Materialize training and eval datasets
inf dataset create -n my-train-split -t training --file ./train.jsonl
inf dataset create -n my-eval-split -t eval --file ./eval.jsonl
# → Datasets ds_trn_abc12 / ds_evl_def34 created.
# 2. Create (or reuse) a rubric the evals will score against
inf eval rubric create -n my-rubric -f ./rubric.md
# → Rubric rub_xyz56 / version rv_ver78 created.
# 3. Pick a recipe and base model
inf training recipes
inf training models
# 4. Queue the training run
inf training create \
--name distill-hardreasoning-qwen3.5-4b-v2 \
--recipe inf-public-training-recipe:qwen-3.5-4b-fft \
--training-dataset ds_trn_abc12 \
--eval-dataset ds_evl_def34 \
--rubric rub_xyz56 \
--sample-packing false \
--num-epochs 5 \
--task-id distill-hardreasoning-v2
# → Training job job_90ab12 queued.
# 5. Track progress until it finishes
inf training poll job_90ab12
```
## `inf training models`
Discover the base models you can fine-tune and (with `--judge`) the judge models that can score checkpoints.
```bash theme={"system"}
inf training models
```
### Options
| Flag | Required | Description | Default |
| --------- | -------- | ------------------------------------------------------------------- | ------- |
| `--judge` | No | List judge models instead of base models (use with `--judge-model`) | Off |
The table shows each model's canonical alias, full name, and ID prefix. Pair with `--json` when scripting to preserve full IDs — those full IDs are what you pass to `--base-model` / `--judge-model` on `inf training create`.
### Examples
```bash theme={"system"}
# List base models you can fine-tune
inf training models
# List judge models instead
inf training models --judge
# Dump full IDs for scripting
inf training models --json | jq -r '.[].id'
```
## `inf training recipes`
Recipes bundle a base model + judge model + GPU plan + full `trainingConfig`. `inf training recipes` lists everything visible to the active project (public recipes + the project's own recipes).
```bash theme={"system"}
inf training recipes
```
### Options
| Flag | Required | Description | Default |
| -------------------- | -------- | -------------------------------------- | ------- |
| `--include-archived` | No | Include archived recipes | Off |
| `--public-only` | No | Show only public recipes | Off |
| `--project-only` | No | Show only the active project's recipes | Off |
Only super-admins can fork a public recipe into a project recipe. If you need to customize a recipe's `trainingConfig`, use the override flags on [`inf training create`](#inf-training-create) rather than trying to clone the recipe.
## `inf training recipes get`
Inspect a specific recipe, including its full `trainingConfig`. Useful for spotting knobs you may want to override at queue time.
```bash theme={"system"}
inf training recipes get
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ----------- |
| `id` | Yes | Recipe ID |
### Examples
```bash theme={"system"}
# List recipes visible to the active project
inf training recipes
# Inspect a specific recipe (including its trainingConfig)
inf training recipes get inf-public-training-recipe:qwen-3.5-4b-fft
# Only show project-owned recipes
inf training recipes --project-only
```
## `inf training create`
Queue a new training run. You can either specify a recipe (recommended — it pre-fills base model, judge, GPU plan, and `trainingConfig`) or pass individual flags. Override flags let you tweak specific `trainingConfig` fields without forking the recipe.
```bash theme={"system"}
inf training create \
--name \
--training-dataset \
--eval-dataset \
--rubric
```
**Alias:** `inf training queue`
### Options
| Flag | Required | Description | Default |
| -------------------------- | -------- | ----------------------------------------------------------------------------------------------------- | -------------- |
| `-n, --name ` | Yes | Display name for the run | — |
| `--training-dataset ` | Yes | Training-type dataset ID (also accepts `--training-dataset-id`) | — |
| `--eval-dataset ` | Yes | Eval-type dataset ID (also accepts `--eval-dataset-id`) | — |
| `--rubric ` | Yes | Rubric ID (also accepts `--rubric-id`) | — |
| `--rubric-version-id ` | No | Pin to a specific rubric version | Latest version |
| `--recipe ` | No | Training recipe ID — pre-fills base model, judge, GPU plan, and `trainingConfig` (also `--recipe-id`) | — |
| `--base-model ` | No | Base model to fine-tune (overrides the recipe, also `--base-model-id`) | Recipe default |
| `--judge-model ` | No | Judge model for evals (overrides the recipe, also `--judge-model-id`) | Recipe default |
| `--num-nodes ` | No | Number of training nodes | Recipe default |
| `--gpus-per-node ` | No | GPUs per node (1–8) | Recipe default |
| `--task-id ` | No | Associate the run with an existing task | — |
| `--sample-packing ` | No | Override `trainingConfig.sample_packing` (`true`/`false`) | Recipe default |
| `--num-epochs ` | No | Override `trainingConfig.num_epochs` | Recipe default |
| `--max-steps ` | No | Override `trainingConfig.max_steps` (use `-1` for "no cap") | Recipe default |
| `--learning-rate ` | No | Override `trainingConfig.learning_rate` | Recipe default |
| `--dry-run` | No | Print the exact tRPC payload that would be sent and exit — nothing is created | Off |
GPU hardware selection is managed server-side and is not configurable from the CLI. When `--rubric-version-id` is omitted, the CLI fetches the latest version of `--rubric` before queueing — pin a version if you need reproducibility across re-runs. Prints the new training job ID along with an `inf training poll ` follow-up command. The run status starts as `queued` while datasets are prepared, then moves to `running`.
### Escape hatches for recipe-pinned configs
The public recipes are opinionated: `sample_packing` is often `true`, and `num_epochs` / `max_steps` are tuned for the typical dataset size. When your dataset is small or shaped differently, those defaults can cause `torchrun` to crash (for example, with `args.max_steps must be set to a positive value if dataloader does not have a length, was -1` — which is what happens when sample packing collapses a tiny dataset into fewer batches than the FSDP shard count).
The `--sample-packing`, `--num-epochs`, `--max-steps`, and `--learning-rate` flags give you override knobs **without** needing to fork the recipe (only super-admins can). Pair them with `--dry-run` to sanity-check the resulting payload before burning a job slot.
### Examples
```bash theme={"system"}
# Minimal queue with a recipe
inf training create \
--name distill-v1 \
--recipe inf-public-training-recipe:qwen-3.5-4b-fft \
--training-dataset ds_trn_abc12 \
--eval-dataset ds_evl_def34 \
--rubric rub_xyz56
# Override recipe defaults for a small dataset and verify the payload first
inf training create \
--name distill-hardreasoning-qwen3.5-4b-v2 \
--recipe inf-public-training-recipe:qwen-3.5-4b-fft \
--training-dataset ds_trn_abc12 \
--eval-dataset ds_evl_def34 \
--rubric rub_xyz56 \
--sample-packing false \
--num-epochs 5 \
--dry-run
# Use the `queue` alias
inf training queue \
--name distill-v1 \
--base-model model_abc \
--judge-model model_xyz \
--training-dataset ds_trn_abc12 \
--eval-dataset ds_evl_def34 \
--rubric rub_xyz56
```
## `inf training list`
Display a table of training runs in the active project.
```bash theme={"system"}
inf training list
```
**Alias:** `inf training ls`
### Options
| Flag | Required | Description | Default |
| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `-s, --status ` | No | Filter by status: `exporting_datasets`, `queued`, `running`, `cycling`, `completed`, `failed`, `cancelled`, or `timed_out` | All statuses |
| `-l, --limit ` | No | Maximum number of results | `20` |
| `--offset ` | No | Offset for pagination | `0` |
The table shows the run ID (8-char prefix), name, status (color-coded), base model, progress (`currentStep/totalSteps`), current loss, and creation date.
### Examples
```bash theme={"system"}
# Show only running training jobs
inf training list --status running
# Get the first 50 training runs
inf training list --limit 50
# Page through results
inf training list --limit 20 --offset 40
```
## `inf training get`
View detailed information about a specific training run. Without `--error`, prints the full detail view. With `--error`, dumps only the fields that matter when a run crashed — ideal for triaging `failed` jobs from CI or a script.
```bash theme={"system"}
inf training get
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ------------------- |
| `id` | Yes | The training job ID |
### Options
| Flag | Required | Description | Default |
| --------- | -------- | -------------------------------------------------------------------------------------------------- | ------- |
| `--error` | No | Print only the status, status detail, and error message in a highlighted block (for `failed` runs) | Off |
### Output
The default detail view includes every field on the training job:
| Field | Description |
| ----------------------------------------- | ------------------------------------------------------------------------------- |
| `id`, `name` | Run identifier and display name |
| `status` | One of the status values listed under [`inf training list`](#inf-training-list) |
| `baseModelId` | Base model the run fine-tunes from |
| `adapter` | Adapter type (e.g. LoRA) |
| `currentStep` / `totalSteps` | Progress counters |
| `currentLoss` | Most recent training loss |
| `numNodes` | Number of nodes participating in the run |
| `provider` / `providerRunId` | Underlying training provider and their internal run ID |
| `evalDatasetName` / `rubricName` | Eval configuration (if the run is configured for mid-training evals) |
| `filteredDatasetName` | Training dataset name |
| `startedAt` / `completedAt` / `createdAt` | Lifecycle timestamps |
| `statusDetail` / `errorMessage` | Populated when the run fails or ends in a non-success state |
With `--error`, the output collapses to just `status`, `statusDetail`, and `errorMessage`.
### Examples
```bash theme={"system"}
# Full detail view
inf training get job_abc123
# Just the error payload for a failed run
inf training get job_abc123 --error
```
## `inf training cancel`
Cancel a `queued` or `running` training job. Completed and already-cancelled jobs will reject the call.
```bash theme={"system"}
inf training cancel
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ------------------- |
| `id` | Yes | The training job ID |
### Options
| 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
```bash theme={"system"}
# Cancel interactively
inf training cancel job_abc123
# Cancel non-interactively (required in CI)
inf training cancel job_abc123 --yes
```
## `inf training logs`
Stream or view log entries for a training run. Logs are color-coded by level, and each line is prefixed with the training phase it came from (`torchrun_init`, `training`, `inference_export`, …) so you can tell setup crashes apart from training crashes at a glance.
```bash theme={"system"}
inf training logs
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ------------------- |
| `id` | Yes | The training job ID |
### Options
| Flag | Required | Description | Default |
| ----------------- | -------- | ----------------------------------------------------------------------------- | ---------- |
| `-l, --limit ` | No | Maximum number of log entries | `50` |
| `--level ` | No | Filter by log level (e.g. `error`, `warn`, `info`) | All levels |
| `--phase ` | No | Filter by training phase (substring match — e.g. `torchrun_init`, `training`) | No filter |
| `-f, --follow` | No | Continuously poll for new logs (like `tail -f`) | Off |
Log entries are timestamped and color-coded: errors in red, warnings in yellow, info in blue. In follow mode, the CLI polls for new logs every 3 seconds until you press `Ctrl+C`.
### Examples
```bash theme={"system"}
# View the last 50 log entries
inf training logs job_abc123
# Stream logs in real time
inf training logs job_abc123 --follow
# Only show error logs
inf training logs job_abc123 --level error
# Only show setup-phase logs (everything before training starts)
inf training logs job_abc123 --phase torchrun_init
```
## `inf training poll`
Wait for a training run to complete, printing status updates as the status changes.
```bash theme={"system"}
inf training poll
```
### Arguments
| Argument | Required | Description |
| -------- | -------- | ------------------- |
| `id` | Yes | The training job ID |
### Options
| Flag | Required | Description | Default |
| -------------------------- | -------- | ------------------------ | ------- |
| `-i, --interval ` | No | Poll interval in seconds | `10` |
The CLI prints a status line each time the status changes, showing the status, progress, and current loss. It exits automatically when the run reaches `completed`, `failed`, `cancelled`, or `timed_out`. On a `failed` exit, the CLI points you at `inf training get