# βοΈ Dataset Diffs
Source: https://docs.oxen.ai/concepts/diffs
Quickly find changes in your datasets with Oxen.ai
Oxen.ai has built in tools to help you find differences in your datasets. It is as simple as running the `oxen diff` command with the path to your datasets.
```bash CLI theme={null}
oxen diff dataset.csv -o diff.csv
```
```bash Python theme={null}
import oxen
result = oxen.diff("dataset.csv")
print(result.get()) # .get() will resolve the diff data type
```
```
Column changes:
+ label (str)
Row changes:
Ξ 1 (modified)
+ 3 (added)
- 2 (removed)
shape: (6, 7)
+-------------+-----+-----+-------+--------+-------------+-------------------+
| file | x | y | width | height | label.right | .oxen.diff.status |
| --- | --- | --- | --- | --- | --- | --- |
| str | i64 | i64 | i64 | i64 | str | str |
+-------------+-----+-----+-------+--------+-------------+-------------------+
| image_0.jpg | 0 | 0 | 10 | 10 | cat | modified |
| image_1.jpg | 1 | 2 | 10 | 20 | null | removed |
| image_1.jpg | 200 | 100 | 10 | 20 | dog | added |
| image_2.jpg | 4 | 10 | 20 | 20 | null | removed |
| image_3.jpg | 4 | 10 | 20 | 20 | dog | added |
| image_4.jpg | 10 | 10 | 10 | 10 | dog | added |
+-------------+-----+-----+-------+--------+-------------+-------------------+
```
Under the hood Oxen.ai is using a combination of hashing and diffing algorithms to find the differences in your datasets. This allows you to quickly find changes in your datasets, whether they are rows, columns, or individual cells. Oxen's diff tool tries to strike a balance between being easy to use and being flexible enough to handle complex datasets.
# Diff Types
Oxen.ai currently supports a [TextDiff](/python-api/diff/text_diff) and a [TabularDiff](/python-api/diff/tabular_diff) data type.
The `TabularDiff` data type is used to represent the differences in tabular data, such as CSV, TSV, or Parquet files. The `TextDiff` data type is used to represent the differences in text files, such as markdown, code, or configuration files. In the future, we plan to add support for other data types such as images, audio, and video.
# Diff Command Syntax
The `oxen diff` command supports multiple syntax patterns similar to Git:
* `oxen diff` - Compare working tree with HEAD
* `oxen diff ` - Compare specific file in working tree with HEAD
* `oxen diff ` - Compare commit with HEAD
* `oxen diff ..` - Compare two commits using range syntax
* `oxen diff ` - Compare two commits
* `oxen diff [--] [...]` - Compare commit with HEAD for specific paths
* `oxen diff .. [--] [...]` - Compare two commits for specific paths
* `oxen diff file1 file2` - Compare two local files (not in repository)
The `..` separator can be used to specify commit ranges, making it easy to compare different versions of your data.
# Pick Your Tooling
All the functionality below is available through the [π₯οΈ Command Line](/getting-started/command-line/start_repository), [π¦ Rust Library](https://crates.io/crates/liboxen), [π Python Library](/python-api), as well as the [π Web Interface](https://oxen.ai). This guide will focus on the command line tooling, but the same principles apply to the other interfaces.
Using the [Oxen.ai Hub](https://oxen.ai) you can quickly visualize and navigate the changes in your datasets with an easy to use interface. Sign up for free π [here](https://oxen.ai/register).
We will build up from simple examples to more complex ones. Starting from adding and removing rows, to modifying rows, to detecting schema changes, and finally providing specific target fields you are interested in.
All the data below can be found in the [datasets/diff-examples repository](https://www.oxen.ai/datasets/diff-examples).
# Let's Build a Dataset
In order to demonstrate how to use the `oxen diff` command, we will need a dataset to work with. Imagine we are collecting a dataset for fine-tuning a Large Language Model (LLM). This dataset will have a set of `prompts` and a `category` that they belong to.
Create a new file called `dataset.csv` and add the following data to it.
```
prompt,category
What is the capital of France?,geography
What is 2+10?,math
What is the capital of Germany?,geography
What is the best python library for http requests?,programming
Tell me a story about an ox.,story
```
If you are not familiar with the `oxen df` command it is a handy tool to manipulate and inspect tabular data. You can use it with any CSV, TSV, Parquet, or line delimited JSON file.
```bash CLI theme={null}
oxen df dataset.csv
```
```python Python theme={null}
import oxen
# the oxen.df module can save and load files into a polars data frame
# for more info on polars see: https://pola.rs/
# it will infer the type of data from the fileextension
df = oxen.df("dataset.csv")
print(df)
```
```
shape: (5, 2)
+-----------------------------------+-------------+
| prompt | category |
| --- | --- |
| str | str |
+-----------------------------------+-------------+
| What is the capital of France? | geography |
| What is 2+10? | math |
| What is the capital of Germany? | geography |
| What is the best python library β¦ | programming |
| Tell me a story about an ox. | story |
+-----------------------------------+-------------+
```
In order to return to this initial version of the data at any point, let's add and commit it to a local Oxen repository.
```bash CLI theme={null}
oxen init
oxen add dataset.csv
oxen commit -m "Initial dataset"
```
```python Python theme={null}
import os
from oxen import Repo
repo = Repo("/path/to/data")
repo.init()
repo.add("dataset.csv")
repo.commit("Adding my data")
```
## Adding Rows
Let's start with a completely additive workflow as if we are collecting a large datasets of prompts. Add a row to the dataset by simply appending to the file.
```bash theme={null}
echo "20*20,math" >> dataset.csv
```
If you want to see the changes between the current version of your file and the previous version, you can use the `oxen diff` command. By default, Oxen compares the working tree with the HEAD commit (the last committed version).
```bash CLI theme={null}
oxen diff dataset.csv
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv")
print(result.get())
```
```
Row changes:
+ 1 (added)
shape: (1, 3)
+--------+----------+-------------------+
| prompt | category | .oxen.diff.status |
| --- | --- | --- |
| str | str | str |
+--------+----------+-------------------+
| 20*20 | math | added |
+--------+----------+-------------------+
```
As you can see Oxen found the one added row and augmented the data frame with an `.oxen.diff.status` column to show the status of the row.
There are three possible values for the `.oxen.diff.status` column:
* `added`
* `removed`
* `modified`
## Removing Rows
Next remove the first entry of the file to see how Oxen handles deletions. We will use the `sed` command with the in place flag `-i` to remove the first row from the file.
```bash theme={null}
sed -i '' '2d' dataset.csv
```
(Note: the `-i ''` flag is for MacOS, if you are using Linux you can simply use `-i`.) Since the file is a CSV with a header row, you will need to remove the second row hence `2d`.
Verify that the first row was removed by using the `oxen diff` command.
```bash CLI theme={null}
oxen diff dataset.csv
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv")
print(result.get())
```
```
Row changes:
+ 1 (added)
- 1 (removed)
shape: (2, 3)
+--------------------------------+-----------+-------------------+
| prompt | category | .oxen.diff.status |
| --- | --- | --- |
| str | str | str |
+--------------------------------+-----------+-------------------+
| What is the capital of France? | geography | removed |
| 20*20 | math | added |
+--------------------------------+-----------+-------------------+
```
## Modifing Rows
This is great for adding and removing rows, but what about modifying rows? Say we change the `category` of "geography" to be a more generic "trivia" category and add a new prompt to it "What is the fastest land animal?".
Edit the `datasets.csv` file to look like this:
```bash theme={null}
prompt,category
What is 2+10?,math
What is the capital of Germany?,trivia
What is the best python library for http requests?,programming
Tell me a story about an ox.,story
20*20,math
What is the fastest land animal?,trivia
```
If we run the `oxen diff` command again, we will see the changes.
```
Row changes:
+ 3 (added)
- 2 (removed)
shape: (5, 3)
+----------------------------------+-----------+-------------------+
| prompt | category | .oxen.diff.status |
| --- | --- | --- |
| str | str | str |
+----------------------------------+-----------+-------------------+
| What is the capital of France? | geography | removed |
| What is the capital of Germany? | geography | removed |
| 20*20 | math | added |
| What is the capital of Germany? | trivia | added |
| What is the fastest land animal? | trivia | added |
+----------------------------------+-----------+-------------------+
```
You'll notice that for every row we modified we end up having +1 addition and +1 removal. This is because Oxen is treating the modified row as one added row and one removed row.
## Specifying Keys
The reason that the above example treats the modified row as a new row and a removed row is because both the `prompt` and `category` columns being considered keys under the hood. `oxen diff` hashes the combination of keys in order to find differences in the data. The default keys are all the common columns between the two versions of the datasets.
If you have a unique identifier for each row, you can use the `--keys` (or `-k`) flag to specify the column or columns that should be used as the primary keys.
```bash CLI theme={null}
oxen diff dataset.csv -k prompt
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv", keys=["prompt"])
print(result.get())
```
```
Row changes:
Ξ 1 (modified)
+ 2 (added)
- 1 (removed)
shape: (4, 4)
+----------------------------------+---------------+----------------+-------------------+
| prompt | category.left | category.right | .oxen.diff.status |
| --- | --- | --- | --- |
| str | str | str | str |
+----------------------------------+---------------+----------------+-------------------+
| 20*20 | null | math | added |
| What is the capital of France? | geography | null | removed |
| What is the capital of Germany? | geography | trivia | modified |
| What is the fastest land animal? | null | trivia | added |
+----------------------------------+---------------+----------------+-------------------+
```
Great! This collapsed our added and removed row into a single modified row. The category column has now been split into two columns, `category.left` and `category.right`, to show the old and new values.
Assumming these changes look good, you can add and commit the changes to your local repository.
```bash CLI theme={null}
oxen add dataset.csv
oxen commit -m "Added and removed rows"
```
```python Python theme={null}
# ... instantiate repo
repo.add("dataset.csv")
repo.commit("Added and removed rows")
```
## Adding Columns
Adding and removing rows is great, but what about changes to the schema itself? Instead of using the prompt as a key, let's add an `id` column to the dataset and use that as the key. Let's also add an `answer` column to the dataset, so that we can evaluate the responses.
Update your raw csv with the new columns like so:
```
id,prompt,answer,category
0,What is 2+10?,12,math
1,What is the capital of Germany?,Berlin,trivia
2,What is the best python library for http requests?,requests,programming
3,Tell me a story about an ox.,I am sorry I cannot do that.,story
4,20*20,400,math
5,What is the fastest land animal?,cheetah,trivia
```
Now if you run the `oxen diff` command, you will see that it automatically detects the added columns and displays the new values in `id.right` and `answer.right`.
```bash CLI theme={null}
oxen diff dataset.csv
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv")
print(result.get())
```
```
Column changes:
+ id (i64)
+ answer (str)
Row changes:
Ξ 6 (modified)
shape: (6, 5)
+-----------------------------------+-------------+----------+------------------------------+-------------------+
| prompt | category | id.right | answer.right | .oxen.diff.status |
| --- | --- | --- | --- | --- |
| str | str | i64 | str | str |
+-----------------------------------+-------------+----------+------------------------------+-------------------+
| 20*20 | math | 4 | 400 | modified |
| What is 2+10? | math | 0 | 12 | modified |
| What is the best python library β¦ | programming | 2 | requests | modified |
| Tell me a story about an ox. | story | 3 | I am sorry I cannot do that. | modified |
| What is the capital of Germany? | trivia | 1 | Berlin | modified |
| What is the fastest land animal? | trivia | 5 | cheetah | modified |
+-----------------------------------+-------------+----------+------------------------------+-------------------+
```
Removing a column would show the values in columns called `.left` to show the values in columns that are now missing. If you are happy with the changes, you can add and commit the changes to your local repository.
```bash CLI theme={null}
oxen add dataset.csv
oxen commit -m "Added id and answer column"
```
```python Python theme={null}
# ... instantiate repo
repo.add("dataset.csv")
repo.commit("Added id and answer column")
```
## Specifying Compares
Not only can you specify keys to narrow down the scope of what fields oxen hashes, but you can also specify columns to compare with the `--compares` (`-c`) flag. This specifies the fields oxen compares.
You can think of the keys as the fields that are hashed to create a unique id to tell if a row was added or removed. The compares are the fields that are compared to check if a row was modified. By default if you specify a single key, the rest of the columns become the compares. If you specify multiple keys, the compares are all the columns that are not keys.
To see this in action, let's add one row, remove one row, and modify 3 existing ones to demonstrate how this works. In this case we will only modify values of the `answer` column.
Overwrite the `dataset.csv` file with the following data.
```
id,prompt,answer,category
0,What is 2+10?,12,math
1,What is the capital of Germany?,The capital of Germany is Berlin,trivia
3,Tell me a story about an ox.,I am sorry Hal.,story
4,20*20,20*20=400,math
5,What is the fastest land animal?,cheetah,trivia
6,What is Oxen.ai?,Imagine git - but can handle large datasets,trivia
```
Since we only modified the answers in this dataset and not the category or the prompt, we can use the `-c` flag to specify that we are only interested in changes in the `answer` column.
```bash CLI theme={null}
oxen diff dataset.csv -k id,prompt -c answer
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv", keys=["id", "prompt"], compares=["answer"])
print(result.get())
```
```
Row changes:
Ξ 3 (modified)
+ 1 (added)
- 1 (removed)
shape: (5, 5)
+-----+-----------------------------+----------------------------+------------------------+-------------------+
| id | prompt | answer.left | answer.right | .oxen.diff.status |
| --- | --- | --- | --- | --- |
| i64 | str | str | str | str |
+-----+-----------------------------+----------------------------+------------------------+-------------------+
| 1 | What is the capital of | The capital of Germany is | Berlin | modified |
| | Germany? | Berlin | | |
| 2 | What is the best python | null | requests | added |
| | library β¦ | | | |
| 3 | Tell me a story about an | I am sorry Hal. | I am sorry I cannot do | modified |
| | ox. | | that. | |
| 4 | 20*20 | 20*20=400 | 400 | modified |
| 6 | What is Oxen.ai? | Imagine git - but can | null | removed |
| | | handle lar⦠| | |
+-----+-----------------------------+----------------------------+------------------------+-------------------+
```
Contrast this with a default diff which will show 8 changes, 4 added and 4 removed, and you can see the id field is duplicated because we are flagging one addition and one removal for each changed row.
```bash CLI theme={null}
oxen diff dataset.csv
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv")
print(result.get())
```
```
Row changes:
+ 4 (added)
- 4 (removed)
shape: (8, 5)
+-----+----------------------------------+----------------------------------+-------------+-------------------+
| id | prompt | answer | category | .oxen.diff.status |
| --- | --- | --- | --- | --- |
| i64 | str | str | str | str |
+-----+----------------------------------+----------------------------------+-------------+-------------------+
| 1 | What is the capital of Germany? | Berlin | trivia | added |
| 1 | What is the capital of Germany? | The capital of Germany is Berlin | trivia | removed |
| 2 | What is the best python library | requests | programming | added |
| | β¦ | | | |
| 3 | Tell me a story about an ox. | I am sorry Hal. | story | removed |
| 3 | Tell me a story about an ox. | I am sorry I cannot do that. | story | added |
| 4 | 20*20 | 20*20=400 | math | removed |
| 4 | 20*20 | 400 | math | added |
| 6 | What is Oxen.ai? | Imagine git - but can handle | trivia | removed |
| | | lar⦠| | |
+-----+----------------------------------+----------------------------------+-------------+-------------------+
```
A diff that only specifies a key will show the correct number of changes, but it may have many columns that are not relevant to the changes you are interested in. This is because under the hood Oxen infers the compares to be the remaining columns. Having more control over the compares is where the `-c` flag comes in handy.
To see how this works, try using the `-k` flag on the same dataset without any compares.
```bash CLI theme={null}
oxen diff dataset.csv -k id
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv", keys=["id"])
print(result.get())
```
```
Row changes:
Ξ 3 (modified)
+ 1 (added)
- 1 (removed)
shape: (5, 7)
+-----+-----------------+-----------------+-----------------+---------------+----------------+----------------+
| id | prompt | answer.left | answer.right | category.left | category.right | .oxen.diff.sta |
| --- | --- | --- | --- | --- | --- | tus |
| i64 | str | str | str | str | str | --- |
| | | | | | | str |
+-----+-----------------+-----------------+-----------------+---------------+----------------+----------------+
| 1 | What is the | The capital of | Berlin | trivia | trivia | modified |
| | capital of | Germany is | | | | |
| | Germany? | Berlin | | | | |
| 2 | What is the | null | requests | null | programming | added |
| | best python | | | | | |
| | library β¦ | | | | | |
| 3 | Tell me a story | I am sorry Hal. | I am sorry I | story | story | modified |
| | about an ox. | | cannot do that. | | | |
| 4 | 20*20 | 20*20=400 | 400 | math | math | modified |
| 6 | What is | Imagine git - | null | trivia | null | removed |
| | Oxen.ai? | but can handle | | | | |
| | | lar⦠| | | | |
+-----+-----------------+-----------------+-----------------+---------------+----------------+----------------+
```
The above output is `(5 rows x 7 columns)` which isn't too bad, but if you have a dataset with many columns, it can quickly become overwhelming with irrelevant information. If you know where to look, you can use the `-c` flag to narrow down the scope of the diff.
## Saving Results
The `--output` (`-o`) flag can be used to save the results of the diff to a new file. This is useful if you want to save the results of the diff to a new file for further inspection or to share with others.
```bash CLI theme={null}
oxen diff dataset.csv -o diff.csv
```
```python Python theme={null}
import oxen
oxen.diff("dataset.csv", output="diff.csv")
```
The above command will save the results of the diff to a new file called `diff.csv`. You can then load it into a jupyter notebook, pandas, or even back into Oxen to do more analysis on the results.
## Real World Example
To drive all these features home, imagine you have taken the dataset above and run it through an LLM with a prompt to get the responses. You have saved the results in a new file called `model_results.csv`.
Below is an example script that runs the prompts through `gpt-3.5-turbo` and saves the results to a new file. This script uses the `openai` python package to interact with the OpenAI API.
[process\_csv\_with\_openai.py](https://www.oxen.ai/datasets/diff-examples/file/main/process_with_openai.py)
```python theme={null}
import csv
import time
from openai import OpenAI
import argparse
import os
client = OpenAI(
# This is the default and can be omitted
api_key=os.environ.get("OPENAI_API_KEY"),
)
def process_csv_with_gpt4(input_csv, output_csv):
print(f'Processing {input_csv} with GPT-4 and writing to {output_csv}')
with open(input_csv, mode='r', encoding='utf-8') as infile, open(output_csv, mode='w', newline='', encoding='utf-8') as outfile:
reader = csv.DictReader(infile)
fieldnames = ['id', 'prompt', 'answer', 'category', 'response', 'is_correct', 'model', 'inference_time']
writer = csv.DictWriter(outfile, fieldnames=fieldnames)
writer.writeheader()
for row in reader:
start_time = time.time()
print(f'Processing row: {row}')
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": row['prompt'],
}
],
model="gpt-3.5-turbo",
)
end_time = time.time()
inference_time = end_time - start_time
# Simplified correctness check; customize based on your needs
print(f'Chat completion: {chat_completion}')
response = chat_completion.choices[0].message.content.strip()
is_correct = 'yes' if row['answer'].lower() in response.lower() else 'no'
writer.writerow({
'id': row['id'],
'prompt': row['prompt'],
'answer': row['answer'],
'category': row['category'],
'response': response,
'is_correct': is_correct,
'model': 'gpt-3.5-turbo', # Adjust based on the model used
'inference_time': inference_time
})
# main
if __name__ == '__main__':
# argparse can be used to accept input/output file names from command line
parser = argparse.ArgumentParser(description='Process CSV with GPT-4')
parser.add_argument('input_csv', help='Input CSV file')
parser.add_argument('output_csv', help='Output CSV file')
args = parser.parse_args()
process_csv_with_gpt4(args.input_csv, args.output_csv)
```
Run this script on the `dataset.csv` file to get the `model_results.csv` file.
```bash theme={null}
python process_csv_with_openai.py dataset.csv model_results.csv
```
Quickly inspect the `model_results.csv` file with the `oxen df` command to make sure the csv was created correctly.
```bash theme={null}
oxen df model_results.csv
```
Output:
```
shape: (6, 8)
+-----+--------------------------+------------------------+-------------+--------------------------------+------------+-------+----------------+
| id | prompt | answer | category | response | is_correct | model | inference_time |
| --- | --- | --- | --- | --- | --- | --- | --- |
| i64 | str | str | str | str | str | str | f64 |
+-----+--------------------------+------------------------+-------------+--------------------------------+------------+-------+----------------+
| 0 | What is 2+10? | 12 | math | 2+10=12 | yes | gpt-4 | 0.750142 |
| 1 | What is the capital of | Berlin | trivia | Berlin | yes | gpt-4 | 0.428595 |
| | Germany? | | | | | | |
| 2 | What is the best python | requests | programming | There is no one best library | yes | gpt-4 | 3.663857 |
| | library ⦠| | | for⦠| | | |
| 3 | Tell me a story about an | I am sorry I cannot do | story | Once upon a time in a small | no | gpt-4 | 7.15331 |
| | ox. | that. | | vill⦠| | | |
| 4 | 20*20 | 400 | math | 400 | yes | gpt-4 | 0.422363 |
| 5 | What is the fastest land | cheetah | trivia | The fastest land animal is the | yes | gpt-4 | 0.91197 |
| | animal? | | | c⦠| | | |
+-----+--------------------------+------------------------+-------------+--------------------------------+------------+-------+----------------+
```
This dataset has the same `id`, `prompt`, `answer`, and `category` columns as the original dataset, but it also has some additional columns such as `response`, `is_correct`, `model`, and `inference_time`.
Add and commit the model results to your local repository.
```bash theme={null}
oxen add model_results.csv
oxen commit -m "Added model results"
```
Let's say you tweaked the prompt and wanted to run the dataset through the LLM again. Since you have the results versioned in your local repository, you can fearlessly overwrite the file and run the `oxen diff` command to see the differences.
Overwrite the `model_results.csv` file with the new results.
```
id,prompt,answer,category,response,is_correct,model,inference_time
0,What is 2+10?,12,math,12,true,model-2,0.21
1,What is the capital of Germany?,Berlin,trivia,Berlin,true,model-2,0.12
2,What is the best python library for http requests?,requests,programming,requests,true,model-2,0.31
3,Tell me a story about an ox.,I am sorry I cannot do that.,story,I am sorry I cannot do that.,true,model-2,0.23
4,20*20,400,math,400,true,model-2,0.09
5,What is the fastest land animal?,cheetah,trivia,cheetah,true,model-2,0.41
```
If we do a base diff without any flags, we will see that every row is has been marked as added and removed, since the `model` and `inference_time` columns could be different for each row.
```bash CLI theme={null}
oxen diff dataset.csv
```
```python Python theme={null}
import oxen
result = oxen.diff("dataset.csv")
print(result.get())
```
```
Row changes:
+ 6 (added)
- 6 (removed)
shape: (12, 9)
+-----+----------------------------------+---------+----------+---+------------+---------+----------------+-------------------+
| id | prompt | answer | category | β¦ | is_correct | model | inference_time | .oxen.diff.status |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| i64 | str | str | str | | bool | str | f64 | str |
+-----+----------------------------------+---------+----------+---+------------+---------+----------------+-------------------+
| 4 | 20*20 | 400 | math | β¦ | true | model-2 | 0.09 | added |
| 4 | 20*20 | 400 | math | β¦ | true | model-1 | 0.1 | removed |
| 0 | What is 2+10? | 12 | math | β¦ | true | model-2 | 0.21 | added |
| 0 | What is 2+10? | 12 | math | β¦ | true | model-1 | 0.23 | removed |
| β¦ | β¦ | β¦ | β¦ | β¦ | β¦ | β¦ | β¦ | β¦ |
| 1 | What is the capital of Germany? | Berlin | trivia | β¦ | true | model-2 | 0.12 | added |
| 1 | What is the capital of Germany? | Berlin | trivia | β¦ | false | model-1 | 0.11 | removed |
| 5 | What is the fastest land animal? | cheetah | trivia | β¦ | true | model-1 | 0.4 | removed |
| 5 | What is the fastest land animal? | cheetah | trivia | β¦ | true | model-2 | 0.41 | added |
+-----+----------------------------------+---------+----------+---+------------+---------+----------------+------------
```
This is clearly not what we want. We want to see the differences in the `response` and `is_correct` columns, and ignore the `model` and `inference_time` columns.
In combination with the `--keys` flag, you can use the `--compares` (or `-c`) flag to specify the columns you are interested in.
```bash CLI theme={null}
oxen diff model_results.csv -k id,prompt,answer -c response,is_correct
```
```python Python theme={null}
import oxen
result = oxen.diff(
"dataset.csv",
keys=["id", "prompt", "answer"],
compares=["response", "is_correct"]
)
print(result.get())
```
```
Row changes:
Ξ 2 (modified)
shape: (2, 8)
+-----+----------------+----------------+----------------+----------------+----------------+----------------+---------------+
| id | prompt | answer | response.left | response.right | is_correct.lef | is_correct.rig | .oxen.diff.st |
| --- | --- | --- | --- | --- | t | ht | atus |
| i64 | str | str | str | str | --- | --- | --- |
| | | | | | bool | bool | str |
+-----+----------------+----------------+----------------+----------------+----------------+----------------+---------------+
| 1 | What is the | Berlin | Munich | Berlin | false | true | modified |
| | capital of | | | | | | |
| | Germany? | | | | | | |
| 3 | Tell me a | I am sorry I | Once upon a | I am sorry I | false | true | modified |
| | story about an | cannot do | time | cannot do | | | |
| | ox. | that. | | that. | | | |
+-----+----------------+----------------+----------------+----------------+----------------+----------------+---------------+
```
This now narrows down the scope of the diff to only the `response` and `is_correct` columns. We can see that the new model has a different response for the prompts `1` and `3`. Diff allows us to quickly narrow down the responses that model 1 and model 2 disagree on, and which ones are correct.
## Next Up: Comparing Different Files
Now that you understand the basics of the diff command, you may be wondering if you can compare different files or different commits. The answer is yes!
## Comparing Different Files
You can compare two local files (that may not be in the repository) by passing both file paths:
```bash CLI theme={null}
oxen diff model_results_1.csv model_results_2.csv
```
```python Python theme={null}
import oxen
result = oxen.diff("model_results_1.csv", to="model_results_2.csv")
print(result.get())
```
## Comparing Different Commits
You can also compare the same file across different commits or branches using commit identifiers:
```bash CLI theme={null}
# Compare a specific commit with HEAD
oxen diff abc123 dataset.csv
# Compare two commits using range syntax
oxen diff abc123..def456 dataset.csv
# Compare two commits
oxen diff abc123 def456 dataset.csv
# Compare branches
oxen diff main..feature-branch dataset.csv
```
```python Python theme={null}
import oxen
# Compare specific commit with HEAD
result = oxen.diff("dataset.csv", revision="abc123")
print(result.get())
```
This is useful for tracking how your dataset has evolved over time, comparing different versions of model results, or understanding what changed between branches.
You can find all the example data used in this guide in the [datasets/diff-examples repository](https://www.oxen.ai/datasets/diff-examples).
[Next: Comparing Different Files](/concepts/compare)
# π·οΈ File Metadata
Source: https://docs.oxen.ai/concepts/file_metadata
Oxen.ai gives you the flexibility to attach metadata to files to make them more discoverable and useful.
## Data Type Detection
By default, Oxen.ai will detect the data type of a file based on the file extension and content type. The default data types are:
* `tabular` -> `csv`, `tsv`, `jsonl`, `parquet`, `arrow`
* `text` -> `txt`
* `image` -> `png`, `jpg`, `jpeg`, `gif`, `bmp`, `tiff`, `webp`
* `video` -> `mp4`, `mov`
* `audio` -> `mp3`, `wav`, `m4a`, `ogg`, `flac`
## Tabular Data
When you add a tabular file to Oxen, it automatically detects and versions the schema of any tabular data. This is done by using [Polars](https://www.pola.rs/) under the hood to infer the column names and datatypes.
To list all the schemas that have been detected and committed, you can use the `oxen schemas` subcommand.
```bash theme={null}
oxen schemas
```
Output:
```
+-----------------------+------+----------------------------------+------------------------+
| path | name | hash | fields |
+==========================================================================================+
| annotations/train.csv | ? | 53732ea1c2a9ba5807bd59978ebb69f5 | [file, ..., is_fluffy] |
|-----------------------+------+----------------------------------+------------------------|
| annotations/test.csv | ? | 36d0edc8779f42e30b0d630aa83bc83c | [file, ..., height] |
+-----------------------+------+----------------------------------+------------------------+
```
The schema detection is done on a per file basis. This means that if you have a directory of csv or parquet files, each file will have its own schema.
## View Schema
To view a specific schema, you can pass in a schema hash, name, or path to the `oxen schemas` command.
```bash theme={null}
oxen schemas annotations/train.csv
```
Output:
```
+--------+-------+----------+
| name | dtype | metadata |
+===========================+
| file | str | |
|--------+-------+----------|
| label | str | |
|--------+-------+----------|
| min_x | f64 | |
|--------+-------+----------|
| min_y | f64 | |
|--------+-------+----------|
| width | i64 | |
|--------+-------+----------|
| height | i64 | |
+--------+-------+----------+
```
## Add Schema
Schemas are automatically detected when you add `csv`, `tsv`, `jsonl`, `parquet`, and `arrow` files to Oxen. Before a schema is committed, you can see the detected schemas in the `oxen status` command.
```bash theme={null}
oxen add annotations/train.csv
oxen status
```
Output:
```
On branch main -> 503591398980c485
Directories to be committed
added: annotations with 1 file
Files to be committed:
(use "oxen restore --staged ..." to unstage)
modified: annotations/train.csv
Schemas to be committed
(use "oxen schemas show --staged " to view staged schema)
detected schema: annotations/train.csv 23d86a4c1481b817b57ee8ccd7d9016b
```
To view more detailed information about the detected schema, use the `--staged` flag on the `oxen schemas` command.
```bash theme={null}
oxen schemas --staged annotations/train.csv
```
Output:
```
annotations/train.csv 23d86a4c1481b817b57ee8ccd7d9016b
+-----------+-------+----------+
| name | dtype | metadata |
+==============================+
| file | str | |
|-----------+-------+----------|
| label | str | |
|-----------+-------+----------|
| min_x | f64 | |
|-----------+-------+----------|
| min_y | f64 | |
|-----------+-------+----------|
| width | f64 | |
|-----------+-------+----------|
| height | f64 | |
|-----------+-------+----------|
| is_fluffy | str | |
|-----------+-------+----------|
| breed | str | |
+-----------+-------+----------+
```
To view how Polars interprets the schema before adding the file, you can use the `oxen df` command with the `--schema` flag.
```bash theme={null}
oxen df annotations/train.csv --schema
```
Output:
```
+-----------+-------+
| column | dtype |
+===================+
| file | str |
|-----------+-------|
| label | str |
|-----------+-------|
| min_x | f64 |
|-----------+-------|
| min_y | f64 |
|-----------+-------|
| width | f64 |
|-----------+-------|
| height | f64 |
|-----------+-------|
| is_fluffy | str |
|-----------+-------|
| breed | str |
+-----------+-------+
```
## Additional Metadata
You can also add additional information to the schema. This is useful if you want to provide context about the data for a UI, data fetching, or any other reason.
Notice the empty column `metadata` in the schema above. You can add arbitrary JSON blobs to the schema itself, as well as each column.
Metadata may provide useful information for your end application:
* Transforms you want to perform.
* How you want to render the data.
* Information about the data itself, such as a description of the schema or colun.
## Schema Metadata
At the root of each schema is an `Optional` metadata value. This is useful for adding information about the schema itself. For example, you can add a description of the schema or a json blob that gives context to a data renderer.
```bash theme={null}
oxen schemas add annotations/train.csv -m '{"task": "bounding_box", "description": "Extracting bounding boxes from images"}'
```
You will see the additional metadata listed above the schema if it is added.
```
"annotations/train.csv"
{"task": "bounding_box", "description": "Extracting bounding boxes from images"}
+-----------+-------+----------+
| name | dtype | metadata |
+==============================+
| file | str | |
|-----------+-------+----------|
| label | str | |
|-----------+-------+----------|
| min_x | f64 | |
|-----------+-------+----------|
| min_y | f64 | |
|-----------+-------+----------|
| width | f64 | |
|-----------+-------+----------|
| height | f64 | |
|-----------+-------+----------|
| is_fluffy | str | |
|-----------+-------+----------|
| breed | str | |
+-----------+-------+----------+
```
## Column Metadata
You can also add metadata to specific columns. Say you wanted to add information to the `file` column about the root directory of the images, you could do the following:
```bash theme={null}
oxen schemas add annotations/train.csv -c 'file' -m '{"root": "images/"}'
```
Output:
```
"annotations/train.csv"
+-----------+-------+---------------------+
| name | dtype | metadata |
+=========================================+
| file | str | {"root": "images/"} |
|-----------+-------+---------------------|
| label | str | |
|-----------+-------+---------------------|
| min_x | f64 | |
|-----------+-------+---------------------|
| min_y | f64 | |
|-----------+-------+---------------------|
| width | f64 | |
|-----------+-------+---------------------|
| height | f64 | |
|-----------+-------+---------------------|
| is_fluffy | str | |
|-----------+-------+---------------------|
| breed | str | |
+-----------+-------+---------------------+
```
The `-c` flag stands for `column` and the `-m` flag stands for `metadata`. The metadata is a JSON blob that can be used to store any information you want.
The [OxenHub UI](https://oxen.ai) uses schema metadata to render more complex datatypes in the UI. For example viewing inline images directly in a dataframe.
## Setting Metadata Over HTTP
The commands above stage schema changes in a local repository. To do the same
from an application, [oxen-server](/getting-started/oxen-server) exposes both
levels as endpoints on a [workspace](/concepts/remote-repos). A workspace stages
the change against a commit, so metadata can be set without committing first β
useful for data frames an application edits continuously.
Both endpoints **replace** the metadata at their level rather than merging into
it. Read the current value from the data frame's schema and merge your keys into
it, or you will drop metadata someone else set.
### Schema Metadata
`PUT /api/repos/:namespace/:repo_name/workspaces/:workspace_id/data_frames/schema/:path`
```bash theme={null}
curl -X PUT \
"https://hub.oxen.ai/api/repos/your-username/my-dataset/workspaces/my-workspace/data_frames/schema/annotations/train.csv" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metadata": {"task": "bounding_box", "description": "Extracting bounding boxes from images"}}'
```
Output:
```
{"status":"success","status_message":"resource_updated","oxen_version":"0.52.4"}
```
This is the HTTP equivalent of `oxen schemas add -m '{...}'`.
### Column Metadata
`POST /api/repos/:namespace/:repo_name/workspaces/:workspace_id/data_frames/columns/schema/metadata/:path`
The column is named in the body rather than the path, alongside its metadata.
```bash theme={null}
curl -X POST \
"https://hub.oxen.ai/api/repos/your-username/my-dataset/workspaces/my-workspace/data_frames/columns/schema/metadata/annotations/train.csv" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"column_name": "file", "metadata": {"_oxen": {"render": {"func": "image"}}}}'
```
Output:
```
{"status":"success","status_message":"resource_updated","oxen_version":"0.52.4"}
```
This is the HTTP equivalent of `oxen schemas add -c 'file' -m '{...}'`, and
the example above is what `--render image` writes.
### Reading It Back
Metadata set this way comes back on the data frame's schema, at
`data_frame.source.schema.metadata` for the schema itself and on each entry of
`data_frame.source.schema.fields` for a column.
```bash theme={null}
curl "https://hub.oxen.ai/api/repos/your-username/my-dataset/workspaces/my-workspace/data_frames/resource/annotations/train.csv" \
-H "Authorization: Bearer YOUR_API_KEY"
```
The `_oxen` key is reserved for metadata oxen itself understands, such as the
`render` functions described below. Your own keys can sit alongside it.
## Commit The Schema
Schemas changes will not be saved until you commit them.
To view the schemas staged for commit, you can use the `--staged` flag.
```bash theme={null}
oxen schemas --staged
```
Output:
```
+-----------------------+------+----------------------------------+--------------------+
| path | name | hash | fields |
+======================================================================================+
| annotations/train.csv | ? | 9d1fac486f95120403d7f18232fa5520 | [file, ..., breed] |
+-----------------------+------+----------------------------------+--------------------+
```
You can then commit the schema to the dataframe with the `commit` subcommand.
```bash theme={null}
oxen commit -m "Overriding schema for annotations/train.csv"
```
These changes are persistent across commits and will be carried forward.
## Name Schema
It is nice to have human readable names to refer to schemas by. Use the `oxen schemas name` command to name a schema.
```bash theme={null}
oxen schemas name annotations/train.csv bounding_box
```
## Remove Schema
If you have accidentally staged a schema, you can remove it with the `oxen schemas rm` command.
```bash theme={null}
oxen schemas rm annotations/train.csv --staged
```
## Render Images
Oxen.ai can render images through the webhub if you add the proper schema metadata.
```bash theme={null}
oxen schemas add data.csv -c 'file' --render image
```
Under the hood this applied a metadata blob to the column, telling Oxen to render an image. More verbosely it would look like:
```bash theme={null}
oxen schemas add data.csv -c 'file' -m '{
"_oxen": {
"render": {
"func": "image"
}
}
}'
```
## Render Links
Oxen.ai can render links to other files through the webhub if you add the proper schema metadata.
```bash theme={null}
oxen schemas add data.csv -c 'file' --render link
```
Under the hood this applied a metadata blob to the column, telling Oxen to render an link. More verbosely it would look like:
```bash theme={null}
oxen schemas add data.csv -c 'file' -m '{
"_oxen": {
"render": {
"func": "link"
}
}
}'
```
# π Datasets
Source: https://docs.oxen.ai/examples/data/datasets
In Oxen.ai, datasets are used to structure your unstructured data. Any tabular data file will automatically be turned into a collaborative database that can be versioned and used to organize data, can be queried, or fed in as training data for models.
Oxen.ai datasets are accessible with an easy to use web interface as well as a command line tools, python, and HTTP library.
## Repositories vs Datasets
Repositories are the top level container for your datasets. They are a collection of versioned files and directories.
Certain files within Oxen with the extensions `csv`, `tsv`, `jsonl` and `parquet` gain super powers. These dataset files can be multi-modal containing links to images, audio, and PDFs. You can query them in natural language, and edit them like a spreadsheet.
Under the hood we turn these raw files into a lightweight database that can be queried, edited, versioned, and downloaded.
## View Your Dataset
Click the file your repository to open the dataset you want to work with. If you want to follow along with this example, download the [Thinking LLMs](https://www.oxen.ai/ox/Thinking-LLMs) dataset.
## Download Your Dataset
Datasets can be downloaded directly from the UI or using the CLI and Python library. You can grab any revision of the dataset by specifying the revision as a branch name or commit id.
```python Python theme={null}
from oxen import RemoteRepo
# Connect to the remote repository (does not download any data)
repo = RemoteRepo("YOUR_USERNAME/YOUR_REPO")
# Download the dataset
repo.download("path/to/dataset.jsonl", revision="main")
```
```bash CLI theme={null}
oxen download YOUR_USERNAME/YOUR_REPO path/to/dataset.jsonl --revision main
```
## Upload Your Dataset
Once you have created a repository, you can use the "Add Files" button on your repository to upload dataset files through the UI. The dataset will automatically be versioned so you can iterate on it and track changes.
Datasets can also be uploaded from the command line or python library. This allows you to integrate Oxen into your existing codebases or CI/CD pipelines. The workflow is similar to git where you `add` and `commit` your changes.
```python Python theme={null}
from oxen import RemoteRepo
# Connect to the remote repository
repo = RemoteRepo("YOUR_USERNAME/YOUR_REPO")
# Upload the file to the remote repository
repo.add("path/to/dataset.jsonl", dst="datasets/")
# Commit the changes
repo.commit("Add dataset.jsonl to the datasets/ directory")
```
```bash CLI theme={null}
# Clone the repository
oxen clone https://hub.oxen.ai/YOUR_USERNAME/YOUR_REPO
cd YOUR_REPO
# Add the file to the repository
oxen add path/to/dataset.jsonl
# Commit the changes
oxen commit -m "Add path/to/dataset.jsonl"
# Sync the changes to the remote repository
oxen push
```
To perform **write operations** on datasets, you need to be an editor on the repository and have your username and API key set. You can set your username and API key using the [CLI](/getting-started/command-line/setup) or [Python library](/python-api/index).
Read more about [Authentication & Authorization](/getting-started/auth).
## Using `fsspec`
Since datasets are just stored as files and directories, you can interact with them directly using [fsspec](https://filesystem-spec.readthedocs.io/en/latest/). This allows you to read and write to them similar to how you would with a local file system.
For example if you want to read the contents of a file on the server, you can simply use the `open` method.
```python Python theme={null}
import oxen
fs = oxen.OxenFS("YOUR_USERNAME", "YOUR_REPO")
with fs.open("path/to/dataset.jsonl") as f:
content = f.read()
# Print the first 100 characters of the file
print(content[:100])
```
If you want to write to a file, you can use the `write` method.
```python Python theme={null}
fs = oxen.OxenFS("YOUR_USERNAME", "YOUR_REPO")
with fs.open("path/to/dataset.jsonl", "w") as f:
f.write("Hello, world!")
```
If you want to specify a commit message, simply add the commit message in the scope of the `with` block.
```python Python theme={null}
fs = oxen.OxenFS("YOUR_USERNAME", "YOUR_REPO")
with fs.open("path/to/dataset.jsonl", "w") as f:
f.write("{\"question\": \"What is the capital of France?\", \"answer\": \"Paris\"}")
f.commit_message = "Add a new row"
```
To learn more about how to use fsspec with Oxen, check out the [OxenFS](/python-api/oxen_fs) documentation.
## Using Pandas
Since `OxenFS` implements the `fsspec` interface, you can use it with any library that supports `fsspec`. For example, you can use it with [pandas](https://pandas.pydata.org/) to read and write to datasets.
```python Python theme={null}
import pandas as pd
# Format: oxen:///@/
df = pd.read_parquet("oxen://openai:gsm8k@main/gsm8k_test.parquet")
# Print the first 5 rows
print(df.head())
# Apply a transformation to the dataframe
df["answer"] = df["answer"].apply(lambda x: x.upper())
# Write the dataframe to a new file
df.to_parquet("oxen://openai:gsm8k@main/gsm8k_test_new.parquet")
```
## Your Dataset is a Database
Datasets look like raw files on the surface, but some of their superpowers come from the fact that Oxen.ai can index them into a [DuckDB](https://duckdb.org/) database on the remote server. This allows you to query your dataset directly with SQL.
You can use the [DataFrame](/python-api/data_frame) class in the python library to interact with your dataset as a database.
```python Python theme={null}
from oxen import DataFrame
# Connect to and index the data frame
df = DataFrame("YOUR_USERNAME/YOUR_REPO", "path/to/file.jsonl")
# Print the first 5 rows that are spam
results = df.query("SELECT * FROM df where category = 'spam' LIMIT 5")
print(results)
```
Not only can you query your dataset, but you can also add rows, add columns, and perform other database operations before committing your changes. This is useful if you want to build labeling workflows or other data pipelines.
```python Python theme={null}
from oxen import DataFrame
# Connect to and index the data frame
# Note: This must be an existing file committed to the repo
# indexing may take a while for large files
df = DataFrame("YOUR_USERNAME/YOUR_REPO", "path/to/file.jsonl")
# Add a row
row_id = data_frame.insert_row({"category": "spam", "message": "Hello, do I have an offer for you!"})
# Get a row by id
row = data_frame.get_row_by_id(row_id)
print(row)
# Update a row
row = data_frame.update_row(row_id, {"category": "ham"})
print(row)
# Delete a row
data_frame.delete_row(row_id)
# Get the current changes to the data frame
status = data_frame.diff()
print(status)
# Commit the changes
data_frame.commit("Updating data.csv")β
```
Note: There are currently some limitations to the DataFrame API.
1. You must have **write access** to the repository to use the DataFrame API. This is because it creates a [workspace](/python-api/workspace) on the remote server to index the dataset.
2. Indexing may take a while for large files, and is performed on instantiation of the DataFrame object.
3. The DataFrame API is currently only supported for single files, you cannot yet use it to JOIN datasets across files.
## Datasets as a Vector Database
If you have a column in your dataset that contains a vector of floats representing a piece of text or image, you can use Oxen.ai as a vector database to sort by similarity.
```python Python theme={null}
from oxen import DataFrame
# Connect to and index the data frame
df = DataFrame("YOUR_USERNAME/YOUR_REPO", "path/to/file.parquet")
# Check if the dataset is indexed for embeddings search
if not df.is_nearest_neighbors_enabled():
# Enable nearest neighbors search
df.enable_nearest_neighbors()
# Get an embedding for a specific row (may return multiple embeddings there are multiple results for the query)
embed_column = "embedding"
embeddings = df.get_embeddings({"prompt": "What is the capital of France?"}, column=embed_column)
# Query the data frame
embedding = embeddings[0]
results = df.query(
embedding=embedding,
sort_by_similarity_to=embed_column
)
for row in results:
print(row["prompt"])
```
If you don't have an embedding column, you can compute one using an Evaluation on the Oxen.ai platform.
## Rendering Images and Links
In the dataset viewer, you can render images and links to other files in the dataset. The assumption is that the value in the rows is a relative path to a file in the same repository. For example if we have a directory of images in the `images` directory, we can render the image by using the relative path to the image `images/my_image_0.png`.
In order to enable the rendering, you need to edit the `render` function in the dataset viewer. Go into the edit mode of the dataset, then edit the column you want to render. You can select from a few different rendering options including: `image`, `link`, `markdown`, and `code`.
This will save metadata to the repository that will be used to render the images and links. To programmatically set the render function, checkout the [file metadata documentation](/concepts/file_metadata).
## Using the UI
### Editing Your Dataset
You can edit your dataset directly from the UI by clicking the pencil icon in the upper right of the dataset viewer. This will open the file in an editor that will allow to add, edit, and delete rows and columns.
The editor will not commit any changes to the repository until you use the "Commit" button to write a message and save your changes.
### Use LLMs to Augment Your Dataset
In Oxen.ai, you can generate new columns and rows using LLMs. This is a great way to automatically label your dataset or generate training data for small LLMs from larger models. Click the "Actions" button and select "Run Inference".
Simply select a model, write a prompt, and run the model row by row on the dataset.
### Query Your Dataset
To query your dataset, write a question in plain English in the search bar. This will automatically translate the question into a SQL query and apply it to the view of your data. For example, you can look at the distribution of question types by asking:
```
What are all the categories sorted by count?
```
If the query engine makes a mistake, no worries! You can edit the SQL query to get the results you want.
# π₯ Performance
Source: https://docs.oxen.ai/examples/data/performance
# πΌοΈ 1 Million Files Benchmark
When we first started working on Oxen.ai, we were inspired by making a tool that would make it easy to collaborate on large datasets that power modern AI research.
One dataset that comes to mind is the original [ImageNet](https://image-net.org/) dataset. This dataset spans 1000 object classes and contains > 1,000,000 training images and 100,000 test images. It commonly gets shared as a tarball, zip file, or gets dumped to S3 without much visibility into the data itself.
A version control system (VCS) would be a much better way to share and iterate on datasets like ImageNet. This is an example of a dataset that hasn't been updated since it's initial release. Backing the dataset with a VCS would allow people to collaborate on the dataset without duplicating data all over the place.
In order to do this effectively, the VCS needs to be fast to make the developer experience worth using. Not an easy task, but one we were willing to plow through at Oxen.ai π
## π The Raw Numbers
To create this benchmark, we took the 1 million+ images from ImageNet and added them to Oxen, DVC, Git-LFS, and S3. The total time is to get the files from A (local filesystem) to B (remote storage) successfully. The steps to reproduce and the machine specs are in the sections below.
Here are the results in ranked order from fastest to slowest.
| Tool | Time | Can view data? |
| ---------------- | ------------------- | -------------------- |
| **π Oxen.ai** | 1 hour and 30 mins | β Yes |
| **Tarball + S3** | 2 hours 21 mins | β No |
| **aws s3 cp** | 2 hours 48 mins | β No |
| **DVC + Local** | 3 hours | β No |
| **DVC + S3** | 4 hours and 51 mins | β Yes w/ Other Tools |
| **Git-LFS** | 20 hours | β No |
Notice that **Oxen is faster than even the laziest of methods**, creating a tarball and uploading it to S3, but with the benefits of being able to view, query, and compare versions of the data. If you would like us to add any other tools to the benchmark, please let us know!
## βοΈ Hardware and Network
All of the benchmarks were executed on a `t3.2xlarge` EC2 instance with `4 vCPUs` and `16.0 GB of RAM` and a `1TB EBS` volume attached. We found that the size of the EBS volume did impact the IOPs for adding and committing data for all tools. All of the network transfer was within us-west-1 within AWS to S3.
## π View the Data
One of the other advantages of using Oxen.ai, besides raw speed, is that you can view, query and collaborate on the data as soon as you've pushed it to the [web hub](https://oxen.ai). Feel free to explore the end result [here](https://oxen.ai/datasets/ImageNet-1k) in Oxen.ai.
## π§ Why not Git?
Everybody knows and loves Git. But we also know that it isn't exactly suited to version data. Trying to add multi-gigabyte datasets can quickly blowup storage costs and cause serious slowdown. And that isn't really Git's purpose, either - GitHub, for instance, doesn't even accept files larger than 100 megabytes.
Over the years, however, several attempts have been made to extend Git to gigabyte or even terabyte scale. In 2015 Git-LFS support was added to GitHub, which speeds up pulls by downloading files lazily, replacing tracked files with pointers and retrieving their content upon checkout. Data Version Control (DVC) came out in 2017, employing a similar concept but storing the file contents externally to Git.
In theory it sounds great to tie your VCS to the most popular version control system in the world in git. But in practice, it is a bit like trying to fill a swimming pool with a straw. You can do it, but you are tied to the limitations of the git protocols.
## π How does Oxen.ai work?
With Oxen.ai, we take a different approach. Rather than trying to extend Git, we built Oxen, taking inspiration from Git where we can. We didn't want to make you learn a completely new tool. If you know how to use git, you know how to use Oxen. But we also designed Oxen specifically to make versioning large amounts of data as fast as possible. Under the hood, Oxen uses Merkle trees, smart network protocols and fast hashing algorithms to reduce the amount of data our repositories store.
Unbound by Git, however, we're also able to employ several other optimizations that make Oxen fast such as block-level deduplication, compression, iterating on subtrees, and more. Some of these optimizations are still under development, but we're excited to share what we have so far, and you can find a deeper dive and list of the upcoming features [here](https://oxen-ai.github.io/).
All of the code is open source and available on [GitHub](https://github.com/oxen-ai/Oxen). We appreciate any feedback you have and welcome any stars and contributions!
***
# π Running the Experiments
To give you a sense of the process as well as point out the advantages & challenges associated with each method, we ran the following experiments below, listed from slowest to fastest.
## Git + LFS (\~20 hours)
Git-LFS is a popular first tool to try since it is already in the Git ecosystem. The problem is that it is painfully slow when it comes to adding, committing, and pushing non-text files. It can also be a bit annoying to remember which files are tracked under LFS vs just regular Git. Many times have I accidentally committed a multi-GB file to git and wondered why my push was taking so long. Removing files from the git merkle tree is a whole other pain.
Steps to reproduce:
```
git init
git lfs install
git lfs track "*.jpg"
git add .gitattributes
git add images # 61 minutes
git commit -m "adding images" # 11 minutes
git push origin main # 19 hours
```
Total time: `20+ hours`
Adding and committing data locally is not terribly slow (still slower than Oxen). But it does have to hash and copy every file into the hidden `.git` directory. The combination of using a slow hashing algorithm and copying large files makes git-lfs slower than it has to be on `add` and `commit`.
The real killer here though is the push π₯±. Pushing data to the remote takes over 20 hours in the case of ImageNet, even on the same network as our other tests.
## DVC + S3 Backend (\~5 hours)
DVC is a popular tool, tightly integrated with the Git ecosystem and can be configured for multiple storage backends. You'll see that you have to toggle back and forth between DVC and git with 11 commands to remember and execute. It is easy to make a mistake and track the wrong things in your git repo as well as simply wrap your head around the fact that you are using two different tools to version your data.
Steps to reproduce:
```
git init
dvc init
git status
git commit -m "Initialize DVC"
dvc add images/ # Executed in 132.12 mins
git add images.dvc .gitignore
git commit -m "adding images"
git remote add origin https://github.com/owner/repository.git
dvc remote add --default datastore s3://my-bucket
git push origin main
dvc push # Executed in 159.55 mins
```
Total Time: `4 hours and 51 mins`
As you can see, DVC is not as slow as Git-LFS, but it is significantly more commands to remember and execute.
## DVC + Local Storage Backend (\~3 hours)
We wanted to do another test with DVC without any network transfer, purely to test the protocol overhead. Transferring to S3 may not be the best apples to apples comparison, since Oxen also compresses and deduplicates data on the network transfer.
```
git init
dvc init
git status
git commit -m "Initialize DVC"
dvc add images/ # Executed in 132.12 mins
git add images.dvc .gitignore
git commit -m "adding images"
dvc remote add -d myremote /home/ubuntu/dvc-remote
git push origin main
dvc push # Executed in 49.53 mins
```
Total Time: `3 hours`
As we'll see below, Oxen is faster than DVC even if you drop the overhead of network transfer.
## Tarball + S3 (\~2 hours 21 mins)
I like to call this one, "F' it, let's just create a tarball and upload it to S3". Easy to remember, easy to use, but not very efficient nor effective when it comes to iterating on data.
```
time tar czf imagenet-images.tar.gz images/ # Executed in 114.66 mins
time aws s3 cp imagenet-images.tar.gz s3://imagenet-tarball # Executed in 27.13 mins
```
Total Time: `2 hours 21 mins`
This may work well for cold storage of data you may rarely want to view again. But for anything else, Oxen is a much better tool.
Oxen smartly compresses and creates smaller data chunks behind the scenes while transferring your data across the network, taking advantage of the network bandwidth and reducing the amount of time it takes to upload and download data.
## aws s3 cp (\~2 hours 48 mins)
You may be asking yourself, well if the tarball takes so long to create, why not just use the `aws s3 cp` command with the `--recursive` flag?
```
time aws s3 cp --recursive images s3://imagenet-files # Executed in 168.28 mins
```
Total Time: `2 hours 48 mins`
This is a bit slower overall than the tarball method, and you still have the same problems of iterating on and viewing the data. By looking at the logs, it looks like the s3 sdk is syncing the files one by one, which accounts for the slowness.
## Oxen.ai (\~1 hour and 30 mins)
With Oxen, if you know how to use git, there are no extra commands to remember. With the same commands as plain old git you can initialize, add, commit, and push your data to the remote.
Steps to reproduce:
```
oxen init
oxen add images # Executed in 41.35 mins
oxen commit -m "adding images" # Executed in 50.75 secs
oxen config --set-remote origin https://hub.oxen.ai/datasets/ImageNet-1k
oxen push # Executed in 49.11 mins
```
Total Time π₯: `1 hour and 30 mins`
If you are curious how Oxen works under the hood, we are working on a detailed technical writeup that dives into the Merkle tree, block-level deduplication, and more [here](https://oxen-ai.github.io/).
# Try Oxen.ai for Yourself
If you would like to try Oxen.ai for yourself, you can sign up for a free account [here](https://oxen.ai/). All of the code is open source and available on [GitHub](https://github.com/oxen-ai/Oxen). Let us know what you think by joining our [Discord](https://discord.com/invite/s3tBEn7Ptg).
# πΎ Version Control
Source: https://docs.oxen.ai/examples/data/versioning
Oxen.ai is built on top of a blazing fast data version control system that allows you to version, branch, and share datasets, model weights, and experiments with your team.
Oxen's [open source data version control system](https://github.com/Oxen-AI/Oxen) shines at workflows and data sizes where git or git-lfs fall short. The interface is inspired by git, so that it is easy to learn for engineers, but has a few core differences. Oxen is built from the ground up to handle large datasets with many files or large csvs, parquet files, or other large binary blobs like model weights, videos or 3D assets.
The developer tools come with a [CLI](/examples/data/versioning#versioning-101), [HTTP APIs](/http-api), and [Python library](/python-api) to make it easy to integrate into your workflow.
## Versioning 101
On the surface, `oxen` looks a lot like `git`. Users can add, commit, data locally then push to a remote server. Similar to git, by default oxen will create a local copy of the data on your machine in your `.oxen` directory before pushing to the remote server.
```bash CLI theme={null}
oxen init
oxen add lotsa_data/
oxen commit -m "adding too much data for git"
# Create the remote on hub.oxen.ai (or `oxen create-remote --name /`)
# and wire it up before pushing:
oxen config --set-remote origin https://hub.oxen.ai//
oxen push origin main
```
```python Python theme={null}
from oxen import Repo
repo = Repo(".")
repo.init()
repo.add("lotsa_data/")
repo.commit("adding too much data for git")
repo.set_remote("origin", "https://hub.oxen.ai//")
repo.push()
```
The first main difference is that `oxen` comes with a remote `oxen-server` that user's can sync data to. This server also allows you to upload data directly without making local copies.
```bash CLI theme={null}
SYNC_DIR=/path/to/data oxen-server start -p 3000 -i 0.0.0.0
```
Say we had already pushed a large dataset to the remote server, and simply wanted to to add a file to a large dataset like ImageNet with [1 Million Files](/examples/data/performance). You do not want to wait to clone all the files locally just to add yours to the server.
```python Python theme={null}
from oxen import RemoteRepo
# Connect to the remote client
repo = RemoteRepo("my-username/my-repo")
# Add the images to the workspace without committing.
# Pass `dst=` so the files land under `images/` on the remote.
repo.add("images/image_1_000_001.png", dst="images/")
repo.add("images/image_1_000_002.png", dst="images/")
# Commit the remote changes
repo.commit("Adding the 1,000,001st image to the dataset")
```
```bash CLI theme={null}
# Assuming you already are in a local repository with a remote configured
# (run `oxen config --set-remote origin ` if you haven't).
oxen workspace create --name add-image --branch main
# Stage multiple files into the workspace before committing
oxen workspace add images/image_1_000_001.png --workspace-name add-image
oxen workspace add images/image_1_000_002.png --workspace-name add-image
# Commit the remote changes
oxen workspace commit \
-m "Adding the 1,000,001st image to the dataset" \
--workspace-name add-image \
--branch main
```
This is just one example of how Oxen.ai enables a more developer friendly workflow for large datasets. There are also optimizations under the hood such as parallel file transfer, scalable merkle trees, and data deduplication to make Oxen go brrr (or mooo?).
## Interfaces
The server exposes a REST API that can be used to interact with data. Oxen.ai's clients include a [command line interface](/getting-started/command-line/start_repository), as well as bindings for [Rust](https://github.com/Oxen-AI/Oxen) π¦, [Python](/python-api) π, and [HTTP interfaces](/http-api) π to make it easy to integrate into your workflow.
## Installation
Oxen makes versioning your datasets as easy as versioning your code. You can install through homebrew or pip or from our [releases page](https://github.com/Oxen-AI/Oxen/releases).
```bash CLI theme={null}
brew install oxen
```
```bash Python theme={null}
pip install oxenai
```
## Remote Workflow
Centralized version control systems like Oxen.ai allow you to have remote first workflows where you do not need to have a fully copy of the data on your local machine. Decentralized version control systems like git by default duplicate all the data to every node in your network.
While the decentralized nature of git makes it easy to maintain full copies of the history across many machines, this is not practical for large datasets. Oxen was designed from the ground up to be able to seamlessly switch between local and remote (centralized) workflows. Only clone what you need, and contribute back to the remote repository when you are done.
### Create a Remote Repository
If you do not already have a remote repository, you can create one with a single `README.md` and initial commit so it is immediately cloneable.
```python Python theme={null}
from oxen import RemoteRepo
# RemoteRepo.create is an instance method β construct first, then call create.
# The Python client adds a README.md and initial commit by default.
repo = RemoteRepo("my-user/my-repo-name")
repo.create()
```
```bash CLI theme={null}
# The CLI defaults to an empty repo, so pass --add_readme to include a
# README.md and initial commit (the equivalent of the Python default).
oxen create-remote --name my-user/my-repo-name --add_readme
```
```bash cURL theme={null}
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://hub.oxen.ai/api/repos \
-d '{
"namespace": "my-user",
"name": "my-repo-name",
"description": "A repository for image classification",
"is_public": true
}'
```
If you want to create an empty repository β with no `README.md` and no initial commit β pass `empty=True` from Python, or simply omit `--add_readme` from the CLI.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("my-user/my-repo-name")
repo.create(empty=True)
```
```bash CLI theme={null}
# The CLI default is an empty repo (no README, no commits).
oxen create-remote --name my-user/my-repo-name
```
```bash cURL theme={null}
# The HTTP API creates a bare empty repository by default β there is no
# `empty` flag because no README is ever added server-side.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://hub.oxen.ai/api/repos \
-d '{
"namespace": "my-user",
"name": "my-repo-name"
}'
```
The reason you may want to start with an empty repository is if you already started a local repository and want to push it to the remote repository. This local repository already has a commit history. When pushing to a remote, commit histories must match. Hence we need to start with an empty remote repository without any commits if we want to push a local repository with a commit history.
### Add Files
You can add files to the remote repository by passing the path to the file and the destination directory. This will upload the file to the remote repository and stage it for commit.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
repo.add("images/000000002754.jpg", dst="images/")
```
```bash CLI theme={null}
# Stage a file into a workspace before committing
oxen workspace add images/000000002754.jpg --workspace-name add-image
```
```bash cURL theme={null}
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/file/:branch/:dst_dir
# Uploads files via multipart form AND commits in one call.
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-F "files[]=@images/000000002754.jpg" \
-F "name=Bessie Oxington" \
-F "email=bessie@oxen.ai" \
-F "message=Adding the 1,000,001st image to the dataset" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/file/main/images
```
### Commit Changes
You can commit changes to the remote repository by passing a message.
```python Python theme={null}
repo.commit("Adding the 1,000,001st image to the dataset")
```
```bash CLI theme={null}
oxen workspace commit \
-m "Adding the 1,000,001st image to the dataset" \
--workspace-name add-image
```
```bash cURL theme={null}
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/workspaces/:workspace_id/merge/:branch
# Commits the staged files in the workspace and merges them into the branch.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/merge/main \
-d '{
"author": "Bessie Oxington",
"email": "bessie@oxen.ai",
"message": "Adding the 1,000,001st image to the dataset"
}'
```
### File Exploration
To see the files in the remote repository you can use `ls`.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
print(repo.ls())
```
```bash cURL theme={null}
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/dir/:revision/:path
# :revision can be a branch name or commit hash. Pass an empty path for the repo root.
curl -X GET -H "Authorization: Bearer $TOKEN" \
"https://hub.oxen.ai/api/repos/ox/CatDogBBox/dir/main/"
```
To view a specific directory you can pass the directory name to the `ls` method.
Note: the directories are paginated so you will need to use the `page_num` parameter to view the next page of results.
There are also `total_pages`, `page_number`, and `total_entries` attributes that give you information about the pagination.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
images_results = repo.ls("images", page_num=1, page_size=10)
print(images_results)
print(images_results.total_pages)
print(images_results.page_number)
print(images_results.total_entries)
```
```bash cURL theme={null}
# Pass `page` and `page_size` as query params for pagination.
curl -X GET -H "Authorization: Bearer $TOKEN" \
"https://hub.oxen.ai/api/repos/ox/CatDogBBox/dir/main/images?page=1&page_size=10"
```
### Downloading Data
You can download individual files and folders if you do not need the entire data repository for your job.
```bash CLI theme={null}
oxen download ox/CatDogBBox annotations/test.csv
```
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
repo.download("annotations/test.csv")
```
```bash cURL theme={null}
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/file/:revision/:path
# :revision can be a branch name or commit hash
curl -X GET -H "Authorization: Bearer $TOKEN" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/file/main/annotations/test.csv \
-o ~/Downloads/test.csv
```
### Checkout a Branch
If you have a data on a separate branch that you want to view you can checkout a branch by passing the branch name to the `checkout` method.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
repo.checkout("my-branch-name")
print(repo.ls())
```
```bash CLI theme={null}
oxen checkout my-branch-name
```
```bash cURL theme={null}
# There is no HTTP "checkout" β branches are referenced by name in the URL of
# subsequent API calls. To verify a branch exists and read its current commit:
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/branches/:branch_name
curl -X GET -H "Authorization: Bearer $TOKEN" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/branches/my-branch-name
```
### Create a New Branch
The `checkout` method also allows you to create a new branch if the branch does not exist.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
repo.checkout("my-new-branch-name", create=True)
print(repo.ls())
```
```bash CLI theme={null}
oxen checkout -b my-new-branch-name
```
```bash cURL theme={null}
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/branches
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/branches \
-d '{
"from_name": "main",
"new_name": "my-new-branch-name"
}'
```
### View Branches
To see all the branches in the remote repository you can use the `branches` method.
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
print(repo.branches())
```
```bash CLI theme={null}
# List both local and remote branches from inside a local clone.
# To list only remote branches, use: `oxen branch -r origin`
oxen branch -a
```
```bash cURL theme={null}
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/branches
curl -X GET -H "Authorization: Bearer $TOKEN" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/branches
```
### Workspaces
Under the hood, the way that we enable remote collaboration is through a concept called a [workspace](/examples/data/workspaces). A workspace can be thought of as an uncommitted working directory that is stored on the server. Just like you can `add` files before committing locally, you can `add` files to a workspace on the remote server before committing. This allows you to build up a set of changes remotely before committing them in bulk.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
# The second positional arg to Workspace is the BRANCH the workspace is tied
# to. The optional `workspace_name` gives the workspace a stable identifier
# so you can reattach to it later by name.
workspace = Workspace(repo, "main", workspace_name="add-images")
workspace.add("/path/to/image.png")
status = workspace.status()
print(status.added_files())
# Commits land on the workspace's branch β "main" in this example.
workspace.commit("Adding the 1,000,001st image to the dataset")
```
```bash CLI theme={null}
# Run from inside a local clone of the repo.
# Workspaces can be addressed by name (-n) or by their server-assigned id (-w).
oxen workspace create -n add-images --branch main
oxen workspace add image.png -n add-images
oxen workspace status -n add-images
oxen workspace commit -n add-images -m "Adding the 1,000,001st image to the dataset" --branch main
```
```bash cURL theme={null}
# 1. Get or create a workspace from a base branch
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/get_or_create \
-d '{
"branch_name": "main",
"name": "add-images"
}'
# 2. Upload and stage a file into the workspace at a destination path
# URL Format: /api/repos/:namespace/:repo_name/workspaces/:workspace_id/files/:dst_path
curl -X POST -H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/image.png" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/files/images
# 3. Commit the workspace and merge it into the target branch
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/merge/main \
-d '{
"author": "Bessie Oxington",
"email": "bessie@oxen.ai",
"message": "Adding the 1,000,001st image to the dataset"
}'
```
The `RemoteRepo.add` method is a shortcut for creating a workspace and adding files to it. It creates a ephemeral workspace and adds the files to it, and deletes the workspace after committing.
To learn more about workspaces, check out the [workspaces documentation](/examples/data/workspaces).
### Clone a Remote Repository
Remote repositories are identified by a remote URL. This is the URL that you can use to clone the repository.
```python Python theme={null}
from oxen import RemoteRepo
remote_repo = RemoteRepo("my-user/my-repo-name")
remote_repo.create(empty=True)
# `url` is a property, not a method β no parentheses.
print(remote_repo.url)
```
```bash CLI theme={null}
# The remote URL follows the format below. View it with:
oxen config --get-remote
```
```bash cURL theme={null}
# Verify the remote repo exists and fetch its metadata.
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name
curl -X GET -H "Authorization: Bearer $TOKEN" \
https://hub.oxen.ai/api/repos/my-user/my-repo-name
```
You can use this URL to clone the repository.
```python Python theme={null}
# Local Repository
from oxen import Repo
from oxen import RemoteRepo
remote_repo = RemoteRepo("my-user/my-repo-name")
remote_repo.create(empty=True)
repo_url = remote_repo.url
local_repo = Repo("/path/to/local/repo")
local_repo.clone(repo_url)
```
Or you can set the remote of an existing local repository to point at the remote repository.
```python Python theme={null}
from oxen import Repo
from oxen import RemoteRepo
remote_repo = RemoteRepo("my-user/my-repo-name")
remote_repo.create(empty=True)
local_repo = Repo("/path/to/local/repo")
local_repo.set_remote("origin", remote_repo.url)
```
## Local Workflow
Local workflow looks a lot like git. The downside is that you have to duplicate all the data locally. The good news is that oxen is much faster than git for large files and repositories.
### Initialize User
Each change you make will be associated with a name and email. Set them before you get started so you know who changed what. The user data is saved by default in `~/.config/oxen/user_config.toml`.
```bash CLI theme={null}
oxen config --name "Bessie Oxington" --email "bessie@yourcomany.com"
```
```python Python theme={null}
from oxen.user import config_user
config_user("Bessie Oxington", "bessie@oxen.ai")
```
### Create Repository
Initialize your first Oxen repository, and commit the first version of your data.
```bash CLI theme={null}
# Initialize the repository
oxen init
# Write data to a file
printf '%s\n' 'name,age' 'bob,12' 'jane,13' > people.csv
# Stage the data for commit
oxen add people.csv
# Commit the changes with a message
oxen commit -m "Adding my data"
```
```python Python theme={null}
import os
from oxen import Repo
# Instantiate a Repo object and create the repo directory
repo = Repo("/path/to/data", mkdir=True)
# Initialize the repository
repo.init()
# Write data to a file
data_path = os.path.join(repo.path, "people.csv")
with open(data_path, "w") as f:
f.write("name,age\nbob,12\njane,13")
# Stage the data for commit
repo.add(data_path)
# Commit the changes with a message
repo.commit("Adding my data")
```
### Create Branch
It is good practice to create a new branch for changes you make to your data. This will allow you to easily compare the parallel versions of your data over time.
```bash CLI theme={null}
# Checkout a branch named `modify-data`
oxen checkout -b modify-data
# Overwrite data in existing file
printf '%s\n' 'name,age' 'bob,12' 'jane,13' 'joe,14' > people.csv
```
```python Python theme={null}
import os
from oxen import Repo
repo = Repo("/path/to/data")
# Create a new branch called `modify-data`
repo.checkout("modify-data", create=True)
# Overwrite data in existing file
data_path = os.path.join(repo.path, "people.csv")
with open(data_path, "w") as f:
f.write("name,age\nbob,12\njane,13\njoe,14")
```
### Delete Branch
Once finished with a branch, you can delete it.
```bash CLI theme={null}
# Checkout main branch locally
oxen checkout main
# Delete 'other_branch' locally
oxen branch -d new_branch # may need -D if branch is not merged into main
# Delete branch in remote repo
oxen push origin --delete new_branch
```
```python Python theme={null}
import os
from oxen import Repo
# Instantiate a Repo object
repo = Repo("/path/to/data")
# Checkout the main branch
repo.checkout("main")
# Delete new_branch. If it has commits not merged into main, oxen will
# refuse the delete β fully merge first, or use the CLI's -D for a force-delete.
repo.branch('new_branch', delete=True)
# Delete remote branch
repo.push('origin', 'new_branch', delete=True)
```
```bash cURL theme={null}
# Deletes the branch on the remote repository.
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/branches/:branch_name
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/branches/new_branch
```
### Status
Check the current state of your local repository by using `oxen status`. Instead of printing out every file that was added/modified/removed (which is unsustainable for large repositories), `oxen` summarizes the changes and lets you page through them.
```bash CLI theme={null}
oxen status
```
```python Python theme={null}
from oxen import Repo
repo = Repo("/path/to/data")
print(repo.status())
```
### Restore Changes
If you are not happy with the changes you made to your data, you can restore them to the previous commit with the `oxen restore` command.
```bash CLI theme={null}
oxen restore --source people.csv
```
### Commit Changes
Once you are happy with the changes you have made to your data, you can commit them to the repository with a new message.
```bash CLI theme={null}
oxen add people.csv
oxen commit -m "Adding Joe to the dataset"
```
```python Python theme={null}
from oxen import Repo
repo = Repo("/path/to/data")
# Stage the data for commit
data_path = os.path.join(repo.path, "people.csv")
repo.add(data_path)
# Commit the changes with a message
repo.commit("Adding Joe to the dataset")
```
### View Commit History
To see the commit history of your repository, you can use the `oxen log` command.
```bash CLI theme={null}
oxen log
```
```python Python theme={null}
from oxen import Repo
# Instantiate a Repo object
repo = Repo("/path/to/data")
# Get the commit history
commits = repo.log()
```
```bash cURL theme={null}
# View the commit history of a remote repo at a given revision.
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/commits/history/:revision
curl -X GET -H "Authorization: Bearer $TOKEN" \
"https://hub.oxen.ai/api/repos/ox/CatDogBBox/commits/history/main?page=1&page_size=25"
```
### Checkout Main Branch
Once you are done making changes to your data, you can return to the main branch with the `oxen checkout` command.
Never fear, the file now has now been reverted to the inital commit again, but your changes will be saved in the branch you created.
```bash CLI theme={null}
oxen checkout main
```
```python Python theme={null}
from oxen import Repo
# Instantiate a Repo object
repo = Repo("/path/to/data")
# Checkout the main branch
repo.checkout("main")
```
### List Branches
To see the branches in your repository, you can use the `oxen branch` command.
```bash CLI theme={null}
oxen branch
```
```python Python theme={null}
from oxen import Repo
# Instantiate a Repo object
repo = Repo("/path/to/data")
# Get the branches
print(repo.branches())
```
```bash cURL theme={null}
# List branches in the remote repository.
# URL Format: https://hub.oxen.ai/api/repos/:namespace/:repo_name/branches
curl -X GET -H "Authorization: Bearer $TOKEN" \
https://hub.oxen.ai/api/repos/ox/CatDogBBox/branches
```
### Push Data
Once your data has been committed locally, you can sync it to the `oxen-server`.
Oxen.ai has a web hub that allows you to collaborate on your data in the cloud. You can create a free account at [https://oxen.ai](https://oxen.ai).
```bash CLI theme={null}
# Go create repo at https://oxen.ai
# ...
oxen config --set-remote origin https://hub.oxen.ai//
oxen config --auth hub.oxen.ai
oxen push origin main
# to push your other branch simply change the branch name from `main` to `modify-data`
```
```python Python theme={null}
# Go create repo at https://oxen.ai
# ...
# Set where to push the data to (replace and with your remote)
repo.set_remote("origin", "https://hub.oxen.ai//")
# Set your auth token (defaults to hub.oxen.ai host)
oxen.auth.config_auth("YOUR_AUTH_TOKEN")
# Push the changes to the remote
repo.push()
```
To learn more about setting up authentication and authorization, read our [security documentation here](/getting-started/auth).
### Clone Data
Clone your data faster than ever before. Oxen has been optimized to the core to make pulling large datasets as fast as possible.
```bash CLI theme={null}
oxen clone https://hub.oxen.ai/ox/CatDogBBox
```
```python Python theme={null}
from oxen import Repo
# Construct a Repo at the local destination, then clone into it.
repo = Repo("/path/to/dst")
repo.clone("https://hub.oxen.ai/ox/CatDogBBox")
```
### Pull Changes
Only pull the changes you need. Oxen will only pull the files that have changed since the last time you pulled.
```bash CLI theme={null}
oxen pull origin main
```
```python Python theme={null}
from oxen import Repo
repo = Repo("/path/to/repo")
repo.pull()
```
# π¦ Workspaces
Source: https://docs.oxen.ai/examples/data/workspaces
A workspace is a server-side working tree: stage files, edit data frames, and hold draft state on the remote without committing.
A workspace is your repository's working tree, hosted on the server. Just like the uncommitted state of a local repo, you can `add`, `rm`, and modify files in a workspace, and read them back, before anything enters commit history. When you're ready, commit the workspace and the whole set of changes lands as one commit. You never need to download the dataset locally.
Because that working tree lives on the server instead of on the client's machine, it can do things a local working directory can't:
* **Shared:** Multiple processes, users, or agents can write to the same named workspace and see each other's changes before anyone commits.
* **Durable:** Staged data lives on the server and sticks around until you commit it or delete the workspace. If your laptop, app server, or CI job restarts, the staged work is still there.
* **Live:** Staged files can be read back through the API right away, and staged tabular files are indexed into DuckDB so you can [query and edit them like a database](#editing-tabular-files-like-a-database).
The tradeoff: staged changes are not version history. They have no log of their own, and deleting the workspace discards them. Committing is what makes them permanent.
## Quick start
### Add to an existing repo without cloning it
Imagine a repository with 1 million images. Instead of cloning the data, init an empty local repo, point it at the remote, and stage files into a workspace.
Don't confuse the workspace name with a branch: `add-images` is just a label for uncommitted remote state on top of `main`. Committing it lands those staged changes on `main` as one commit.
```bash CLI theme={null}
# Init only if you have not already setup the repo locally
oxen init
# Point your local repo to the remote
oxen config --set-remote origin https://hub.oxen.ai/ox/ImageNet-1k
# Create a named workspace pinned to the latest commit on main
oxen workspace create --name add-images --branch main
# Stage a single file into the images/ directory of the workspace
oxen workspace add /path/to/my_images/image.jpg --directory images/ --workspace-name add-images
# See what's staged
oxen workspace status --workspace-name add-images
# Commit the staged changes to main
oxen workspace commit -m "Add new image to images/ directory" -n add-images -b main
```
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/ImageNet-1k") # Host defaults to 'hub.oxen.ai'
# Second arg is the branch the workspace is pinned to.
# workspace_name is the stable label for this staging area.
workspace = Workspace(repo, "main", workspace_name="add-images")
workspace.add("new_images/")
status = workspace.status()
print(status.added_files())
workspace.commit("Add new images to dataset") # Defaults to the workspace's branch (main)
```
```bash cURL theme={null}
export TOKEN=
REPO=https://hub.oxen.ai/api/repos/ox/ImageNet-1k
# Use a stable id your client persists. Workspace paths accept either the id or the name.
export WORKSPACE_ID=add-images
# Create (or fetch) a named workspace on main
curl -X PUT "$REPO/workspaces/get_or_create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch_name": "main", "workspace_id": "'"$WORKSPACE_ID"'", "name": "add-images"}'
# Stage a single file into the images/ directory of the workspace
curl -X POST "$REPO/workspaces/$WORKSPACE_ID/files/images" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/my_images/image.jpg"
# See what's staged
curl "$REPO/workspaces/$WORKSPACE_ID/changes" -H "Authorization: Bearer $TOKEN"
# Commit the staged changes to main
curl -X POST "$REPO/workspaces/$WORKSPACE_ID/merge/main" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Add new image to images/ directory", "author": "Bessie", "email": "bessie@oxen.ai"}'
```
### Bulk-import data into a fresh repo
`oxen workspace add` streams files straight to the remote. It never copies them into a local `.oxen` store the way `add β commit β push` does, so you avoid that extra disk and time cost on imports.
```bash CLI theme={null}
oxen init
oxen config --create-remote --host hub.oxen.ai --scheme https --name ox/ImageNet-1k
oxen workspace create # Returns a workspace ID
oxen workspace add images/ --workspace-id [WORKSPACE_ID]
oxen workspace commit -m "Import 1 million images" -w [WORKSPACE_ID]
```
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/ImageNet-1k")
repo.create() # Create the remote repo first
workspace = Workspace(repo, "main")
workspace.add("images/")
status = workspace.status()
print(status.added_files())
workspace.commit("Import 1 million images")
```
```bash cURL theme={null}
export TOKEN=
REPO=https://hub.oxen.ai/api/repos/ox/ImageNet-1k
# One-shot import: a fresh id is fine because this workspace is unnamed and deleted on commit
export WORKSPACE_ID="$(uuidgen)"
# Create a workspace to import the data
curl -X PUT "$REPO/workspaces/get_or_create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch_name": "main", "workspace_id": "'"$WORKSPACE_ID"'"}'
# Stage each file into the images/ directory of the workspace
curl -X POST "$REPO/workspaces/$WORKSPACE_ID/files/images" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@images/dog_1.jpg"
# Commit the staged changes
curl -X POST "$REPO/workspaces/$WORKSPACE_ID/merge/main" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Import 1 million images", "author": "Bessie", "email": "bessie@oxen.ai"}'
```
The [Driving workspaces over HTTP](#driving-workspaces-over-http) section below walks through the HTTP flow step by step. This is how you build a custom client like a labeling UI, ingestion daemon, or agent without shipping the Oxen CLI or Python SDK.
## How it works
Every workspace is pinned to a base commit (usually the tip of a branch at create time). When you add, remove, or modify files, Oxen records a diff against that commit and stores it on the server.
Staged data is durable but unversioned. It lives on the server, survives restarts of both your client and the server, and persists until the workspace is committed or deleted. It does not appear in the repository's commit history until you commit, so there is no log of staged edits, and deleting the workspace discards them without a trace.
When you commit a workspace:
1. Oxen applies your staged diff on top of the workspace's base commit to produce a new commit.
2. That new commit is added to a target branch on the remote (see [Committing changes](#committing-changes) for how the target is chosen).
3. If the target branch has advanced past the workspace's base commit, Oxen attempts to merge. Conflicts cause the commit to fail and you'll need to resolve them before retrying.
Because workspaces are commit-scoped, two workspaces created from the same branch at different times can see completely different views of the repo. This isolation is intentional, but it also means a long-lived workspace can drift from the branch tip and accumulate conflicts.
## Creating a workspace
A workspace is created against a remote repository and a branch. The second argument to the Python `Workspace` constructor is always the **branch**, not the workspace name.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "main", workspace_name="add-images")
```
```bash CLI theme={null}
oxen workspace create -n add-images -b main
```
```bash cURL theme={null}
curl -X PUT "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/get_or_create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch_name": "main", "workspace_id": "add-images", "name": "add-images"}'
```
Python always requires the branch argument. The CLI defaults to your current local branch when you omit `--branch`. Over HTTP, `branch_name` is always required.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "main") # Unnamed workspace on main
```
```bash CLI theme={null}
oxen workspace create # Uses your current local branch; returns a workspace ID
```
The workspace is pinned to whatever commit the branch points at when you create it. For non-empty repositories, that branch must already exist on the remote.
### Named vs. unnamed workspaces
Every workspace has an **id**. You can optionally also give it a human-readable **name**. The CLI generates a UUID id on create; over HTTP you supply the id yourself.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "main", workspace_name="add-images")
```
```bash CLI theme={null}
oxen workspace create -n add-images
```
```bash cURL theme={null}
# name is optional, omit it for an unnamed workspace
curl -X PUT "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/get_or_create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch_name": "main", "workspace_id": "add-images", "name": "add-images"}'
```
The name matters because of two behavioral differences:
| | Unnamed workspace | Named workspace |
| --------------------- | ----------------------------------- | --------------------------------------------------------------------------- |
| Lifetime after commit | Deleted | Persists, fast-forwarded to the new commit |
| Best for | One-shot imports, throwaway staging | Long-lived staging, app draft state, multi-commit or multi-client workflows |
Use a **named** workspace when you expect to make multiple commits from the same workspace, when several processes or users will share it, or when an application needs to find its staging area again after a restart (list the workspaces and match on name, or use the [get\_or\_create endpoint](#driving-workspaces-over-http)). Use an **unnamed** workspace for one-off imports where you don't need it to stick around.
### Identifying a workspace in CLI commands
Most workspace commands need to know which workspace you're targeting. You can reference a workspace by either its id or its name:
* `--workspace-id ` (short `-w`): the auto-generated id returned from `oxen workspace create`.
* `--workspace-name ` (short `-n`): the name you set with `--name` at create time.
## Listing workspaces
List the workspaces on a remote with `oxen workspace list`.
```bash CLI theme={null}
oxen workspace list -r my_remote # Defaults to `origin` if no remote is provided
```
```bash cURL theme={null}
curl "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces" \
-H "Authorization: Bearer $TOKEN"
```
## Adding files
`oxen workspace add` streams a file's contents directly to the server and stages it on the workspace.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "main", workspace_name="add-images")
workspace.add("/path/to/image.png")
status = workspace.status()
print(status.added_files())
```
```bash CLI theme={null}
oxen workspace add image.png -n add-images
oxen workspace status -n add-images
```
```bash cURL theme={null}
# Stage image.png into the images/ directory of the workspace
curl -X POST "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/files/images" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@image.png"
# See what's staged
curl "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/changes" \
-H "Authorization: Bearer $TOKEN"
```
### Unstaging a file
To remove a file you've staged on the workspace (without touching the base repo), unstage it with `oxen workspace rm --staged`.
```bash CLI theme={null}
oxen workspace rm --staged image.jpg -n add-images
```
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "main", workspace_name="add-images")
workspace.unstage("image.jpg") # Requires oxen > 0.53.0
```
```bash cURL theme={null}
curl -X DELETE "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/changes/image.jpg" \
-H "Authorization: Bearer $TOKEN"
```
### Deleting a file from the base repo
`oxen workspace rm` **without** `--staged` stages a deletion of a file that exists in the base repo. When you commit the workspace, that file will be removed from the branch. Use `--staged` if you only want to unstage a previously added file.
```bash CLI theme={null}
oxen workspace rm image.jpg -n add-images
```
```bash cURL theme={null}
curl -X DELETE "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/$WORKSPACE_ID/files" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '["image.jpg"]'
```
The Python SDK does not expose staging a deletion yet. Despite the name, `Workspace.rm()` unstages a staged file, the same as `unstage()`. Use the CLI or the HTTP endpoint above to stage a removal from the base repo.
## Editing tabular files like a database
Staging isn't limited to whole files. When you open a tabular file (`csv`, `tsv`, `parquet`, `jsonl`, etc.) through the `DataFrame` class, Oxen indexes it into DuckDB inside a workspace. This gives you a queryable, editable database in an uncommitted state. You can insert, update, and delete individual rows and query with SQL, and nothing touches the branch until you commit.
`DataFrame("namespace/repo", "path")` creates a workspace under the hood (or reuses one if you pass a `Workspace` / `workspace_name`). You do not need to construct a `Workspace` yourself for the common case.
```python theme={null}
from oxen import DataFrame
# Connect to and index the data frame in a workspace
# Note: this must be an existing file committed to the repo;
# indexing may take a while for large files
data_frame = DataFrame("datasets/SpamOrHam", "data.tsv")
# Add a row (returns a stable row id)
row_id = data_frame.insert_row({"category": "spam", "message": "Hello, do I have an offer for you!"})
# Read it back
row = data_frame.get_row_by_id(row_id)
# Update and delete by row id
data_frame.update_row(row_id, {"category": "ham"})
data_frame.delete_row(row_id)
# Query the staged state with SQL
results = data_frame.query(sql="SELECT category, COUNT(*) FROM df GROUP BY category")
# Commit the edits, or call data_frame.restore() to discard them
data_frame.commit("Clean up spam labels")
```
This is the machinery behind editing datasets in the Oxen.ai UI and behind [building custom labeling tools](/features/labeling_data). Every cell edit, row insert, and row delete is a staged change in a workspace, batched up until someone commits. It also powers [embeddings search](/features/embeddings), which uses the same DuckDB index to query vector columns without committing.
Since workspaces are shared, an agent can insert rows all day while a human reviews the staged changes, and the dataset only gets a new commit when the batch is approved.
See the [DataFrame Python API](/python-api/data_frame) for the full interface.
## Committing changes
Commit a workspace to land its staged changes as a new commit on the remote.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "main", workspace_name="add-images")
# Optional second arg is the target BRANCH to commit onto
workspace.commit("adding an image using a workspace", "main")
```
```bash CLI theme={null}
oxen workspace commit -m "adding an image" -n add-images -b main
```
```bash cURL theme={null}
curl -X POST "https://hub.oxen.ai/api/repos/ox/CatDogBBox/workspaces/add-images/merge/main" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "adding an image", "author": "Bessie", "email": "bessie@oxen.ai"}'
```
If you don't provide a target branch, each interface picks a default. Note that the target branch must already exist on the remote, the server will never create one for you:
* **Python** commits to the branch the workspace was created from.
* **CLI** commits to your current local branch (and errors if you have no current branch).
* **HTTP** has no default, the target branch is always part of the URL.
```python Python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
repo = RemoteRepo("ox/CatDogBBox")
workspace = Workspace(repo, "my-branch", workspace_name="add-images")
workspace.commit("adding an image using a workspace") # Defaults to my-branch
```
```bash CLI theme={null}
oxen workspace commit -m "adding an image" -n add-images # Commits to your current local branch
```
After a successful commit:
* An **unnamed** workspace is deleted.
* A **named** workspace is fast-forwarded to point at the new commit, so you can keep using it.
### Merge conflicts
The target branch advancing past the workspace's base commit is not a problem by itself. As long as the new commits on the branch touched different files, the workspace merges cleanly. A commit only fails with a "workspace is behind" error when a file you staged also changed on the target branch after the workspace was created.
There is no rebase command for a workspace. To recover from a conflict:
1. Create a fresh workspace, which will be pinned to the current tip of the branch.
2. Re-stage your changes there. For conflicted files, fetch the branch's current version first and re-apply your edits on top of it.
3. Commit the new workspace, and delete the stale one.
## Driving workspaces over HTTP
Everything above is a thin wrapper over the [Repository API](/http-api/index), which means a workspace can be the persistence layer of any application without installing the Oxen CLI or Python SDK. This section walks through the full lifecycle the way an app like a draft editor, labeling backend, or ingestion daemon would use it.
All requests are authenticated with your API key:
```bash theme={null}
export TOKEN=
export REPO=https://hub.oxen.ai/api/repos/ox/CatDogBBox
```
### 1. Get or create a named workspace
`get_or_create` returns an existing workspace when the id already exists, or when a workspace with the given `name` already exists; otherwise it creates one. Persist a stable `workspace_id` in your app (do not mint a fresh UUID on every boot), and pass a `name` so you can also find the workspace by listing.
Workspace paths accept either the id or the name, so you can use the same stable string for both in simple apps:
```bash theme={null}
export WORKSPACE_ID=draft-editor
curl -X PUT "$REPO/workspaces/get_or_create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch_name": "main", "workspace_id": "'"$WORKSPACE_ID"'", "name": "draft-editor"}'
```
To inspect what is already on the remote, list workspaces:
```bash theme={null}
curl "$REPO/workspaces" -H "Authorization: Bearer $TOKEN"
```
### 2. Stage a file
`POST` the file as multipart form data to a directory path inside the workspace. Staging on every save is cheap. Each write simply replaces the staged version of the file.
```bash theme={null}
curl -X POST "$REPO/workspaces/$WORKSPACE_ID/files/images" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@image.jpg"
```
### 3. Read staged content back
`GET` the same path to read the staged version back before anything is committed. If the file isn't staged in the workspace, the request returns a 404 and you can fall back to the committed version on the branch:
```bash theme={null}
# The staged (workspace) version, i.e. the draft
curl "$REPO/workspaces/$WORKSPACE_ID/files/images/image.jpg" \
-H "Authorization: Bearer $TOKEN"
# The committed version on the branch, used as the fallback
curl "$REPO/file/main/images/image.jpg" \
-H "Authorization: Bearer $TOKEN"
```
### 4. List what's staged
The `changes` endpoint returns the workspace's staged additions, modifications, and removals. An app can rebuild its view of what is in draft from this endpoint after a restart.
```bash theme={null}
curl "$REPO/workspaces/$WORKSPACE_ID/changes" \
-H "Authorization: Bearer $TOKEN"
```
To unstage a single file without touching the base repo:
```bash theme={null}
curl -X DELETE "$REPO/workspaces/$WORKSPACE_ID/changes/images/image.jpg" \
-H "Authorization: Bearer $TOKEN"
```
To unstage several paths at once, `DELETE` the `changes` collection with a JSON body:
```bash theme={null}
curl -X DELETE "$REPO/workspaces/$WORKSPACE_ID/changes" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '["images/image.jpg", "data/config.json"]'
```
### 5. Commit the workspace to a branch
When the user hits "publish" (or the batch is approved), merge the workspace into the target branch. All staged changes land as one commit.
```bash theme={null}
# Optional: check mergeability first
curl "$REPO/workspaces/$WORKSPACE_ID/merge/main" -H "Authorization: Bearer $TOKEN"
# Commit
curl -X POST "$REPO/workspaces/$WORKSPACE_ID/merge/main" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Publish edits", "author": "Bessie", "email": "bessie@oxen.ai"}'
```
If a file you staged also changed on the branch, the commit fails with a "workspace is behind" conflict error. See [Merge conflicts](#merge-conflicts) for how to recover. After a successful commit, a named workspace fast-forwards to the new commit and you can start staging again from step 2. The same workspace can serve as the app's staging area indefinitely.
### 6. Clean up
Deleting a workspace permanently discards anything still staged in it:
```bash theme={null}
curl -X DELETE "$REPO/workspaces/$WORKSPACE_ID" -H "Authorization: Bearer $TOKEN"
```
The full endpoint reference, including batch file upload and the workspace data frame endpoints, lives in the [Repository API docs](/http-api/index).
## Example use cases
Workspaces are useful whenever committing on every write would be too noisy, too slow, or premature. The classic cases: editing a repository that's too large to clone, bulk-importing data without paying the disk cost of a local `.oxen` store, batching dozens of changes into one atomic commit, or letting several processes and users build up a staged batch together. If your repo is small enough to clone and the normal `add β commit β push` flow works for you, you don't need a workspace. See the [Version Control guide](/examples/data/versioning) instead.
Here are a few things you could build, to get your wheels turning:
* **A document editor.** Autosaves stage each edit to a named workspace, and reads fall back from the workspace to the committed branch, so drafts live on the server instead of in a database. Hitting "Publish" commits the workspace to `main` as one commit.
* **A data ingestion pipeline.** Workers append raw training examples into a shared workspace all day, then a reviewer fixes bad rows and commits one clean dataset version. This is how [Oxen.ai's labeling tools](/features/labeling_data) work under the hood.
* **An AI agent's scratchpad.** An agent writes files and edits data frames in a workspace while a human reviews the staged changes. Good runs become auditable commits, bad runs get deleted without touching history.
* **A review queue for community datasets.** Contributors upload images or rows to a shared workspace, like a pull request for data. A maintainer reviews the staged batch and commits it, so `main` only ever contains approved data.
* **An edge or sensor data buffer.** Devices push readings into a workspace all day. Committing hourly or daily gives you clean versioned snapshots instead of thousands of tiny commits.
* **A model evaluation harness.** Each eval run writes predictions and metrics into a workspace. Commit only the runs worth keeping, then diff commits to compare models over time.
* **A moderated media app.** User uploads land in a workspace where moderators can view them through the API. Approval is a commit, rejection is an unstage.
# π€ Chat Completions
Source: https://docs.oxen.ai/examples/fine-tuning/chat_completions
How to fine-tune an LLM on a conversation of messages.
This tutorial will show you how to fine-tune an LLM on a history of messages. If you have traces from a chat application or have curated a dataset, you can use them to fine-tune a model to be more accurate, faster, or tailored to your use-case. Often a small, specialized model can outperform a larger, more general model for a specific use-case when it comes to latency, accuracy, and cost.
## Upload Your Dataset
For this example, we are teaching the model to answer questions based on context that is supplied in the system prompt. You can follow along with the [Tutorials/CoQA](https://www.oxen.ai/Tutorials/CoQA/file/main/train_coqa.jsonl) dataset containing over 7,000 rows of chat messages. Each row of the dataset contains a conversation between a user and an assistant.
This particular dataset focuses on multi-turn conversations. Each example starts by grounding some context taken from a news article or a Wikipedia article. The user then asks a question about the context, and the assistant answers the question. There are multiple back and forth exchanges between the user and the assistant, each question building on the previous one. Here's a few examples from the paper to give you an idea of the task.
Notice that you would not be able to answer the second question without the first question being answered first.
## Dataset Format
Oxen.ai supports datasets in a variety of file formats, including jsonl, csv, and parquet. The only requirement is that you have a column where each row is a list of messages. Each message is an dictionary with a `role` and `content` key. The `role` can be "system", "user", or "assistant". The `content` is the message content.
```json theme={null}
{
"conversations": [
{
"messages": [
{"role": "system", "content": "You are a helpful assistant that answers questions based on the provided context."},
{"role": "user", "content": "What is the easiest way to fine-tune a model?"},
{"role": "assistant", "content": "Oxen.ai allows you to fine-tune a model with a few clicks. Just upload your dataset, select your base model, and click 'Fine-tune'."},
{"role": "user", "content": "What modalities does Oxen.ai support?"},
{"role": "assistant", "content": "Oxen.ai supports text, image, and video generation."}
]
}
]
}
```
## Fine-Tuning The Model
Once you have uploaded your dataset, click the "Actions" button and select "Fine-tune a model".
Next select your base model, the messages source, whether you'd like to use LoRA or not. We recommend starting with a smaller model like [Qwen3-0.6B](https://www.oxen.ai/ai/models/qwen-qwen3-0-6b) for faster iteration, or a larger model like [Llama 3.1 8B](https://www.oxen.ai/ai/models/meta-llama-3-1-8b-instruct) for better performance on complex conversations.
For our Advance Options, you can have control over hyper-parameters and model specifications like learning rate, batch size, and number of epochs. These settings can help you optimize for your specific use case, whether you prioritize training speed, model accuracy, or computational efficiency.
## Monitoring the Fine-Tune
While we're fine-tuning your model, you'll be able to see the configuration, logs, and metrics of the fine-tuning. This helps you track the model's progress and identify if you need to adjust any hyperparameters or stop training early if the model has converged.
## Deploying the Model
Once your fine-tuning is complete, go to the info page and click "Deploy". Oxen.ai will spin up a dedicated endpoint for your model to access via a chat interface or through the API. After the model is deployed, you can click the "Chat with this model" button to open a chat interface where you can test multi-turn conversations.
This will bring up a chat interface where you can test your model with back-and-forth conversations to see how it maintains context across multiple turns.
## Model API
You can integrate it into your application using the API. The API is OpenAI compatible, so you can use any OpenAI client library to interact with it. The base URL for the API is `https://hub.oxen.ai/api`.
For chat completions, you'll send a list of messages that includes the conversation history. Each message should have a `role` (either "user", "assistant", or "system") and `content`.
```bash theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model-id",
"messages": [
{"role": "system", "content": "You are a helpful assistant that answers questions based on the provided context."},
{"role": "user", "content": "What is the capital of France?"},
{"role": "assistant", "content": "The capital of France is Paris."},
{"role": "user", "content": "What is its population?"}
]
}'
```
Make sure to replace `your-model-id` with the ID of your fine-tuned model. The model will use the entire conversation history to generate contextually appropriate responses.
## Next Steps
Feel free to join our [Discord](https://discord.com/invite/s3tBEn7Ptg) and ask us or the community any questions you have, we have a community of developers and machine learning experts who are happy to help you out.
# π¨βπ¨ Image Editing
Source: https://docs.oxen.ai/examples/fine-tuning/image_editing
How to fine-tune an image editing model in Oxen.ai
Oxen.ai allows you to get higher quality image edits with consistent brand assets, characters, products, or your own style with no infrastructure setup required. Fine-tune your models with a few clicks, track results during training, and own all your weights to download and use anywhere.
## The Task
For this example, we are going to fine-tune Qwen-Image-Edit to be able to turn a photo of a Yeti Mug from a product catalogue into a photo of the mug being used in the wild. The input images will be the mug we want in the scene on a black background (left) and the output will be the mug in a beautiful scene (right).
The prompt that generated the image on the right was *"a red headed woman sipping from the mug outdoors"*. Notice we did not have to say "yeti mug", but instead we teach the model that "mug" in our context should always be the mug from the reference image.
## Creating a Repository
Oxen.ai repositories are used to store and [version](/examples/data/versioning) your data and models. We can create a new repository by clicking the "Create New Repository" button in your Oxen.ai dashboard.
You will upload your dataset to the repository, and when the fine-tune is complete, the model weights will be saved to the repository on a branch. By versioning your data and models together, you can always track the data that was used to train the model.
## Uploading the Dataset
From your repository, you can click the "Add File" button to upload your dataset. The upload supports unpacking zip files if you want to upload a directory of images.
For image editing models, you will need three things:
1. Input images
2. Reference images
3. Prompts for the edits or changes we want to make
Start by collecting the input images, reference images, and uploading them to the repository. You will also need a csv file that will contain the prompts for the edits or changes we want to make.
## Formatting the Dataset
When you upload tabular data files (like CSV, JSONL, or Parquet) to Oxen.ai, they get superpowers. For example, you can enable image rendering on your image columns to show as thumbnails in the dataset. To view the images, click the "βοΈ" edit button above the dataset, then edit the column to enable image rendering. The video below shows the whole process.
This lets you view images, reference images, and prompts all in one place.
In this case we have one column called `image` that represents the output we want, and a column called `control_image` that represents the reference image that we want to feed as input. There is a third column for the `prompt` that describes the edits or changes we want to make.
The image column needs to contain the **relative path** to the image from the root of the repository. For example, if the image is in the `images` folder, the path should be `images/image_0.png`.
## Kicking off the Fine-Tune
The other superpower your csv file gets is that you can kick off a fine-tune from the dataset page. Click the "Actions" button and select "Fine-Tune a Model".
This will take you to the fine-tune page where you can select the model you want to fine-tune. Select the "Qwen-Image-Edit" model, and make sure the "Control Image" column is set to `control_image` column, the "Image" column is set to `image` column, and the "Prompt" column is set to `prompt` column.
You can also upload some test images and prompts that will be used as samples during the fine-tune.
## Advanced Parameters
Click the "Advanced Parameters" button to see the advanced parameters for the fine-tune. You can set the learning rate, batch size, number of steps, and other parameters here.
One of the best parts of fine-tuning with Oxen.ai is that we track all your experiments for you, so that you can always refer back to the parameters that worked best for future fine-tunes.
## Monitoring the Fine-Tune
While the model is training, you can monitor the progress by clicking the "Samples" tab. This will show you the images that the model has generated so far.
## Deploying the Model
When the model has finished training, you can deploy it to a new model by clicking the "Deploy Model" button.
Once the model is deployed, you can use it in the playground or via the API.
```bash theme={null}
curl -X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "oxen:ox-busy-scarlet-beaver",
"input_image": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"prompt": "Add a funny hat to the ox",
"num_inference_steps": 20
}' https://hub.oxen.ai/api/ai/images/edit
```
## Using the Playground
Click the "Open Playground" button to use the model in the playground. This allows you to prompt the model with different images and prompts to see how it performs.
The playground will save a history of your prompts and images so that you can refer back to them later.
## Exporting the Model
All of the model weights are stored back in your repository when the fine-tune is complete. Navigate to the fine-tune info tab, and you will see a link to the model weights. This is helpful if you want to download the weights to run in ComfyUI or your own infrastructure.
This will take you to the file viewer where you can download the model safetensors.
You can also automatically download the weights with the [oxen cli](/getting-started/command-line/start_repository) or [python library](/python-api).
```bash CLI theme={null}
oxen download user-name/repo-name path/to/model.safetensors --revision COMMIT_OR_BRANCH
```
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("user-name/repo-name")
repo.download("path/to/model.safetensors", revision="COMMIT_OR_BRANCH")
```
## Need Help Fine-Tuning?
If you need help fine-tuning your model, contact us at [hello@oxen.ai](mailto:hello@oxen.ai) and we are happy to help you get started.
# πΌοΈ Image Generation
Source: https://docs.oxen.ai/examples/fine-tuning/image_generation
How to fine-tune an image generation model in Oxen.ai
Oxen.ai allows you to fine-tune an image generation model to create higher quality images with consistent brand assets, characters, products, or your own style with no infrastructure setup required. Fine-tune your models with a few clicks, track results during training, and own all your weights to download and use anywhere.
## The Task
For this example, we are going to fine-tune [Qwen-Image](https://www.oxen.ai/ai/models/qwen-image) to be able to generate images of models wearing a specific outfit.
## Dataset Format
When fine-tuning image generation models, you need a dataset that contains the images and descriptions of the images. The expected format is a csv, jsonl, or parquet file with a column that contains the *relative path* to the image in the repository, and a column that contains the description of the image.
We'll walk through the process of creating a dataset with the images and captions in the following sections.
## Creating a Repository
Oxen.ai repositories are used to store and version your data and models. We can create a new repository by clicking the "Create New Repository" button in your Oxen.ai dashboard.
You will upload your dataset to the repository, and when the fine-tune is complete, the model weights will be saved to a branch in the repository. By versioning your data and models together, you can always track the data that was used to train the model.
## Uploading the Images
From your repository, you can click the "Add File" button to upload your dataset of images. If you upload a zip file, it will automatically be unpacked into the repository in the specified directory.
Note: When uploading your images, organize your files in a folder by specifying a "Target Directory" when uploading the files.
## Turn Your Images into a Dataset
In order to fine-tune in Oxen.ai, you need a csv, jsonl, or parquet file that contains the images and their captions.
With your images uploaded, you can convert the directory of images into a dataset that can be used for the fine-tune. Navigate to the directory of images and click the "Folder to Dataset" button.
This will grab all of the relative paths from the folder and create a parquet file with a column called `file_path` that contains the relative path to the image.
In order to view the images, you will need to enable image rendering on the `file_path` column. Click the "βοΈ" edit button above the dataset, then edit the column to enable image rendering. The video below shows the whole process.
## Captioning The Images
In order for the fine-tune to learn a mapping from text to images, we'll need a prompt column that contains a description of each image. Oxen.ai makes it easy to run models on each row of the dataset in order to automatically generate captions.
Click the "Actions" button above the dataset, then select "Run Inference".
You will need to select a model that is able to go from "image" to "text" from the dropdown on the left.
Now write a prompt that describes what you want in the caption and any formatting you want to apply.
```text theme={null}
Describe the model and the clothing that the model is wearing as if you are writing a prompt for an image generation model. The prompt should be one sentence in length and include the gender as "male" or "female". Also describe their hair color and skin tone (white, tan, black, latina, asian). Only describe the person and outfit.
The prompt should start with something like "A male Nike model wearing" or "A female Nike model wearing"
{file_path}
```
Note: You must supply the curly braces `{}` around the `file_path` column in the prompt to know what column to use for the image.
When you feel good about your prompt after looking at your samples click the "Next ->" button to decide where you want to save the results. By default, the results will create a new version of the existing file.
Now sit back and relax as the model captions your images π βοΈ.
## Kicking off the Fine-Tune
With our images captioned, now we can kick off the fine-tune! Click the "Actions" button and select "Fine-Tune a Model".
This will take you to the fine-tune page where you can select the model you want to fine-tune. Select the "Qwen-Image" model, and make sure the "Image" column is set to the `file_path` column, and the "Prompt" column is set to the `caption` column.
To monitor the fine-tune, you can provide a few sample prompts that will be used to generate images during the fine-tune.
## Advanced Parameters
Click the "Advanced Parameters" button to see the advanced parameters for the fine-tune. You can set the learning rate, batch size, number of steps, and other parameters here.
One of the best parts of fine-tuning with Oxen.ai is that we track all your experiments for you, so that you can always refer back to the parameters that worked best for future fine-tunes.
Click the "Start Fine-Tune" button and you are off to the races!
## Monitoring the Fine-Tune
As the model is training, you can monitor the progress by clicking the "Samples" tab. This will show you the images that the model has generated so far.
## Deploying the Model
When the model has finished training, you can deploy it to a new model by clicking the "Deploy Model" button. The deployment will take a few minutes to complete.
Once the model is deployed, you can use it in the playground or via the API.
```bash theme={null}
curl -X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "oxen:ox-brave-jade-gecko",
"prompt": "A majestic ox standing in a field at sunset",
"num_inference_steps": 28
}' https://hub.oxen.ai/api/ai/images/generate
```
## Using the Playground
Click the "Open Playground" button to use the model in the playground. This allows you to prompt the model with different images and prompts to see how it performs.
The playground will save a history of your prompts and images so that you can refer back to them later.
## Exporting the Model
All of the model weights are stored back in your repository when the fine-tune is complete. Navigate to the fine-tune info tab, and you will see a link to the model weights. This is helpful if you want to download the weights to run in ComfyUI or your own infrastructure.
This will take you to the file viewer where you can download the model `.safetensors` file.
You can also automatically download the weights with the [oxen cli](/getting-started/command-line/start_repository) or [python library](/python-api).
```bash CLI theme={null}
oxen download user-name/repo-name path/to/model.safetensors --revision COMMIT_OR_BRANCH
```
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("user-name/repo-name")
repo.download("path/to/model.safetensors", revision="COMMIT_OR_BRANCH")
```
## Need Help Fine-Tuning?
If you need help fine-tuning your model, contact us at [hello@oxen.ai](mailto:hello@oxen.ai) and we are happy to help you get started.
# ποΈ Vision Language Models
Source: https://docs.oxen.ai/examples/fine-tuning/image_understanding
How to fine-tune a Vision Language Model in Oxen.ai
Oxen.ai allows you to fine-tune a Vision Language Model (VLM) to understand images and videos. Fine-tuned VLMs are great way to process data at scale with high throughput, low latency, and high accuracy in your domain. When you can't describe your task in a text prompt, you can fine-tune a VLM to understand it.
## Preparing the dataset
When fine-tuning a VLM, you need a dataset that contains the images, user prompts, and responses that are expected from the VLM. The dataset format can be a csv, jsonl, or parquet file with a column that contains the *relative path* to the image in the repository. To see an example of the dataset format, check out the [Tutorials/Geometry3K](https://www.oxen.ai/Tutorials/Geometry3K/file/main/train.parquet) dataset.
Each row in this dataset should have an associated image in the repository stored at `images/train/image_{n}.png`.
To upload the dataset you can use the [oxen command line interface](/getting-started/command-line/start_repository). Here's an example of creating a repository from the command line and uploading data:
```bash theme={null}
# Navigate to the directory containing your dataset
cd path/to/data
# Set your username and repository name
export USERNAME=YOUR_USERNAME
export REPO_NAME=YOUR_REPO_NAME
# Create a new repository on the remote server
oxen create-remote --name $USERNAME/$REPO_NAME
# Set the remote origin to the new repository
oxen config --set-remote origin https://hub.oxen.ai/$USERNAME/$REPO_NAME
# Add the dataset to the repository
oxen add .
# Push the dataset to the remote server
oxen push
```
## Rendering Images
In order to view the images, you will need to enable image rendering on your images column. Click the ββοΈβ edit button above the dataset, then edit the column to enable image rendering. The video below shows the whole process.
## Fine-tuning a model
With your images labeled and you are happy with the quality and quantity, it is time to kick off your first fine-tune.
Click the "Actions" button and select "Fine-Tune a Model".
This will take you to the fine-tune page where you can select the model you want to fine-tune. Select the `Image to Text` task, and select the `Qwen/Qwen3-VL-2B-Instruct` model. Make sure the "Image" column is set to the proper `image` column, and the "Prompt" and "Response" columns are set to the inputs and outputs you expect.
All you have to do now is click "Start Fine-Tune", sit back, grab a coffee, and watch the model learn.
## Deploying the Model
Once the model is trained, you can deploy it to the cloud and start using it in your applications. Click the "Deploy" button and we will spin up a dedicated GPU instance for you.
Once the model is deployed, you can chat with it in the UI or via the API. Replace the `model` name with the name of your deployed model.
```bash theme={null}
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "oxen:ox-comfortable-sapphire-locust",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is in this image?"
},
{
"type": "image_url",
"image_url": {
"url": "https://oxen.ai/assets/images/homepage/hero-ox.png"
}
}
]
}
]
}' https://hub.oxen.ai/api/ai/chat/completions
```
For more ways to call the API, check out the [inference](/examples/inference/vision_language_models) examples.
## Downloading the Weights
One of the benefits of using Oxen.ai is we give you the flexibility of deploying to our cloud or managing your own infrastructure. If you want to download the model weights, you can click the path to the model weights and download them.
```bash CLI theme={null}
oxen download user-name/repo-name path/to/model.safetensors --revision COMMIT_OR_BRANCH
```
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("user-name/repo-name")
repo.download("path/to/model.safetensors", revision="COMMIT_OR_BRANCH")
```
## Need Help Fine-Tuning?
If you need help fine-tuning your model, contact us at [hello@oxen.ai](mailto:hello@oxen.ai) and we are happy to help you get started.
# π¬ Text Generation
Source: https://docs.oxen.ai/examples/fine-tuning/text_generation
How to fine-tune an LLM for text generation.
This tutorial will show you how to fine-tune an LLM for text generation. Text generation is useful for tasks like classification like sentiment analysis where you have a single input and output you want the model to learn. Small language models are great for tasks like this because they are fast and cheap to fine-tune and run.
If your application needs to maintain a history of chat messages\[] as context for the model, you should follow the [Chat Completions](/examples/fine-tuning/chat_completions) tutorial.
## Upload Your Dataset
For this example, we are teaching the model to classify financial sentiment from text. You can follow along with the [Tutorials/FinancialSentiment](https://www.oxen.ai/Tutorials/FinancialSentiment/file/main/train_financial_sentiment.parquet) dataset containing 2000 rows of text and their corresponding sentiment labels. The dataset has one column for the prompt and one for the sentiment label (positive, negative, or neutral).
Oxen supports datasets in a variety of formats, including jsonl, csv, and parquet.
## Fine-Tuning The Model
Once you have uploaded your dataset, click the "Actions" button and select "Fine-tune a model".
Next select your base model, the prompt source, the response source, whether you'd like to use LoRA or not, and if you want advanced control over the fine-tune. For this example, we are using the [Qwen3-0.6B](https://www.oxen.ai/ai/models/qwen-qwen3-0-6b) model, which is small and fast to fine-tune.
For our Advance Options, you can have control over hyper-parameters and model specifications like learning rate, batch size, and number of epochs.
## Monitoring the Fine-Tune
While we're fine-tuning your model, you'll be able to see the configuration, logs, and metrics of the fine-tuning.
## Deploying the Model
Once your fine-tuning is complete, go to the info page and click "Deploy". Oxen.ai will spin up a dedicated endpoint for your model to access via a chat interface or through the API.
After the model is deployed, you can click the "Chat with this model" button to open a chat interface.
This will bring up a chat interface where you can test your model to see how it performs.
## Model API
You can integrate it into your application using the API. The API is OpenAI compatible, so you can use any OpenAI client library to interact with it. The base URL for the API is `https://hub.oxen.ai/api/ai`.
```bash theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model-id",
"messages": [{"role": "user", "content": "The company went bankrupt last week."}]
}'
```
Make sure to replace `your-model-id` with the ID of your fine-tuned model.
## Next Steps
Feel free to join our [Discord](https://discord.com/invite/s3tBEn7Ptg) and ask us or the community any questions you have, we have a community of developers and machine learning experts who are happy to help you out.
# π₯ Video Generation
Source: https://docs.oxen.ai/examples/fine-tuning/video_generation
How to fine-tune a video generation model in Oxen.ai
Oxen.ai allows you to fine-tune a video generation model to generate higher quality videos with consistent brand assets, characters, products, or your own style with no infrastructure setup required. Fine-tune your models with a few clicks, deploy your model to an endpoint, and own all your weights to download and use anywhere.
You can train on either still images or short video clips. Images are enough to capture a *look*, training on video clips lets the model learn **motion**, and with models that support audio (like LTX-2.3 Pro) the fine-tune can even learn how a character **sounds**.
*2 second video of the office manager says, "What's happening, Greg."*
## Example: Generating Videos of a Character
In this example, we are going to fine-tune a video model to generate videos of a specific character. The same steps work for any video model on Oxen. Here we use **LTX-2.3 Pro**. We'll use the manager from *Office Space* (great movie, go watch it if you haven't), and we'll see if we can teach the model his voice, posture and cadence.
At the start of the training run the model doesn't know who the "office manager" is, by the end it nails his face, voice, speaking style and signature lean over the cubicle.
*2 second video of the office manager says, "What's happening, Greg." Left: step 1, no concept of the character. Right: step 2000, spot on.*
## Creating the Training Dataset
Every fine-tune starts with a dataset, the media you want the model to produce (images or video clips), each paired with a caption. During training the model ties the caption's words to what it sees, so your captions pull double duty, they're the training signal now, and the template for how you'll prompt later. The takeaway is to caption each clip the way you'd prompt for it. At inference you can prompt freely in that same style, no need to match a caption word for word.
The expected format is a `csv`, `jsonl`, or `parquet` file with a column that contains the *relative path* to each image or video clip in the repository, and a column that contains its description.
In this example, we have two columns.
1. `file_path` - the relative path to the image or video clip in the Oxen repository
2. `caption` - the description of the media, used as the prompt once the model is deployed
**Why the clips fade to black.** LTX-2.3 always works in fixed five-second chunks (**121 frames**), both when training and when generating. The manager's line, though, is only about two seconds long. If we train on a full five seconds, the model learns to stretch that short line to fill the whole clip, and his quick, deadpan delivery comes out slow and dragged. So after he stops talking we replace the rest of the frames with **solid black** and add `duration two seconds` to the caption. Now the model learns to deliver the line in two seconds and then go black, and you get that same tight pacing when you generate by prompting with the same `duration`. We used Claude Code and FFmpeg to cut and pad the clips.
In order to get started, create a repository, then click the "Add Files" button. You can drag and drop a zip file of your media, which is automatically unzipped into your repository. Write a commit message before uploading so your team knows why you added these files. This is handy when iterating on training datasets.
Once your media has been uploaded, navigate into the folder, click **Manage Files**, and choose **Create Dataset**.
On the "Build a dataset from this folder" screen, Oxen collects every file into a parquet with a `file_path` column. Give it a name (for example `clips_121.parquet`) and click **Build Dataset**.
To preview your media, enable rendering on the `file_path` column. Click the "βοΈ" edit button above the dataset, then edit the column to turn on image or video rendering.
## Auto-Captioning the Dataset
Now that we have a dataset, we need a description for each item. You can caption from the dataset viewer ("Actions" β "Run Inference") or in the fine-tune wizard's **Label Data** step. Select a model that can go from `image -> text` (or `video -> text` for clips), such as **Gemini 3 Flash**, write your prompt, and click **Caption All**.
```text theme={null} theme={null}
You are helping caption videos for a fine-tuning dataset. Describe the video in one sentence or less. Respond with only the caption text, nothing else.
{file_path}
```
You must supply the curly braces around the `{file_path}` column in the prompt so the model knows which column to use for the media.
Keep captions consistent with your goal. For a **character**, prefix every caption with a unique trigger token (for example `office_manager_memo_char`) so the model binds the subject to that token. For a **style**, describe the look rather than naming a character the base model already knows, so it generalizes to new subjects. If you're training on clips, add a `duration two seconds` note so the model learns when to cut to black.
If you want to further refine your prompts, you can always click the "βοΈ" edit button on the dataset viewer and hand label the captions. Every change is version controlled, so you can always roll back to an earlier version of the dataset.
## Kicking off the Fine-Tune
With your media labeled and happy with the quality and quantity, it is time to kick off your first fine-tune. Click "Create Fine-tune" and walk through the steps.
1. **Choose Task**, select **Generate Video**.
2. **Choose Model**, pick your base model. Here we use **LTX-2.3 Pro** ("image-to-video with native audio").
3. **Pick Dataset**, select the dataset you built (or upload a new one).
4. **Label Data**, caption the clips if you haven't already (see above).
5. **Configure Training**, set the **Video Column** to `file_path` and the **Caption Column** to `caption`, add a few sample prompts, and start the run.
Oxen ships good per-model defaults. You can leave them as-is, or adjust based on the size of your dataset.
| Setting | Default | Tweak for a tiny single-subject dataset |
| -------------------- | ------------- | ----------------------------------------------------------------- |
| Steps | 3000 | Stop early, watch the samples and end the run once it locks in |
| LoRA rank / alpha | 32 / 32 | Lower both to keep a small dataset from going to mush |
| LoRA dropout | 0.25 | Turn off, you're honing one subject, not regularizing for variety |
| Learning rate | 0.0002 | Keep |
| Audio learning rate | 0.0001 | Keep, roughly half the video LR since LTX learns audio faster |
| Frames / resolution | 121 @ 960Γ576 | Keep |
| Train / Sample audio | On | Keep on to learn the voice |
In the "Samples" section you can specify a few prompts to test as the model trains. This helps you get a feel for how the model is performing and make sure it is learning what you want. Match the caption format you trained on, including your trigger token and any `duration` note. Click "Create Fine-tune" and Oxen provisions a dedicated GPU. You can launch multiple runs in parallel to sweep hyperparameters.
## Watching the Model Learn
As your model trains, Oxen automatically samples videos from the prompts you specified in the previous step (by default every 200 steps). You can see the model start to learn the subject's face, motion, and (where supported) audio after a couple hundred steps.
*2 second video of the office manager in a cowboy hat and dark aviators says, "You're going to have to fine-tune that on Oxen.ai." An out-of-distribution prompt, the LoRA adds new details without losing the character or overfitting.*
Add an out-of-distribution sample prompt like the one above (details never in the training data). A healthy LoRA still renders those while keeping the subject, which tells you it isn't overfitting. You can download and run any checkpoint locally, or deploy it on Oxen, and stop the run as soon as the samples look good, since you only pay for the GPU time you use while fine-tuning.
## Deploying the Model
When the model has finished training, deploy it by clicking the "Deploy Model" button. Deployment takes a few minutes to complete. Some models offer more than one pipeline, for example a fast distilled pipeline for quick iteration and a higher-quality production pipeline.
Once the model is deployed, you can use it in the playground or via the API. The model page gives you a ready-to-paste curl command with your API key filled in. Replace the `model` name with the name of your deployed model.
```bash theme={null} theme={null}
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ox-objective-cyan-puffin",
"prompt": "An ox walking in a field",
"run_fast": true
}' https://hub.oxen.ai/api/ai/videos/generate
```
## Using the Playground
Click the "Open Playground" button to use the model in the playground. This lets you prompt the model with different images and prompts to see how it performs. If your model supports it, you can pass a **first frame** to ground the generation. Without one, the model generates from what it learned. The playground saves a history of your prompts and generations so you can refer back to them later.
## Exporting the Model
All of the model weights are stored back in your repository when the fine-tune is complete. Navigate to the fine-tune info tab, and you will see a link to the model weights. This is helpful if you want to download the weights to run in ComfyUI or your own infrastructure. From the file viewer you can download the safetensors, or pull them automatically with the [oxen cli](/getting-started/command-line/start_repository) or [python library](/python-api).
```bash CLI theme={null} theme={null}
oxen download user-name/repo-name path/to/model.safetensors --revision COMMIT_OR_BRANCH
```
```python Python theme={null} theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("user-name/repo-name")
repo.download("path/to/model.safetensors", revision="COMMIT_OR_BRANCH")
```
## Need Help Fine-Tuning?
If you need help fine-tuning your model, contact us at [hello@oxen.ai](mailto:hello@oxen.ai) and we are happy to help you get started.
# π¬ Language Models
Source: https://docs.oxen.ai/examples/inference/chat_completions
Integrate an LLM into your application through the `/ai/chat/completions` API.
## Quick Start
The Oxen.ai chat completions API is fully [OpenAI-compatible](https://platform.openai.com/docs/api-reference/chat). You can use the OpenAI SDK, `curl`, or any HTTP client that speaks the OpenAI chat format.
**Base URL:** `https://hub.oxen.ai/api/ai`
**Endpoint:** `POST /ai/chat/completions`
Browse [all available models](https://www.oxen.ai/ai/models).
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "What is a great name for an ox?"}
]
}'
```
```python Python (OpenAI SDK) theme={null}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{"role": "user", "content": "What is a great name for an ox?"}
]
)
print(response.choices[0].message.content)
```
## Authentication
Every request requires a Bearer token in the `Authorization` header. You can find your API key in your [account settings](https://www.oxen.ai/settings/profile).
```bash theme={null}
Authorization: Bearer $OXEN_API_KEY
```
## Response Format
The API returns an OpenAI-compatible JSON response:
```json theme={null}
{
"id": "chatcmpl-af41f027-e4d5-4c4b-ac40-625fb4ebfb1e",
"object": "chat.completion",
"created": 1774040155,
"model": "claude-sonnet-4-6",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "How about \"Beauregard\"?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 11,
"completion_tokens": 4,
"total_tokens": 15
}
}
```
| Field | Description |
| --------------------------- | ------------------------------------------------------------------------------- |
| `id` | Unique identifier for the completion |
| `object` | Always `"chat.completion"` |
| `created` | Unix timestamp of when the completion was created |
| `model` | The model that generated the response |
| `choices` | Array of completion choices (typically one) |
| `choices[].message.content` | The generated text |
| `choices[].finish_reason` | Why generation stopped: `"stop"` (natural end) or `"length"` (hit `max_tokens`) |
| `usage` | Token counts for the request |
## Parameters
| Parameter | Type | Default | Description |
| ------------- | ------- | ------------- | ------------------------------------------------------------------------------------------------- |
| `model` | string | *required* | Model name, e.g. `"claude-sonnet-4-6"`, `"gpt-5-4-2026-03-05"`, `"gemini-3-1-flash-lite-preview"` |
| `messages` | array | *required* | Array of message objects with `role` and `content` |
| `max_tokens` | integer | model default | Maximum number of tokens to generate |
| `temperature` | float | model default | Sampling temperature (0-2). Lower is more deterministic. |
| `stream` | boolean | `false` | Enable [streaming](#streaming) with server-sent events |
### Messages
Each message in the `messages` array has a `role` and `content`:
| Role | Description |
| ----------- | ------------------------------------------------------- |
| `system` | Sets the behavior and context for the model |
| `user` | The user's input |
| `assistant` | Previous model responses (for multi-turn conversations) |
```json theme={null}
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"},
{"role": "assistant", "content": "The capital of France is Paris."},
{"role": "user", "content": "What is its population?"}
]
}
```
## Streaming
Set `"stream": true` to receive responses as server-sent events (SSE). Each event is a `chat.completion.chunk` object with a `delta` instead of a `message`.
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-flash-lite-preview",
"messages": [
{"role": "user", "content": "Write a haiku about data."}
],
"stream": true
}'
```
```python Python (OpenAI SDK) theme={null}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
stream = client.chat.completions.create(
model="gemini-3-1-flash-lite-preview",
messages=[
{"role": "user", "content": "Write a haiku about data."}
],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
```
Each SSE line is prefixed with `data: ` and contains a JSON chunk:
```json theme={null}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1774040190,"model":"gemini-3-1-flash-lite-preview","choices":[{"index":0,"delta":{"content":"hello"},"finish_reason":null}]}
```
The stream ends with:
```
data: [DONE]
```
## Vision
Models that support vision (such as `gemini-3-1-pro-preview` or `claude-sonnet-4-6`) accept images in the `messages` array. For full details and examples including base64 encoding and video understanding, see [Vision Language Models](/examples/inference/vision_language_models).
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-pro-preview",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{"type": "image_url", "image_url": {"url": "https://oxen.ai/assets/images/homepage/hero-ox.png"}}
]
}
]
}'
```
```python Python (OpenAI SDK) theme={null}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
response = client.chat.completions.create(
model="gemini-3-1-pro-preview",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{"type": "image_url", "image_url": {"url": "https://oxen.ai/assets/images/homepage/hero-ox.png"}},
],
}
]
)
print(response.choices[0].message.content)
```
## Documents (PDFs)
Attach a document with a `file` content part. Place the document before the text part for the best results.
Pass the file inline as a base64 data URL in `file.file_data`:
```bash cURL theme={null}
FILE_DATA="data:application/pdf;base64,$(base64 < report.pdf | tr -d '\n')"
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [
{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "report.pdf", "file_data": "'"$FILE_DATA"'"}},
{"type": "text", "text": "Summarize the key findings in this document."}
]
}
]
}'
```
```python Python (OpenAI SDK) theme={null}
import base64
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
with open("report.pdf", "rb") as f:
file_data = "data:application/pdf;base64," + base64.standard_b64encode(f.read()).decode()
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "report.pdf", "file_data": file_data}},
{"type": "text", "text": "Summarize the key findings in this document."},
],
}
],
)
print(response.choices[0].message.content)
```
You can also reference a document by URL (handy for files generated in a
[workspace](/concepts/workspaces)) by passing it in `file.file_url`. Pass
exactly one of `file_data` (a base64 data URL) or `file_url`; the URL
must be publicly accessible (and unexpired, if presigned).
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [
{
"role": "user",
"content": [
{"type": "file", "file": {"file_url": "https://arxiv.org/pdf/1706.03762"}},
{"type": "text", "text": "Summarize the key findings in this document."}
]
}
]
}'
```
```python Python (OpenAI SDK) theme={null}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
pdf_url = "https://arxiv.org/pdf/1706.03762"
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [
{"type": "file", "file": {"file_url": pdf_url}},
{"type": "text", "text": "Summarize the key findings in this document."},
],
}
],
)
print(response.choices[0].message.content)
```
Only `application/pdf` is supported, and a document must be 24 MB or
smaller; other inputs return a `400`. Referencing files by OpenAI `file_id` is
not supported. Inline the file with `file_data` or pass `file_url`.
## Audio
Attach audio with an `audio_url` content part to a model that supports audio
input (such as `gemini-3-1-pro-preview`).
Place the audio part before the text part for the best results.
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-pro-preview",
"messages": [
{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/audio/DoOrDoNot.m4a"}},
{"type": "text", "text": "Transcribe this clip and summarize it in one sentence."}
]
}
]
}'
```
```python Python (OpenAI SDK) theme={null}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
response = client.chat.completions.create(
model="gemini-3-1-pro-preview",
messages=[
{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/audio/DoOrDoNot.m4a"}},
{"type": "text", "text": "Transcribe this clip and summarize it in one sentence."},
],
}
],
)
print(response.choices[0].message.content)
```
To send a local file, base64-encode it into a `data:` URL and pass it as the
`audio_url`:
```python Python (OpenAI SDK) theme={null}
import base64
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
with open("clip.mp3", "rb") as f:
audio_url = "data:audio/mp3;base64," + base64.standard_b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gemini-3-1-pro-preview",
messages=[
{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": audio_url}},
{"type": "text", "text": "What is said in this clip?"},
],
}
],
)
print(response.choices[0].message.content)
```
The URL must be publicly accessible (and unexpired, if presigned). Audio must be
20 MB or smaller; larger files return a `400`. Supported formats vary by
provider: OpenAI audio models (e.g. `gpt-audio`) accept only `wav` and `mp3`,
while Gemini models (e.g. `gemini-3-1-pro-preview`) additionally accept `m4a`,
`aac`, `ogg`, and `flac`.
## Tool use
Tool calling (function calling) follows the same [OpenAI Chat Completions tool format](https://platform.openai.com/docs/guides/function-calling). You send a `tools` array describing each functionβs JSON Schema; the model may reply with `tool_calls` instead of plain text. You execute those functions in your app, then send the results back in new `tool` messages so the model can finish the answer.
| Concept | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tools` | Array of `{ "type": "function", "function": { "name", "description", "parameters" } }` objects. `parameters` is a JSON Schema object for the arguments. |
| `tool_choice` | Optional. `"auto"` (default) lets the model decide; `"none"` disables tools; or force a specific function with `{"type": "function", "function": {"name": "..."}}`. |
| Assistant `tool_calls` | When `finish_reason` is `"tool_calls"`, `choices[0].message.tool_calls` lists each call with `id`, `function.name`, and `function.arguments` (a JSON string). |
| `tool` messages | Each result uses `role: "tool"`, `tool_call_id` matching the callβs `id`, and `content` as a string (often JSON your tool returned). |
### Raw `curl`: first request (tools only)
The model may respond with `tool_calls` instead of user-facing `content`:
```bash theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5-4-2026-03-05",
"messages": [
{"role": "user", "content": "What is the weather in Paris?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
}
]
}'
```
Example assistant payload (abbreviated):
```json theme={null}
{
"choices": [
{
"finish_reason": "tool_calls",
"index": 0,
"message": {
"content": null,
"role": "assistant",
"tool_calls": [
{
"function": {
"arguments": "{\"city\":\"Paris\"}",
"name": "get_weather"
},
"id": "call_GRNwPXnbuQW4Sa3QNB3FYkYw",
"index": 0,
"type": "function"
}
]
}
}
],
"created": 1774809792,
"id": "chatcmpl-1ce4aeac-6c34-468a-ba6b-b96c5372a1dc",
"model": "gpt-5-4-2026-03-05",
"object": "chat.completion",
"usage": {
"completion_tokens": 67,
"prompt_tokens": 572,
"total_tokens": 639
}
}
```
Run your function locally, then call the API again with the full transcript: original messages, the assistant message including `tool_calls`, and one `tool` message per call. Replace IDs and `tool_calls` with values from the first response. Repeat until `finish_reason` is `"stop"` (or `"length"`) and there are no new `tool_calls`.
### Follow-up request: `curl` and OpenAI Python SDK
The follow-up HTTP body matches what the OpenAI SDK builds when you append assistant and `tool` messages in a loop.
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5-4-2026-03-05",
"messages": [
{
"role": "user",
"content": "What is the weather in Paris?"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_01ABC",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Paris\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_01ABC",
"content": "{\"temperature_c\": 18, \"conditions\": \"Partly cloudy\"}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
}
]
}'
```
```python Python (OpenAI SDK) theme={null}
from openai import OpenAI
import json
import os
client = OpenAI(
api_key=os.environ["OXEN_API_KEY"],
base_url="https://hub.oxen.ai/api/ai",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
},
"required": ["city"],
},
},
},
]
messages = [{"role": "user", "content": "What is the weather in Paris?"}]
def get_weather(city: str) -> str:
# Your real implementation would call a weather API.
return json.dumps({"temperature_c": 18, "conditions": "Partly cloudy"})
while True:
response = client.chat.completions.create(
model="gpt-5-4-2026-03-05",
messages=messages,
tools=tools
)
choice = response.choices[0]
msg = choice.message
if not msg.tool_calls:
print(msg.content)
break
messages.append(msg)
for call in msg.tool_calls:
name = call.function.name
args = json.loads(call.function.arguments or "{}")
if name == "get_weather":
output = get_weather(args["city"])
else:
output = json.dumps({"error": f"unknown tool: {name}"})
messages.append(
{
"role": "tool",
"tool_call_id": call.id,
"content": output,
}
)
```
## Errors
The API returns errors as JSON with an `error` object and a standard HTTP status code.
| Status | Meaning |
| ------ | --------------------------------------------------------------- |
| `400` | Bad request (missing model, empty messages, invalid parameters) |
| `401` | Invalid or missing API key |
| `429` | Rate limit exceeded |
| `500` | Internal server error |
```json theme={null}
{
"error": {
"message": "You must specify a model to call"
}
}
```
# π¨ Image Editing
Source: https://docs.oxen.ai/examples/inference/image_editing
How to use the image editing API on Oxen.ai.
### Image Editing
The image editing endpoint allows you to edit images using AI models. Simply provide an input image URL and a prompt describing the edits you want to make. To see the list of models that support image editing, visit the [Models](https://www.oxen.ai/ai/models?modalities=image-to-image) page and filter by "Image to Image".
```bash cURL theme={null}
curl -X POST \
https://hub.oxen.ai/api/ai/images/edit \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "Qwen/Qwen-Image-Edit",
"input_image": "https://example.com/image.png",
"prompt": "Add a funny hat to the ox",
"num_inference_steps": 28
}'
```
```python Python theme={null}
import requests
import os
response = requests.post(
"https://hub.oxen.ai/api/ai/images/edit",
headers={
"Authorization": f"Bearer {os.getenv('OXEN_API_KEY')}",
"Content-Type": "application/json"
},
json={
"model": "Qwen/Qwen-Image-Edit",
"input_image": "https://example.com/image.png",
"prompt": "Add a funny hat to the ox",
"num_inference_steps": 28
}
)
# The response contains the edited image URL
edited_image_url = response.json()["image_url"]
print(f"Edited image: {edited_image_url}")
```
For models that support multiple input images, you can pass an array of image URLs:
```bash cURL (multiple images) theme={null}
curl -X POST \
https://hub.oxen.ai/api/ai/images/edit \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "Qwen/Qwen-Image-Edit",
"input_image": [
"https://example.com/image1.png",
"https://example.com/image2.png"
],
"prompt": "Add a funny hat to the ox",
"num_inference_steps": 28
}'
```
```python Python (multiple images) theme={null}
import requests
import os
response = requests.post(
"https://hub.oxen.ai/api/ai/images/edit",
headers={
"Authorization": f"Bearer {os.getenv('OXEN_API_KEY')}",
"Content-Type": "application/json"
},
json={
"model": "Qwen/Qwen-Image-Edit",
"input_image": [
"https://example.com/image1.png",
"https://example.com/image2.png"
],
"prompt": "Add a funny hat to the ox",
"num_inference_steps": 28
}
)
edited_image_url = response.json()["image_url"]
print(f"Edited image: {edited_image_url}")
```
## Parameters
* **model**: The model identifier to use for image editing (e.g., `Qwen/Qwen-Image-Edit`)
* **input\_image**: URL of the input image(s) you want to edit. Can be a string (single image URL) or an array of strings (multiple image URLs) for models that support multiple images as input
* **prompt**: Text description of the edits you want to make
* **num\_inference\_steps**: Number of inference steps (optional, defaults vary by model)
## Playground Interface
The model playground allows you to quickly test out the boundaries of any model in the UI. This is a great way to experiment with different prompts and see how the model performs before integrating it into your application.
The generated images automatically get saved to a dataset that you can share with your team, download and use to train your own model, or use in your application.
# πΌοΈ Image Generation
Source: https://docs.oxen.ai/examples/inference/image_generation
How to use the image generation API on Oxen.ai.
### Image Generation
The image generation endpoint allows you to generate images from text prompts. Simply provide a prompt describing the image you want to create. To see the list of models that support image generation, visit the [Models](https://www.oxen.ai/ai/models?modalities=text-to-image) page and filter by "Text to Image".
```bash cURL theme={null}
curl -X POST \
https://hub.oxen.ai/api/ai/images/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "Qwen/Qwen-Image",
"prompt": "A majestic ox standing in a field at sunset",
"num_inference_steps": 28
}'
```
```python Python theme={null}
import requests
import os
response = requests.post(
"https://hub.oxen.ai/api/ai/images/generate",
headers={
"Authorization": f"Bearer {os.getenv('OXEN_API_KEY')}",
"Content-Type": "application/json"
},
json={
"model": "Qwen/Qwen-Image",
"prompt": "A majestic ox standing in a field at sunset",
"num_inference_steps": 28
}
)
# The response contains the generated image URL
image_url = response.json()["image_url"]
print(f"Generated image: {image_url}")
```
## Parameters
* **model**: The model identifier to use for image generation (e.g., `Qwen/Qwen-Image`)
* **prompt**: Text description of the image you want to generate
* **num\_inference\_steps**: Number of inference steps (optional, defaults vary by model)
# π₯ Video Generation
Source: https://docs.oxen.ai/examples/inference/video_generation
How to use the video generation API on Oxen.ai.
### Video Generation
The video generation endpoint allows you to generate videos from text prompts. Simply provide a prompt describing the video you want to create. To see the list of models that support video generation, visit the [Models](https://www.oxen.ai/ai/models?modalities=text-to-video) page and filter by "Text to Video".
Video generation can take a few minutes to generate, depending on the model. So we recommend using the `run_fast` parameter to speed up the process. We are working on async video generation, reach out at [support@oxen.ai](mailto:support@oxen.ai) if you want early access.
```bash cURL theme={null}
curl -X POST \
https://hub.oxen.ai/api/ai/videos/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "wan-2.2-t2v-fast",
"prompt": "An ox walking in a field",
"run_fast": true
}'
```
```python Python theme={null}
import requests
import os
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": f"Bearer {os.getenv('OXEN_API_KEY')}",
"Content-Type": "application/json"
},
json={
"model": "wan-2.2-t2v-fast",
"prompt": "An ox walking in a field",
"run_fast": True
}
)
# The response contains the generated video URL
video_url = response.json()["video_url"]
print(f"Generated video: {video_url}")
```
## Parameters
* **model**: The model identifier to use for video generation (e.g., `wan-2.2-t2v-fast`)
* **prompt**: Text description of the video you want to generate
* **run\_fast**: Whether to use fast generation mode (optional, defaults to `true`)
# ποΈ Vision Language Models
Source: https://docs.oxen.ai/examples/inference/vision_language_models
Leverage image understanding with the `/ai/chat/completions` endpoint.
## What are VLMs?
Vision language models extend the ability of language models to understand image and video data. As input they can accept images and videos as well as a text prompt, and as output they can generate text.
For example, instead of training a classifier from scratch, you can pass in your list of categories and a description what to look for in the prompt, and let the VLM take care of the rest.
Here is the [list of supported models](https://www.oxen.ai/ai/models?modalities=image-to-text,video-to-text).
## Image Understanding
The `/ai/chat/completions` endpoint supports vision language models for image understanding. If you want to send an image to a model that supports vision such as Qwen3-VL, Qwen3.5, or Gemini 3 Pro/Flash, you can add a message with the `image_url` type.
### Using Image URLs
```bash cURL (image url) theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-pro-preview",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is in this image?"
},
{
"type": "image_url",
"image_url": {
"url": "https://oxen.ai/assets/images/homepage/hero-ox.png"
}
}
]
}
]
}'
```
### Using Base64 Encoded Images
You can also directly pass in the base64 encoded image.
```bash cURL (base64 encoded image) theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is in this image?"
},
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,YOUR_BASE64_ENCODED_IMAGE_HERE"
}
}
]
}
]
}'
```
### Python Example
From python this would look like:
```python Python theme={null}
import openai
import os
import base64
# Read and encode the image to base64
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# Initialize the client
client = openai.OpenAI(
api_key=os.getenv("OXEN_API_KEY"),
base_url="https://hub.oxen.ai/api/ai"
)
# Encode your image
base64_image = encode_image("path/to/your/image.jpg")
# Send the request with base64 encoded image
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is in this image?"
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
]
}
]
)
print(response.choices[0].message.content)
```
## Video Understanding
The `/ai/chat/completions` endpoint also supports video understanding through vision language models. To send a video to a model that supports video understanding, you can add a message with the `video_url` type.
### Using Video URLs
```bash cURL (video url) theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-flash-preview",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is happening in this video?"
},
{
"type": "video_url",
"video_url": {
"url": "https://example.com/path/to/video.mp4"
}
}
]
}
]
}'
```
### Using Base64 Encoded Videos
You can also directly pass in the base64 encoded video.
```bash cURL (base64 encoded video) theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-flash-preview",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Describe the main events in this video."
},
{
"type": "video_url",
"video_url": {
"url": "data:video/mp4;base64,YOUR_BASE64_ENCODED_VIDEO_HERE"
}
}
]
}
]
}'
```
### Python Example
From python this would look like:
```python Python theme={null}
import openai
import os
import base64
# Read and encode the video to base64
def encode_video(video_path):
with open(video_path, "rb") as video_file:
return base64.b64encode(video_file.read()).decode('utf-8')
# Initialize the client
client = openai.OpenAI(
api_key=os.getenv("OXEN_API_KEY"),
base_url="https://hub.oxen.ai/api/ai"
)
# Encode your video
base64_video = encode_video("path/to/your/video.mp4")
# Send the request with base64 encoded video
response = client.chat.completions.create(
model="gemini-3-flash-preview",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is happening in this video?"
},
{
"type": "video_url",
"video_url": {
"url": f"data:video/mp4;base64,{base64_video}"
}
}
]
}
]
)
print(response.choices[0].message.content)
```
## Playground Interface
Want to test out prompts without writing any code? You can use the [playground interface](https://www.oxen.ai/ai/models/claude-sonnet-4-6) to chat with a model. This is a great way to kick the tires of a base model or a model you [fine-tuned](/getting-started/fine-tuning) after deploying it.
## Fine-Tuning VLMs
Oxen.ai also allows you to fine-tune vision language models on your own data. This is a great way to get a model that is tailored to your specific use case.
Once the model has been fine-tuned, you can easily deploy the model behind an inference endpoint and start the evaluation loop over again.
Learn more about [fine-tuning VLMs](/examples/fine-tuning/image_understanding).
# Activate model deployment
Source: https://docs.oxen.ai/fine-tuning-api/activate-model-deployment
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/models/{id}/activate
Activates an inactive model deployment.
# Cancel generation
Source: https://docs.oxen.ai/fine-tuning-api/cancel-generation
https://hub.oxen.ai/api/_spec/oxen_hub_api.json delete /api/ai/queue/{generation_id}
Cancels a generation. The row is retained with status set to cancelled.
# Create chat completion
Source: https://docs.oxen.ai/fine-tuning-api/create-chat-completion
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/chat/completions
Generates a model response for the given conversation. Compatible with the OpenAI chat completions API.
# Deactivate model deployment
Source: https://docs.oxen.ai/fine-tuning-api/deactivate-model-deployment
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/models/{id}/deactivate
# Delete custom model
Source: https://docs.oxen.ai/fine-tuning-api/delete-custom-model
https://hub.oxen.ai/api/_spec/oxen_hub_api.json delete /api/ai/models/{id}
# Edit image
Source: https://docs.oxen.ai/fine-tuning-api/edit-image
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/images/edit
Edits an image given a prompt and source image.
# Enqueue generation
Source: https://docs.oxen.ai/fine-tuning-api/enqueue-generation
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/queue
Enqueues an async image or video generation job.
# Create an evaluation
Source: https://docs.oxen.ai/fine-tuning-api/evaluations/create-an-evaluation
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/repos/{namespace}/{repo_name}/evaluations/*resource_path
Run model inference over a dataset in the repository. The resource path (branch and file) is encoded in the URL, e.g. `POST /api/repos/{ns}/{repo}/evaluations/main/datasets/training.parquet`.
# Get evaluation status
Source: https://docs.oxen.ai/fine-tuning-api/evaluations/get-evaluation-status
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/repos/{namespace}/{repo_name}/evaluations/{evaluation_id}
Fetch a single evaluation by ID. Use this to poll for progress (`evaluation.progress.processed` / `evaluation.progress.total`) and completion (`evaluation.status`).
# Favorite a model
Source: https://docs.oxen.ai/fine-tuning-api/favorite-a-model
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/models/{id}/favorite
# Create a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/create-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/repos/{namespace}/{repo_name}/fine_tunes
Start a new fine-tune for a given repository.
# Delete a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/delete-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json delete /api/repos/{namespace}/{repo_name}/fine_tunes/{id}
Delete a fine-tune job and any associated model metadata.
# Deploy a checkpoint from a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/deploy-a-checkpoint-from-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/checkpoints/{step}/deploy
Deploy a specific training checkpoint to make it available for inference. Pass `retry=true` to tear down any existing deployment for this checkpoint before creating a new one.
# Get a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/get-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/repos/{namespace}/{repo_name}/fine_tunes/{id}
Fetch a single fine-tune job by ID within a repository.
# Get logs for a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/get-logs-for-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/logs
Fetch aggregated logs for a specific fine-tune run.
# Get training status for a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/get-training-status-for-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/train_status
Retrieve the current training status of a fine-tune job.
# List all fine-tunes accessible to the current user
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/list-all-fine-tunes-accessible-to-the-current-user
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/user/fine_tunes
Return all fine-tune jobs the authenticated user has access to, including their own repositories and repositories in organizations they belong to.
# List checkpoints for a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/list-checkpoints-for-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/checkpoints
Retrieve all saved checkpoints for a fine-tune training run. Returns an empty list if the model branch has not been created yet (e.g. before training has produced checkpoints).
# List fine-tunes for a user
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/list-fine-tunes-for-a-user
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/users/{username}/fine_tunes
Return all fine-tune jobs in repositories belonging to the given user. Only the user themselves can access this endpoint.
# List fine-tunes for an organization
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/list-fine-tunes-for-an-organization
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/orgs/{name}/fine_tunes
Return all fine-tune jobs in repositories belonging to the given organization. Requires the authenticated user to be a member of the organization.
# List fine-tunes in a repository
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/list-fine-tunes-in-a-repository
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/repos/{namespace}/{repo_name}/fine_tunes
Return the fine-tune jobs associated with a given repository.
# Run a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/run-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/actions/run
Kick off a run for an existing fine-tune. The job must be in a runnable state (created or tokenizing).
# Stop a running fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/stop-a-running-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/actions/stop
Stop a fine-tune if it is currently running or queued.
# Tokenize data for a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/tokenize-data-for-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/tokenize
Trigger tokenization for a fine-tune that is in created state.
# Update a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/update-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json patch /api/repos/{namespace}/{repo_name}/fine_tunes/{id}
Partially update a fine-tune job's attributes.
# Update training status for a fine-tune job
Source: https://docs.oxen.ai/fine-tuning-api/fine_tunes/update-training-status-for-a-fine-tune-job
https://hub.oxen.ai/api/_spec/oxen_hub_api.json put /api/repos/{namespace}/{repo_name}/fine_tunes/{id}/train_status/{new_status}
Update the training status of a fine-tune job.
# Generate audio
Source: https://docs.oxen.ai/fine-tuning-api/generate-audio
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/audio/generate
Creates audio (e.g. speech) from a text prompt.
# Generate image
Source: https://docs.oxen.ai/fine-tuning-api/generate-image
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/images/generate
Creates an image from a text prompt.
# Generate video
Source: https://docs.oxen.ai/fine-tuning-api/generate-video
https://hub.oxen.ai/api/_spec/oxen_hub_api.json post /api/ai/videos/generate
Creates a video from a text prompt.
# Get generation details
Source: https://docs.oxen.ai/fine-tuning-api/get-generation-details
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/generations/{generation_id}
Full metadata for a single generation, including cost and the user who triggered it.
# Get generation status
Source: https://docs.oxen.ai/fine-tuning-api/get-generation-status
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/queue/{generation_id}
Retrieves metadata for a single queued generation.
# List favorite models
Source: https://docs.oxen.ai/fine-tuning-api/list-favorite-models
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/models/favorites
# List featured models
Source: https://docs.oxen.ai/fine-tuning-api/list-featured-models
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/models/featured
# List in-flight queue items
Source: https://docs.oxen.ai/fine-tuning-api/list-in-flight-queue-items
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/queue
Lean polling view of the workbench render queue. Returns active generations (status queued or processing) by default; pass an explicit `status=` filter to include terminal rows. For paginated history with cost aggregates, use `/api/ai/generations`.
# List models
Source: https://docs.oxen.ai/fine-tuning-api/list-models
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/models
Lists all available models. OpenAI-compatible.
# List past generations
Source: https://docs.oxen.ai/fine-tuning-api/list-past-generations
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/generations
Paginated browse view of completed and in-flight generations for a namespace. Use `/api/ai/queue` for the lean polling view of in-flight rows.
# Fine-Tuning Overview
Source: https://docs.oxen.ai/fine-tuning-api/overview
Learn how to fine-tune models on Oxen.ai to customize them for your specific use cases
## What is Fine-Tuning?
Fine-tuning allows you to customize pre-trained models with your own data, adapting them to your specific use cases. On Oxen.ai, you can fine-tune models for:
* **Text Generation** - Chatbots, Q\&A systems, content generation
* **Image Generation** - Custom image styles, branded content
* **Image Editing** - Style transfer, image-to-image transformations
* **Video Generation** - Custom video styles and content
* **Vision-Language Tasks** - Image captioning, visual Q\&A
## Getting Started
### Prerequisites
Before fine-tuning, you need:
1. **An Oxen.ai account** - [Sign up](https://oxen.ai/register) if you haven't already
2. **A repository** with your training data
3. **Training data** in a supported format (Parquet, CSV, etc.)
4. **An API key** for authentication
### Authentication
All fine-tuning API requests require authentication using a bearer token:
```bash theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://hub.oxen.ai/api/repos/{namespace}/{repo}/fine_tunes
```
Get your API key from your [account settings](https://oxen.ai/settings/profile).
## Request Structure
All fine-tuning requests follow the same base structure:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "",
"training_params": {
// Operation-specific parameters
}
}
```
### Common Fields
| Field | Description | Required |
| ----------------- | ----------------------------------------------------------------- | -------- |
| `resource` | Path to your training data (e.g., `main/train.parquet`) | Yes |
| `base_model` | The model to fine-tune (e.g., `meta-llama/Llama-3.2-1B-Instruct`) | Yes |
| `script_type` | The type of fine-tuning operation | Yes |
| `training_params` | Operation-specific training parameters | Yes |
### Operation Types (`script_type`)
The `script_type` determines what kind of fine-tuning you're doing:
* `text_generation` - For text-based models (Q\&A, chatbots, completion)
* `text_chat_messages` - For conversational chat models
* `image_generation` - For text-to-image models
* `image_editing` - For image-to-image transformation
* `image_to_text` - For image captioning and VLMs
* `image_to_video` - For image-to-video generation
* `text_to_video` - For text-to-video generation
* `multi_image_editing` - For multi-image editing models
## Common Training Parameters
While each operation type has specific parameters, many share common training configuration:
### LoRA Parameters
Most fine-tuning uses LoRA (Low-Rank Adaptation) for efficient training:
* `use_lora` - Enable LoRA (typically `true`)
* `lora_rank` - Rank of LoRA matrices (default: 16, lower = faster/less memory)
* `lora_alpha` - LoRA scaling factor (default: 16)
### Training Configuration
* `batch_size` - Number of samples per training step (default: 1)
* `learning_rate` - Step size for optimization (typical: 0.0001-0.0002)
* `epochs` or `steps` - Training duration (text models use epochs, image models use steps)
* `gradient_accumulation` or `grad_accum` - Accumulate gradients across multiple steps
### Data Configuration
Each operation type requires specific data columns:
**Text models:**
* `question_column` - Input text column
* `answer_column` - Output/response column
**Image models:**
* `image_column` - Output image column
* `caption_column` - Text prompt column
* `control_image_column` - Input image column (for editing)
## Quick Start Guides
Choose your use case to get started with minimal examples:
Fine-tune chatbots and Q\&A models
Create custom image styles
Fine-tune image transformation models
Generate custom videos
## Detailed API Reference
For complete parameter documentation and advanced configuration:
* [Text Generation Reference](/fine-tuning-api/reference/text_generation)
* [Text Chat Messages Reference](/fine-tuning-api/reference/text_chat_messages)
* [Image Generation Reference](/fine-tuning-api/reference/image_generation)
* [Image Editing Reference](/fine-tuning-api/reference/image_editing)
* [Image to Text Reference](/fine-tuning-api/reference/image_to_text)
* [Image to Video Reference](/fine-tuning-api/reference/image_to_video)
* [Text to Video Reference](/fine-tuning-api/reference/text_to_video)
* [Multi-Image Editing Reference](/fine-tuning-api/reference/multi_image_editing)
## Parameter Guide
Learn about common training parameters and how to tune them:
* [Understanding LoRA](/fine-tuning-api/parameters#lora-low-rank-adaptation)
* [Learning Rate and Optimization](/fine-tuning-api/parameters#learning-rate-and-optimization)
* [Batch Size and Memory Management](/fine-tuning-api/parameters#batch-size-and-memory)
* [Training Duration](/fine-tuning-api/parameters#training-duration)
## Next Steps
1. **Choose your use case** from the Quick Start guides above
2. **Prepare your data** in the required format
3. **Start your first fine-tune** using the API
4. **Monitor progress** and deploy your model
Need help? Join our [Discord community](https://discord.com/invite/s3tBEn7Ptg) or check out the [detailed examples](/getting-started/fine-tuning).
# Parameter Guide
Source: https://docs.oxen.ai/fine-tuning-api/parameters
Understanding fine-tuning parameters and how to tune them
## Overview
This guide explains common training parameters across all fine-tuning operations. Use this reference to understand what each parameter does and how to adjust them for your use case.
## LoRA (Low-Rank Adaptation)
LoRA is a technique for efficient fine-tuning that drastically reduces memory requirements and training time.
### `use_lora`
**Type:** `boolean`
**Default:** `true`
**Applies to:** All models
Whether to use LoRA for fine-tuning. Almost always recommended.
* `true` - Use LoRA (faster, less memory, recommended)
* `false` - Full fine-tuning (slower, more memory, rarely needed)
```json theme={null}
{
"training_params": {
"use_lora": true
}
}
```
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Range:** `1-128` (typical: `8-64`)
**Applies to:** All models when `use_lora: true`
The rank of LoRA matrices. Lower rank = faster training and less memory, but potentially less expressive.
**When to adjust:**
* **Reduce to 8** if you're out of memory or want faster training
* **Increase to 32-64** if you have a large, complex dataset and need more capacity
```json theme={null}
{
"training_params": {
"use_lora": true,
"lora_rank": 16
}
}
```
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Typical:** Same as `lora_rank`
**Applies to:** All models when `use_lora: true`
Scaling factor for LoRA updates. Typically set equal to `lora_rank`.
**When to adjust:**
* Keep equal to `lora_rank` in most cases
* Increase to make LoRA updates stronger (rare)
* Decrease to make updates more subtle (rare)
```json theme={null}
{
"training_params": {
"use_lora": true,
"lora_rank": 16,
"lora_alpha": 16
}
}
```
## Learning Rate and Optimization
### `learning_rate`
**Type:** `number`
**Default:** `0.0001` (text), `0.0002` (image)
**Typical range:** `0.00001-0.001`
**Applies to:** All models
The step size for parameter updates. Too high = unstable training, too low = slow convergence.
**When to adjust:**
* **Decrease by 10x** if training is unstable or loss is spiking
* **Increase by 2-3x** if training is too slow or plateau early
* **Text models**: Start with `0.0001`
* **Image models**: Start with `0.0002`
```json theme={null}
{
"training_params": {
"learning_rate": 0.0001
}
}
```
If you're unsure, stick with the defaults. Learning rate is the most sensitive parameter.
## Batch Size and Memory
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Typical range:** `1-8`
**Applies to:** All models
Number of samples processed together in one training step.
**Trade-offs:**
* **Larger batch size** = faster training, more stable, but more memory
* **Smaller batch size** = slower training, less stable, but less memory
**When to adjust:**
* **Reduce to 1** if you get out-of-memory errors
* **Increase to 2-4** if you have GPU memory to spare and want faster training
```json theme={null}
{
"training_params": {
"batch_size": 1
}
}
```
### `gradient_accumulation` / `grad_accum`
**Type:** `integer`
**Default:** `1`
**Typical range:** `1-16`
**Applies to:** All models
Accumulate gradients over multiple steps before updating parameters. This simulates a larger batch size without using more memory.
**When to use:**
* Set to 4-8 if you want the stability of larger batches but don't have the memory
* Effective batch size = `batch_size Γ gradient_accumulation`
```json theme={null}
{
"training_params": {
"batch_size": 1,
"gradient_accumulation": 4 // Effective batch size = 4
}
}
```
## Training Duration
### `epochs` (Text Models)
**Type:** `integer`
**Default:** `1`
**Typical range:** `1-5`
**Applies to:** Text generation models
Number of complete passes through the training dataset.
**Guidelines:**
* **1 epoch** - Good starting point, often sufficient
* **2-3 epochs** - For better learning on small datasets
* **>5 epochs** - Risk of overfitting
```json theme={null}
{
"training_params": {
"epochs": 1
}
}
```
### `steps` (Image/Video Models)
**Type:** `integer`
**Default:** `2000` (image), `3000` (editing)
**Typical range:** `1000-5000`
**Applies to:** Image and video generation models
Total number of training steps (optimizer updates).
**Guidelines:**
* **1000 steps** - Quick test runs
* **2000-3000 steps** - Standard training
* **4000-5000 steps** - Complex styles or large datasets
```json theme={null}
{
"training_params": {
"steps": 2000
}
}
```
## Logging and Checkpointing
### `logging_steps`
**Type:** `integer`
**Default:** `10`
**Applies to:** Text models
How often to log training metrics (loss, learning rate, etc.).
```json theme={null}
{
"training_params": {
"logging_steps": 10
}
}
```
### `save_steps_ratio`
**Type:** `number`
**Default:** `0.25`
**Range:** `0.0-1.0`
**Applies to:** Text models
Save checkpoints at this fraction of total training. For example, `0.25` with 4 epochs saves after each epoch.
```json theme={null}
{
"training_params": {
"save_steps_ratio": 0.25
}
}
```
### `save_strategy`
**Type:** `string`
**Default:** `"epoch"`
**Options:** `"epoch"`, `"steps"`
**Applies to:** Text models
When to save checkpoints:
* `"epoch"` - Save at the end of each epoch
* `"steps"` - Save based on `save_steps_ratio`
```json theme={null}
{
"training_params": {
"save_strategy": "epoch"
}
}
```
### `sample_every` (Image/Video Models)
**Type:** `integer`
**Default:** `200`
**Applies to:** Image and video models
Generate sample outputs every N steps to monitor progress visually.
```json theme={null}
{
"training_params": {
"sample_every": 200
}
}
```
## Model-Specific Parameters
### Text Generation
#### `seq_length`
**Type:** `integer`
**Default:** `1024`
**Range:** `128-4096`
Maximum sequence length for text. Longer = more context, but more memory.
```json theme={null}
{
"training_params": {
"seq_length": 1024
}
}
```
#### `neftune_noise_alpha`
**Type:** `number`
**Default:** `0`
**Range:** `0-15`
Add noise during training for better generalization (NEFTune). Set to 5-15 to enable.
```json theme={null}
{
"training_params": {
"neftune_noise_alpha": 0
}
}
```
### Image Generation/Editing
#### `timestep_type`
**Type:** `string`
**Default:** `"sigmoid"` (generation), `"weighted"` (editing)
**Options:** `"weighted"`, `"sigmoid"`, `"linear"`
How to sample timesteps during diffusion training.
* `"sigmoid"` - Focus on mid-range timesteps (balanced)
* `"weighted"` - Focus on difficult timesteps
* `"linear"` - Uniform sampling (simple)
```json theme={null}
{
"training_params": {
"timestep_type": "sigmoid"
}
}
```
#### `sample_width` / `sample_height`
**Type:** `integer`
**Default:** `1024`
**Applies to:** Image editing
Resolution for sample generation during training.
```json theme={null}
{
"training_params": {
"sample_width": 1024,
"sample_height": 1024
}
}
```
#### `cache_text_embeddings`
**Type:** `boolean`
**Default:** `false`
**Applies to:** Image models
Pre-compute and cache text embeddings for faster training.
```json theme={null}
{
"training_params": {
"cache_text_embeddings": false
}
}
```
## Quick Reference Tables
### Common Parameter Sets
**Fast Iteration (Test Runs):**
```json theme={null}
{
"batch_size": 1,
"learning_rate": 0.0001,
"epochs": 1, // or steps: 1000
"lora_rank": 8
}
```
**Standard Training:**
```json theme={null}
{
"batch_size": 1,
"learning_rate": 0.0001,
"epochs": 2, // or steps: 2000
"lora_rank": 16
}
```
**High Quality (Large Dataset):**
```json theme={null}
{
"batch_size": 2,
"learning_rate": 0.0001,
"gradient_accumulation": 4,
"epochs": 3, // or steps: 4000
"lora_rank": 32
}
```
## Troubleshooting
1. Reduce `batch_size` to 1
2. Reduce `lora_rank` to 8
3. Reduce `seq_length` (text) or `sample_width`/`sample_height` (image)
4. Enable `cache_text_embeddings` (image models)
1. Increase `learning_rate` by 2-3x
2. Check your data quality and column mappings
3. Increase training duration (`epochs` or `steps`)
1. Decrease `learning_rate` by 10x
2. Increase `gradient_accumulation` to smooth updates
3. Reduce `batch_size` to 1
1. Increase training duration (more `epochs` or `steps`)
2. Increase `lora_rank` to 32 or 64
3. Ensure your captions clearly describe the unique aspects
4. Add more training data
## Next Steps
* [Quick Start Guides](/fine-tuning-api/overview#quick-start-guides) - Apply these parameters
* [API Reference](/fine-tuning-api/overview#detailed-api-reference) - Complete parameter lists
* [Examples](/getting-started/fine-tuning) - See parameters in action
# Image Editing
Source: https://docs.oxen.ai/fine-tuning-api/quickstart/image-editing
Fine-tune an image editing model for custom transformations
## Overview
Fine-tune image editing models to learn specific transformations: style transfer, object manipulation, background changes, or any image-to-image task.
## Your Data
Your training data needs three columns:
* **Control/Input image** - The original image
* **Target/Output image** - The transformed image
* **Caption** - Text describing the transformation
Example data in `edits.parquet`:
| control\_image | edited\_image | caption |
| -------------- | --------------- | ---------------------------- |
| inputs/001.jpg | outputs/001.jpg | add sunglasses to the person |
| inputs/002.jpg | outputs/002.jpg | change background to beach |
| inputs/003.jpg | outputs/003.jpg | apply vintage filter |
## Minimal Example
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
# Create fine-tune
data = {
"resource": "main/edits.parquet",
"base_model": "black-forest-labs/FLUX.1-Kontext-dev",
"script_type": "image_editing",
"training_params": {
"control_image_column": "control_image", # Input image column
"image_column": "edited_image", # Output image column
"caption_column": "caption", # Description column
"steps": 3000
}
}
response = requests.post(url, headers=headers, json=data)
fine_tune_id = response.json()["fine_tune"]["id"]
# Start training
run_url = f"{url}/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
print(f"Fine-tune started: {fine_tune_id}")
```
```bash cURL theme={null}
# Create fine-tune
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "main/edits.parquet",
"base_model": "black-forest-labs/FLUX.1-Kontext-dev",
"script_type": "image_editing",
"training_params": {
"control_image_column": "control_image",
"image_column": "edited_image",
"caption_column": "caption",
"steps": 3000
}
}'
# Start training (use the ID from the response)
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/FINE_TUNE_ID/actions/run \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Key Parameters
Only these fields are required to start:
| Parameter | Description | Example |
| ---------------------- | -------------------------------------------- | ---------------------------------------- |
| `control_image_column` | Input/original image column | `"control_image"`, `"input"`, `"source"` |
| `image_column` | Output/transformed image column | `"edited_image"`, `"output"`, `"target"` |
| `caption_column` | Transformation description column | `"caption"`, `"prompt"`, `"description"` |
| `steps` | Number of training steps (2000-5000 typical) | `3000` |
All other parameters use sensible defaults.
## Supported Models
Popular choices for image editing:
* `black-forest-labs/FLUX.1-Kontext-dev` - High quality, versatile
* `Qwen/Qwen-Image-Edit` - Fast, good for quick iterations
See the [full model list](/fine-tuning-api/reference/image_editing#supported-models) for all available options.
## Data Requirements
For best results:
* **Quantity**: 20-100 image pairs minimum
* **Quality**: High resolution, aligned transformations
* **Captions**: Clear descriptions of what changed between input and output
* **Consistency**: Transformations should follow a consistent pattern or style
## Sample During Training
Add sample prompts to see progress during training:
```python theme={null}
data = {
"resource": "main/edits.parquet",
"base_model": "black-forest-labs/FLUX.1-Kontext-dev",
"script_type": "image_editing",
"training_params": {
"control_image_column": "control_image",
"image_column": "edited_image",
"caption_column": "caption",
"steps": 3000,
"samples": [
{
"ctrl_img_url": "https://your-repo.com/test_image.jpg",
"prompt": "apply the trained style transformation"
}
],
"sample_every": 200 # Generate sample every 200 steps
}
}
```
## Monitor Progress
```python theme={null}
status_url = f"https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/{fine_tune_id}"
response = requests.get(status_url, headers=headers)
fine_tune = response.json()["fine_tune"]
print(f"Status: {fine_tune['status']}")
print(f"Current step: {fine_tune.get('current_step', 0)}")
```
## Next Steps
* [Advanced parameters](/fine-tuning-api/reference/image_editing) - Resolution, LoRA, sampling settings
* [Deploy your model](/getting-started/inference) - Use your fine-tuned model
* [Full tutorial](/fine-tuning-api/tutorials/02_fine_tuning_image) - Complete walkthrough with deployment
## Common Issues
Ensure input and output images show the same scene/subject. The model learns the transformation between them.
Adjust `learning_rate` (lower for subtle, higher for stronger). Default is 0.0002.
Reduce `batch_size` to 1 and `sample_height`/`sample_width` to 512 or 768.
Ensure your captions consistently describe the transformation. Train for more steps (4000-5000) or increase dataset size.
# Image Generation
Source: https://docs.oxen.ai/fine-tuning-api/quickstart/image-generation
Fine-tune an image generation model for custom styles
## Overview
Fine-tune image generation models to create images in your specific style, brand, or artistic direction. Perfect for branded content, character design, or artistic styles.
## Your Data
Your training data should have:
* **Image column** - Paths or URLs to your training images
* **Caption column** - Text descriptions of each image
Example data in `images.parquet`:
| image | caption |
| -------------- | ----------------------------------- |
| images/001.jpg | a red sports car in cyberpunk style |
| images/002.jpg | a blue sedan in cyberpunk style |
| images/003.jpg | a motorcycle in cyberpunk style |
## Minimal Example
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
# Create fine-tune
data = {
"resource": "main/images.parquet",
"base_model": "black-forest-labs/FLUX.1-dev",
"script_type": "image_generation",
"training_params": {
"image_column": "image", # Your image column name
"caption_column": "caption", # Your caption column name
"steps": 2000
}
}
response = requests.post(url, headers=headers, json=data)
fine_tune_id = response.json()["fine_tune"]["id"]
# Start training
run_url = f"{url}/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
print(f"Fine-tune started: {fine_tune_id}")
```
```bash cURL theme={null}
# Create fine-tune
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "main/images.parquet",
"base_model": "black-forest-labs/FLUX.1-dev",
"script_type": "image_generation",
"training_params": {
"image_column": "image",
"caption_column": "caption",
"steps": 2000
}
}'
# Start training (use the ID from the response)
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/FINE_TUNE_ID/actions/run \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Key Parameters
Only these fields are required to start:
| Parameter | Description | Example |
| ---------------- | -------------------------------------------- | ---------------------------------------- |
| `image_column` | Name of your image column | `"image"`, `"file"`, `"path"` |
| `caption_column` | Name of your caption/prompt column | `"caption"`, `"prompt"`, `"description"` |
| `steps` | Number of training steps (1000-3000 typical) | `2000` |
All other parameters use sensible defaults.
## Supported Models
Popular choices for image generation:
* `black-forest-labs/FLUX.1-dev` - High quality, state-of-the-art
* `black-forest-labs/FLUX.2-dev` - Latest version, even better quality
* `Qwen/Qwen-Image` - Fast, good for quick iterations
See the [full model list](/fine-tuning-api/reference/image_generation#supported-models) for all available options.
## Data Requirements
For best results:
* **Quantity**: 10-50 images minimum, 100-500 images ideal
* **Quality**: High resolution, consistent style
* **Captions**: Descriptive prompts that explain what makes your images unique
* **Consistency**: Images should share common elements (style, subject, theme)
## Monitor Progress
Image models generate sample outputs during training. Check them to see progress:
```python theme={null}
status_url = f"https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/{fine_tune_id}"
response = requests.get(status_url, headers=headers)
fine_tune = response.json()["fine_tune"]
print(f"Status: {fine_tune['status']}")
print(f"Current step: {fine_tune.get('current_step', 0)}")
# Sample images are generated every 200 steps by default
if "sample_outputs" in fine_tune:
print(f"Sample images: {fine_tune['sample_outputs']}")
```
## Next Steps
* [Advanced parameters](/fine-tuning-api/reference/image_generation) - Learning rate, LoRA, sampling settings
* [Deploy your model](/getting-started/inference) - Generate images with your fine-tuned model
* [Parameter guide](/fine-tuning-api/parameters) - Understanding training parameters
## Common Issues
Ensure image paths are relative to your repository root, or use full URLs. Check that images are committed to your Oxen repository.
Reduce `batch_size` to 1. Image models require significant GPU memory.
Try training for more steps (3000-5000) or increase your dataset size. Ensure captions clearly describe the unique aspects of your style.
Start with 1000 steps for testing. FLUX models take 1-2 hours on GPU for 2000 steps.
# Text Generation
Source: https://docs.oxen.ai/fine-tuning-api/quickstart/text
Fine-tune a text generation model in minutes
## Overview
Fine-tune text generation models for chatbots, Q\&A systems, or content generation. This guide shows the minimal setup to get started.
## Your Data
Your training data should have two columns:
* **Input column** - The user prompt or question
* **Output column** - The expected response or answer
Example data in `train.parquet`:
| text | sentiment |
| ----------------------------------- | --------- |
| The product exceeded expectations | positive |
| Terrible customer service | negative |
| Average experience, nothing special | neutral |
## Minimal Example
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
# Create fine-tune
data = {
"resource": "main/train.parquet",
"base_model": "meta-llama/Llama-3.2-1B-Instruct",
"script_type": "text_generation",
"training_params": {
"question_column": "text", # Your input column name
"answer_column": "sentiment", # Your output column name
"epochs": 1
}
}
response = requests.post(url, headers=headers, json=data)
fine_tune_id = response.json()["fine_tune"]["id"]
# Start training
run_url = f"{url}/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
print(f"Fine-tune started: {fine_tune_id}")
```
```bash cURL theme={null}
# Create fine-tune
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "main/train.parquet",
"base_model": "meta-llama/Llama-3.2-1B-Instruct",
"script_type": "text_generation",
"training_params": {
"question_column": "text",
"answer_column": "sentiment",
"epochs": 1
}
}'
# Start training (use the ID from the response)
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/FINE_TUNE_ID/actions/run \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Key Parameters
Only these fields are required to start:
| Parameter | Description | Example |
| ----------------- | --------------------------------------- | --------------------------------------- |
| `question_column` | Name of your input/prompt column | `"text"`, `"question"`, `"prompt"` |
| `answer_column` | Name of your output/response column | `"sentiment"`, `"answer"`, `"response"` |
| `epochs` | Number of training passes (1-3 typical) | `1` |
All other parameters use sensible defaults.
## Supported Models
Popular choices for text generation:
* `meta-llama/Llama-3.2-1B-Instruct` - Fast, good for Q\&A
* `meta-llama/Llama-3.2-3B-Instruct` - Balanced performance
* `meta-llama/Llama-3.1-8B-Instruct` - Higher quality, slower
* `Qwen/Qwen3-0.6B` - Very fast, lightweight
See the [full model list](/fine-tuning-api/reference/text_generation#supported-models) for all available options.
## Monitor Progress
Check the status of your fine-tune:
```python theme={null}
status_url = f"https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/{fine_tune_id}"
response = requests.get(status_url, headers=headers)
status = response.json()["fine_tune"]["status"]
print(f"Status: {status}")
```
Status values: `created`, `running`, `completed`, `errored`
## Next Steps
* [Advanced parameters](/fine-tuning-api/reference/text_generation) - Learning rate, batch size, LoRA configuration
* [Deploy your model](/getting-started/inference) - Use your fine-tuned model for inference
* [Full tutorial](/fine-tuning-api/tutorials/01_fine_tuning) - End-to-end walkthrough with monitoring
## Common Issues
Double-check your `question_column` and `answer_column` names match your data exactly. Column names are case-sensitive.
Reduce `batch_size` to 1 or try a smaller model like `Llama-3.2-1B-Instruct`.
Start with 1 epoch. If results aren't good enough, try 2-3 epochs. More isn't always better.
# Video Generation
Source: https://docs.oxen.ai/fine-tuning-api/quickstart/video
Fine-tune video generation models for custom content
## Overview
Fine-tune video generation models to create videos in your specific style. Works for both text-to-video and image-to-video generation.
## Your Data
### Text-to-Video
Data should have:
* **Video column** - Paths to your training videos
* **Caption column** - Text descriptions of each video
Example `videos.parquet`:
| video | caption |
| ------------- | -------------------------------- |
| clips/001.mp4 | person walking in cyberpunk city |
| clips/002.mp4 | car driving through neon streets |
### Image-to-Video
Data should have:
* **Video column** - Output video paths
* **Image column** - First frame/reference image
* **Caption column** - Description of the motion/action
Example `img2vid.parquet`:
| image | video | caption |
| -------------- | ------------- | ------------------------ |
| frames/001.jpg | clips/001.mp4 | zoom into the building |
| frames/002.jpg | clips/002.mp4 | camera pan left to right |
## Minimal Example: Text-to-Video
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
# Create fine-tune
data = {
"resource": "main/videos.parquet",
"base_model": "YOUR_VIDEO_MODEL", # e.g., a video generation model
"script_type": "text_to_video",
"training_params": {
"video_column": "video",
"caption_column": "caption",
"steps": 2000
}
}
response = requests.post(url, headers=headers, json=data)
fine_tune_id = response.json()["fine_tune"]["id"]
# Start training
run_url = f"{url}/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
print(f"Fine-tune started: {fine_tune_id}")
```
```bash cURL theme={null}
# Create fine-tune
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "main/videos.parquet",
"base_model": "YOUR_VIDEO_MODEL",
"script_type": "text_to_video",
"training_params": {
"video_column": "video",
"caption_column": "caption",
"steps": 2000
}
}'
# Start training
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/FINE_TUNE_ID/actions/run \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Minimal Example: Image-to-Video
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
# Create fine-tune
data = {
"resource": "main/img2vid.parquet",
"base_model": "YOUR_VIDEO_MODEL",
"script_type": "image_to_video",
"training_params": {
"image_column": "image", # First frame/reference
"video_column": "video", # Output video
"caption_column": "caption", # Motion description
"steps": 2000
}
}
response = requests.post(url, headers=headers, json=data)
fine_tune_id = response.json()["fine_tune"]["id"]
# Start training
run_url = f"{url}/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
print(f"Fine-tune started: {fine_tune_id}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "main/img2vid.parquet",
"base_model": "YOUR_VIDEO_MODEL",
"script_type": "image_to_video",
"training_params": {
"image_column": "image",
"video_column": "video",
"caption_column": "caption",
"steps": 2000
}
}'
```
## Key Parameters
**Text-to-Video:**
| Parameter | Description | Example |
| ---------------- | ------------------ | ----------------------- |
| `video_column` | Video file column | `"video"`, `"clip"` |
| `caption_column` | Description column | `"caption"`, `"prompt"` |
| `steps` | Training steps | `2000` |
**Image-to-Video:**
| Parameter | Description | Example |
| ---------------- | --------------------------- | ----------------------- |
| `image_column` | First frame/reference image | `"image"`, `"frame"` |
| `video_column` | Output video column | `"video"`, `"clip"` |
| `caption_column` | Motion description | `"caption"`, `"motion"` |
| `steps` | Training steps | `2000` |
## Data Requirements
Video fine-tuning is resource-intensive:
* **Quantity**: 50-200 videos minimum
* **Quality**: Consistent resolution, frame rate, duration
* **Length**: 2-10 seconds per clip (shorter is better)
* **Format**: MP4, WebM, or other common formats
* **Captions**: Describe motion, camera movement, and key actions
Video fine-tuning requires significant compute resources and storage. Expect longer training times compared to image or text models.
## Monitor Progress
```python theme={null}
status_url = f"https://hub.oxen.ai/api/repos/YOUR_NAMESPACE/YOUR_REPO/fine_tunes/{fine_tune_id}"
response = requests.get(status_url, headers=headers)
fine_tune = response.json()["fine_tune"]
print(f"Status: {fine_tune['status']}")
print(f"Current step: {fine_tune.get('current_step', 0)}")
```
## Next Steps
* [Text-to-Video Reference](/fine-tuning-api/reference/text_to_video) - All parameters
* [Image-to-Video Reference](/fine-tuning-api/reference/image_to_video) - All parameters
* [Deploy your model](/getting-started/inference) - Generate videos with your fine-tuned model
## Common Issues
Ensure videos are committed to your Oxen repository. Check file paths are correct and relative to repo root.
Video models need significant GPU memory. Reduce `batch_size` to 1 and consider shorter video clips.
Video fine-tuning takes hours to days. Start with 1000 steps for testing. Use shorter videos (2-5 seconds) for faster iteration.
Ensure training videos have consistent quality, resolution, and frame rate. Increase training steps to 3000-5000.
# Fine-Tune: Image Editing
Source: https://docs.oxen.ai/fine-tuning-api/reference/image_editing
Fine-tune a model to edit images
## Overview
This schema is used for fine-tuning models with **image editing** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_editing",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `image_editing` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Qwen Image Edit (`Qwen/Qwen-Image-Edit`)
* FLUX.1-Kontext \[dev] (`black-forest-labs/FLUX.1-Kontext-dev`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| ----------------------- | ------- | -------- | ---------------------------------------------------- |
| `batch_size` | integer | No | Batch Size (default: 1) (min: 1) |
| `cache_text_embeddings` | boolean | No | Cache Text Embeddings |
| `caption_column` | string | Yes | Caption Column (prompt) (DataFrame column name) |
| `control_image_column` | string | Yes | Control Image Column (input) (DataFrame column name) |
| `gradient_accumulation` | integer | No | Gradient Accumulation (default: 1) (min: 1) |
| `image_column` | string | Yes | Target Image Column (output) (DataFrame column name) |
| `learning_rate` | number | No | Learning Rate (default: 0.0002) |
| `lora_alpha` | integer | No | LoRA Alpha (default: 16) (min: 1) |
| `lora_rank` | integer | No | LoRA Rank (default: 16) (min: 1) |
| `sample_every` | integer | No | Sample Every (default: 200) (min: 1) |
| `sample_height` | integer | No | Sample Height (default: 1024) (min: 1) |
| `sample_width` | integer | No | Sample Width (default: 1024) (min: 1) |
| `samples` | array | No | Samples (array of object) |
| `steps` | integer | No | Steps (default: 3000) (min: 1) |
| `timestep_type` | string | No | Timestep Type (options: weighted, sigmoid, linear) |
| `use_lora` | boolean | No | Use LoRA |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_editing",
"training_params": {
"batch_size": 1,
"cache_text_embeddings": false,
"caption_column": "",
"control_image_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"sample_height": 1024,
"sample_width": 1024,
"samples": [
{
"ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"prompt": "an ox holding a sign that says 'Oxen.ai'"
},
{
"ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"prompt": "a herd of oxen running in a field"
}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_editing",
"training_params": {{
"batch_size": 1,
"cache_text_embeddings": false,
"caption_column": "",
"control_image_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"sample_height": 1024,
"sample_width": 1024,
"samples": [
{{
"ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"prompt": "an ox holding a sign that says 'Oxen.ai'"
}},
{{
"ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"prompt": "a herd of oxen running in a field"
}}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "image_editing", "training_params": { "batch_size": 1, "cache_text_embeddings": false, "caption_column": "", "control_image_column": "", "gradient_accumulation": 1, "image_column": "", "learning_rate": 0.0002, "lora_alpha": 16, "lora_rank": 16, "sample_every": 200, "sample_height": 1024, "sample_width": 1024, "samples": [ { "ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "prompt": "an ox holding a sign that says 'Oxen.ai'" }, { "ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "prompt": "a herd of oxen running in a field" } ], "steps": 3000, "timestep_type": "weighted", "use_lora": true } }'
```
## Field Details
### `batch_size`
**Batch Size**
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `cache_text_embeddings`
**Cache Text Embeddings**
**Type:** `boolean`
**Default:** `false`
### `caption_column`
**Caption Column (prompt)**
**Type:** `string`
**Default:** `""`
### `control_image_column`
**Control Image Column (input)**
**Type:** `string`
**Default:** `""`
### `gradient_accumulation`
**Gradient Accumulation**
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `image_column`
**Target Image Column (output)**
**Type:** `string`
**Default:** `""`
### `learning_rate`
**Learning Rate**
**Type:** `number`
**Default:** `0.0002`
### `lora_alpha`
**LoRA Alpha**
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**LoRA Rank**
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `sample_every`
**Sample Every**
**Type:** `integer`
How often to generate samples during training (n steps)
**Default:** `200`
**Minimum:** `1`
### `sample_height`
**Sample Height**
**Type:** `integer`
**Default:** `1024`
**Minimum:** `1`
### `sample_width`
**Sample Width**
**Type:** `integer`
**Default:** `1024`
**Minimum:** `1`
### `samples`
**Samples**
**Type:** `array`
Used to show progress during the fine-tuning process
**Default:** `[{"ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "prompt": "an ox holding a sign that says 'Oxen.ai'"}, {"ctrl_img_url": "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "prompt": "a herd of oxen running in a field"}]`
### `steps`
**Steps**
**Type:** `integer`
**Default:** `3000`
**Minimum:** `1`
### `timestep_type`
**Timestep Type**
**Type:** `string`
**Default:** `"weighted"`
**Options:** `weighted`, `sigmoid`, `linear`
### `use_lora`
**Use LoRA**
**Type:** `boolean`
**Default:** `true`
# Fine-Tune: Image Generation
Source: https://docs.oxen.ai/fine-tuning-api/reference/image_generation
Fine-tune a model to generate an image from text
## Overview
This schema is used for fine-tuning models with **image generation** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_generation",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `image_generation` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Qwen Image (`Qwen/Qwen-Image`)
* FLUX.1 \[dev] (`black-forest-labs/FLUX.1-dev`)
* FLUX.2 \[dev] (`black-forest-labs/FLUX.2-dev`)
* Z-Image-Turbo (`Tongyi-MAI/Z-Image-Turbo`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| ----------------------- | ------- | -------- | --------------------------------------- |
| `batch_size` | integer | No | (default: 1) (min: 1) |
| `caption_column` | string | Yes | caption\_column (DataFrame column name) |
| `gradient_accumulation` | integer | No | (default: 1) (min: 1) |
| `image_column` | string | Yes | image\_column (DataFrame column name) |
| `learning_rate` | number | No | (default: 0.0002) |
| `lora_alpha` | integer | No | (default: 16) (min: 1) |
| `lora_rank` | integer | No | (default: 16) (min: 1) |
| `sample_every` | integer | No | (default: 200) (min: 1) |
| `samples` | array | No | (array of object) |
| `steps` | integer | No | (default: 2000) (min: 1) |
| `timestep_type` | string | No | (options: weighted, sigmoid, linear) |
| `use_lora` | boolean | No | use\_lora |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_generation",
"training_params": {
"batch_size": 1,
"caption_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"samples": [
{
"prompt": "an ox holding a sign that says 'Oxen.ai'"
},
{
"prompt": "a herd of oxen running in a field"
}
],
"steps": 2000,
"timestep_type": "sigmoid",
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_generation",
"training_params": {{
"batch_size": 1,
"caption_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"samples": [
{{
"prompt": "an ox holding a sign that says 'Oxen.ai'"
}},
{{
"prompt": "a herd of oxen running in a field"
}}
],
"steps": 2000,
"timestep_type": "sigmoid",
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "image_generation", "training_params": { "batch_size": 1, "caption_column": "", "gradient_accumulation": 1, "image_column": "", "learning_rate": 0.0002, "lora_alpha": 16, "lora_rank": 16, "sample_every": 200, "samples": [ { "prompt": "an ox holding a sign that says 'Oxen.ai'" }, { "prompt": "a herd of oxen running in a field" } ], "steps": 2000, "timestep_type": "sigmoid", "use_lora": true } }'
```
## Field Details
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `caption_column`
**Type:** `string`
**Default:** `""`
### `gradient_accumulation`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `image_column`
**Type:** `string`
**Default:** `""`
### `learning_rate`
**Type:** `number`
**Default:** `0.0002`
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `sample_every`
**Type:** `integer`
**Default:** `200`
**Minimum:** `1`
### `samples`
**Type:** `array`
**Default:** `[{"prompt": "an ox holding a sign that says 'Oxen.ai'"}, {"prompt": "a herd of oxen running in a field"}]`
### `steps`
**Type:** `integer`
**Default:** `2000`
**Minimum:** `1`
### `timestep_type`
**Type:** `string`
**Default:** `"sigmoid"`
**Options:** `weighted`, `sigmoid`, `linear`
### `use_lora`
**Type:** `boolean`
**Default:** `true`
# Fine-Tune: Image To Text
Source: https://docs.oxen.ai/fine-tuning-api/reference/image_to_text
Image to text fine-tuning schema
## Overview
This schema is used for fine-tuning models with **image to text** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_to_text",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `image_to_text` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Qwen3 VL 8B - Instruct (`Qwen/Qwen3-VL-8B-Instruct`)
* Qwen3 VL 2B - Instruct (`Qwen/Qwen3-VL-2B-Instruct`)
* Qwen3 VL 4B - Instruct (`Qwen/Qwen3-VL-4B-Instruct`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| --------------------- | ------- | -------- | ----------------------------------------------------------------- |
| `answer_column` | string | Yes | Response Column (DataFrame column name) |
| `batch_size` | integer | No | (default: 1) (min: 1) |
| `enable_thinking` | boolean | No | enable\_thinking |
| `epochs` | integer | No | (default: 1) (min: 1) |
| `grad_accum` | integer | No | (default: 1) (min: 1) |
| `image_columns` | array | Yes | Image Columns (array of string) (Multiple DataFrame column names) |
| `learning_rate` | number | No | (default: 0.0001) |
| `logging_steps` | integer | No | (default: 10) (min: 1) |
| `lora_alpha` | integer | No | (default: 16) (min: 1) |
| `lora_rank` | integer | No | (default: 16) (min: 1) |
| `neftune_noise_alpha` | number | No | (default: 0) |
| `question_column` | string | Yes | Prompt Column (DataFrame column name) |
| `save_steps_ratio` | number | No | (default: 0.25) |
| `save_strategy` | string | No | save\_strategy |
| `seq_length` | integer | No | (default: 4096) (min: 1) |
| `use_lora` | boolean | No | Use LoRA |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_to_text",
"training_params": {
"answer_column": "",
"batch_size": 1,
"enable_thinking": false,
"epochs": 1,
"grad_accum": 1,
"image_columns": [],
"learning_rate": 0.0001,
"logging_steps": 10,
"lora_alpha": 16,
"lora_rank": 16,
"neftune_noise_alpha": 0,
"question_column": "",
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"seq_length": 4096,
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_to_text",
"training_params": {{
"answer_column": "",
"batch_size": 1,
"enable_thinking": false,
"epochs": 1,
"grad_accum": 1,
"image_columns": [],
"learning_rate": 0.0001,
"logging_steps": 10,
"lora_alpha": 16,
"lora_rank": 16,
"neftune_noise_alpha": 0,
"question_column": "",
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"seq_length": 4096,
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "image_to_text", "training_params": { "answer_column": "", "batch_size": 1, "enable_thinking": false, "epochs": 1, "grad_accum": 1, "image_columns": [], "learning_rate": 0.0001, "logging_steps": 10, "lora_alpha": 16, "lora_rank": 16, "neftune_noise_alpha": 0, "question_column": "", "save_steps_ratio": 0.25, "save_strategy": "epoch", "seq_length": 4096, "use_lora": true } }'
```
## Field Details
### `answer_column`
**Response Column**
**Type:** `string`
Column containing the captions or responses
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `enable_thinking`
**Type:** `boolean`
**Default:** `false`
### `epochs`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `grad_accum`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `image_columns`
**Image Columns**
**Type:** `array`
Columns containing image file paths
**Default:** `[]`
### `learning_rate`
**Type:** `number`
**Default:** `0.0001`
**Minimum:** `0`
### `logging_steps`
**Type:** `integer`
**Default:** `10`
**Minimum:** `1`
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `neftune_noise_alpha`
**Type:** `number`
**Default:** `0`
**Minimum:** `0`
### `question_column`
**Prompt Column**
**Type:** `string`
Column containing the prompts or questions for each image
### `save_steps_ratio`
**Type:** `number`
**Default:** `0.25`
### `save_strategy`
**Type:** `string`
**Default:** `"epoch"`
### `seq_length`
**Type:** `integer`
**Default:** `4096`
**Minimum:** `1`
### `use_lora`
**Use LoRA**
**Type:** `boolean`
Enable LoRA for faster fine-tuning and lower memory use
**Default:** `true`
# Fine-Tune: Image To Video
Source: https://docs.oxen.ai/fine-tuning-api/reference/image_to_video
Image to video fine-tuning schema
## Overview
This schema is used for fine-tuning models with **image to video** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_to_video",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `image_to_video` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Wan2.1 1.3B - Text to Video (`Wan-AI/Wan2.1-T2V-1.3B-Diffusers`)
* Wan2.2 A14B - Text to Video (`Wan-AI/Wan2.2-T2V-A14B-Diffusers`)
* Wan2.1 14B - Text to Video (`Wan-AI/Wan2.1-T2V-14B-Diffusers`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| ----------------------- | ------- | -------- | --------------------------------------- |
| `batch_size` | integer | No | (default: 1) (min: 1) |
| `caption_column` | string | Yes | caption\_column (DataFrame column name) |
| `gradient_accumulation` | integer | No | (default: 1) (min: 1) |
| `image_column` | string | Yes | image\_column (DataFrame column name) |
| `learning_rate` | number | No | (default: 0.0002) |
| `lora_alpha` | integer | No | (default: 16) (min: 1) |
| `lora_rank` | integer | No | (default: 16) (min: 1) |
| `num_frames` | integer | No | (default: 81) (min: 1) |
| `sample_every` | integer | No | (default: 200) (min: 1) |
| `samples` | array | No | Samples (array of object) |
| `steps` | integer | No | (default: 3000) (min: 1) |
| `timestep_type` | string | No | (options: weighted, linear, sigmoid) |
| `use_lora` | boolean | No | use\_lora |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_to_video",
"training_params": {
"batch_size": 1,
"caption_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"num_frames": 81,
"sample_every": 200,
"samples": [
{
"prompt": "an ox holding a sign that says 'Oxen.ai'"
},
{
"prompt": "a herd of oxen running in a field"
}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "image_to_video",
"training_params": {{
"batch_size": 1,
"caption_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"num_frames": 81,
"sample_every": 200,
"samples": [
{{
"prompt": "an ox holding a sign that says 'Oxen.ai'"
}},
{{
"prompt": "a herd of oxen running in a field"
}}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "image_to_video", "training_params": { "batch_size": 1, "caption_column": "", "gradient_accumulation": 1, "image_column": "", "learning_rate": 0.0002, "lora_alpha": 16, "lora_rank": 16, "num_frames": 81, "sample_every": 200, "samples": [ { "prompt": "an ox holding a sign that says 'Oxen.ai'" }, { "prompt": "a herd of oxen running in a field" } ], "steps": 3000, "timestep_type": "weighted", "use_lora": true } }'
```
## Field Details
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `caption_column`
**Type:** `string`
### `gradient_accumulation`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `image_column`
**Type:** `string`
### `learning_rate`
**Type:** `number`
**Default:** `0.0002`
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `num_frames`
**Type:** `integer`
**Default:** `81`
**Minimum:** `1`
### `sample_every`
**Type:** `integer`
**Default:** `200`
**Minimum:** `1`
### `samples`
**Samples**
**Type:** `array`
Used to show progress during the fine-tuning process
**Default:** `[{"prompt": "an ox holding a sign that says 'Oxen.ai'"}, {"prompt": "a herd of oxen running in a field"}]`
### `steps`
**Type:** `integer`
**Default:** `3000`
**Minimum:** `1`
### `timestep_type`
**Type:** `string`
**Default:** `"weighted"`
**Options:** `weighted`, `linear`, `sigmoid`
### `use_lora`
**Type:** `boolean`
# Fine-Tune: Multi Image Editing
Source: https://docs.oxen.ai/fine-tuning-api/reference/multi_image_editing
Fine-tune a model to take multiple images as input and output an image as output
## Overview
This schema is used for fine-tuning models with **multi image editing** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "multi_image_editing",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `multi_image_editing` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Qwen Image Edit - 2509 (`Qwen/Qwen-Image-Edit-2509`)
* Qwen Image Edit - 2511 (`Qwen/Qwen-Image-Edit-2511`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| ----------------------- | ------- | -------- | ---------------------------------------------------------------------------------- |
| `batch_size` | integer | No | Batch Size (default: 1) (min: 1) |
| `caption_column` | string | Yes | Caption Column (DataFrame column name) |
| `control_image_columns` | array | Yes | Control Image Columns (inputs) (array of string) (Multiple DataFrame column names) |
| `gradient_accumulation` | integer | No | Gradient Accumulation (default: 1) (min: 1) |
| `image_column` | string | Yes | Image Column (output) (DataFrame column name) |
| `learning_rate` | number | No | Learning Rate (default: 0.0002) |
| `lora_alpha` | integer | No | LoRA Alpha (default: 16) (min: 1) |
| `lora_rank` | integer | No | LoRA Rank (default: 16) (min: 1) |
| `sample_every` | integer | No | Sample Every (default: 200) (min: 1) |
| `sample_height` | integer | No | Sample Height (default: 1024) (min: 1) |
| `sample_width` | integer | No | Sample Width (default: 1024) (min: 1) |
| `samples` | array | No | Samples (array of object) |
| `steps` | integer | No | Steps (default: 3000) (min: 1) |
| `timestep_type` | string | No | Timestep Type (options: weighted, sigmoid, linear) |
| `use_lora` | boolean | No | Use LoRA |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "multi_image_editing",
"training_params": {
"batch_size": 1,
"caption_column": "",
"control_image_columns": [],
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"sample_height": 1024,
"sample_width": 1024,
"samples": [
{
"ctrl_img_urls": [
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png"
],
"prompt": "an ox holding a sign that says 'Oxen.ai'"
},
{
"ctrl_img_urls": [
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png"
],
"prompt": "a herd of oxen running in a field"
}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "multi_image_editing",
"training_params": {{
"batch_size": 1,
"caption_column": "",
"control_image_columns": [],
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"sample_height": 1024,
"sample_width": 1024,
"samples": [
{{
"ctrl_img_urls": [
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png"
],
"prompt": "an ox holding a sign that says 'Oxen.ai'"
}},
{{
"ctrl_img_urls": [
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png",
"https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png"
],
"prompt": "a herd of oxen running in a field"
}}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "multi_image_editing", "training_params": { "batch_size": 1, "caption_column": "", "control_image_columns": [], "gradient_accumulation": 1, "image_column": "", "learning_rate": 0.0002, "lora_alpha": 16, "lora_rank": 16, "sample_every": 200, "sample_height": 1024, "sample_width": 1024, "samples": [ { "ctrl_img_urls": [ "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png" ], "prompt": "an ox holding a sign that says 'Oxen.ai'" }, { "ctrl_img_urls": [ "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png" ], "prompt": "a herd of oxen running in a field" } ], "steps": 3000, "timestep_type": "weighted", "use_lora": true } }'
```
## Field Details
### `batch_size`
**Batch Size**
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `caption_column`
**Caption Column**
**Type:** `string`
**Default:** `""`
### `control_image_columns`
**Control Image Columns (inputs)**
**Type:** `array`
**Default:** `[]`
### `gradient_accumulation`
**Gradient Accumulation**
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `image_column`
**Image Column (output)**
**Type:** `string`
**Default:** `""`
### `learning_rate`
**Learning Rate**
**Type:** `number`
**Default:** `0.0002`
### `lora_alpha`
**LoRA Alpha**
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**LoRA Rank**
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `sample_every`
**Sample Every**
**Type:** `integer`
How often to generate samples during training (n steps)
**Default:** `200`
**Minimum:** `1`
### `sample_height`
**Sample Height**
**Type:** `integer`
**Default:** `1024`
**Minimum:** `1`
### `sample_width`
**Sample Width**
**Type:** `integer`
**Default:** `1024`
**Minimum:** `1`
### `samples`
**Samples**
**Type:** `array`
Used to show progress during the fine-tuning process
**Default:** `[{"ctrl_img_urls": ["https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png"], "prompt": "an ox holding a sign that says 'Oxen.ai'"}, {"ctrl_img_urls": ["https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png", "https://hub.oxen.ai/api/repos/ox/Oxen-Character-Simple-Vector-Graphic/file/main/images/reference/bloxy_white_bg.png"], "prompt": "a herd of oxen running in a field"}]`
### `steps`
**Steps**
**Type:** `integer`
**Default:** `3000`
**Minimum:** `1`
### `timestep_type`
**Timestep Type**
**Type:** `string`
**Default:** `"weighted"`
**Options:** `weighted`, `sigmoid`, `linear`
### `use_lora`
**Use LoRA**
**Type:** `boolean`
**Default:** `true`
# Fine-Tune: Text Chat Messages
Source: https://docs.oxen.ai/fine-tuning-api/reference/text_chat_messages
Default fine-tune schema
## Overview
This schema is used for fine-tuning models with **text chat messages** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_chat_messages",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `text_chat_messages` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* OpenAI/GPT-OSS-20B (`openai/gpt-oss-20b`)
* Llama 3.1 8B Instruct (`meta-llama/Llama-3.1-8B-Instruct`)
* Llama 4 Scout (`meta-llama/Llama-4-Scout-17B-16E-Instruct`)
* Llama 3.2 3B Instruct (`meta-llama/Llama-3.2-3B-Instruct`)
* Qwen/Qwen3-1.7B (`Qwen/Qwen3-1.7B`)
* Qwen/Qwen3-4B (`Qwen/Qwen3-4B`)
* Qwen/Qwen3-0.6B (`Qwen/Qwen3-0.6B`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| --------------------- | ------- | -------- | ----------------------------------------------- |
| `batch_size` | integer | No | (default: 1) (min: 1) |
| `enable_thinking` | boolean | No | enable\_thinking |
| `epochs` | integer | No | (default: 1) (min: 1) |
| `grad_accum` | integer | No | (default: 1) (min: 1) |
| `learning_rate` | number | No | (default: 0.0001) |
| `logging_steps` | integer | No | (default: 10) (min: 1) |
| `lora_alpha` | integer | No | (default: 16) (min: 1) |
| `lora_rank` | integer | No | (default: 16) (min: 1) |
| `messages_column` | string | Yes | Messages (input) Column (DataFrame column name) |
| `neftune_noise_alpha` | number | No | (default: 0) |
| `save_steps_ratio` | number | No | (default: 0.25) |
| `save_strategy` | string | No | save\_strategy |
| `seq_length` | integer | No | (default: 1024) (min: 1) |
| `use_lora` | boolean | No | Use LoRA |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_chat_messages",
"training_params": {
"batch_size": 1,
"enable_thinking": false,
"epochs": 1,
"grad_accum": 1,
"learning_rate": 0.0001,
"logging_steps": 10,
"lora_alpha": 16,
"lora_rank": 16,
"messages_column": "",
"neftune_noise_alpha": 0,
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"seq_length": 1024,
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_chat_messages",
"training_params": {{
"batch_size": 1,
"enable_thinking": false,
"epochs": 1,
"grad_accum": 1,
"learning_rate": 0.0001,
"logging_steps": 10,
"lora_alpha": 16,
"lora_rank": 16,
"messages_column": "",
"neftune_noise_alpha": 0,
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"seq_length": 1024,
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "text_chat_messages", "training_params": { "batch_size": 1, "enable_thinking": false, "epochs": 1, "grad_accum": 1, "learning_rate": 0.0001, "logging_steps": 10, "lora_alpha": 16, "lora_rank": 16, "messages_column": "", "neftune_noise_alpha": 0, "save_steps_ratio": 0.25, "save_strategy": "epoch", "seq_length": 1024, "use_lora": true } }'
```
## Field Details
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `enable_thinking`
**Type:** `boolean`
**Default:** `false`
### `epochs`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `grad_accum`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `learning_rate`
**Type:** `number`
**Default:** `0.0001`
**Minimum:** `0`
### `logging_steps`
**Type:** `integer`
**Default:** `10`
**Minimum:** `1`
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `messages_column`
**Messages (input) Column**
**Type:** `string`
### `neftune_noise_alpha`
**Type:** `number`
**Default:** `0`
**Minimum:** `0`
### `save_steps_ratio`
**Type:** `number`
**Default:** `0.25`
### `save_strategy`
**Type:** `string`
**Default:** `"epoch"`
### `seq_length`
**Type:** `integer`
**Default:** `1024`
**Minimum:** `1`
### `use_lora`
**Use LoRA**
**Type:** `boolean`
Enable LoRA for faster fine-tuning and lower memory use
**Default:** `true`
# Fine-Tune: Text Generation
Source: https://docs.oxen.ai/fine-tuning-api/reference/text_generation
Fine tune a model to generate text given an input
## Overview
This schema is used for fine-tuning models with **text generation** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_generation",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `text_generation` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Llama 3.2 1B Instruct (`meta-llama/Llama-3.2-1B-Instruct`)
* OpenAI/GPT-OSS-20B (`openai/gpt-oss-20b`)
* Llama 3.1 8B Instruct (`meta-llama/Llama-3.1-8B-Instruct`)
* Llama 3.2 3B Instruct (`meta-llama/Llama-3.2-3B-Instruct`)
* Qwen/Qwen3-1.7B (`Qwen/Qwen3-1.7B`)
* Qwen/Qwen3-4B (`Qwen/Qwen3-4B`)
* Qwen/Qwen3-0.6B (`Qwen/Qwen3-0.6B`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| --------------------- | ------- | -------- | --------------------------------------------------- |
| `answer_column` | string | Yes | Assistant (Response) Column (DataFrame column name) |
| `batch_size` | integer | No | (default: 1) (min: 1) |
| `enable_thinking` | boolean | No | enable\_thinking |
| `epochs` | integer | No | (default: 1) (min: 1) |
| `grad_accum` | integer | No | (default: 1) (min: 1) |
| `learning_rate` | number | No | (default: 0.0001) |
| `logging_steps` | integer | No | (default: 10) (min: 1) |
| `lora_alpha` | integer | No | (default: 16) (min: 1) |
| `lora_rank` | integer | No | (default: 16) (min: 1) |
| `neftune_noise_alpha` | number | No | (default: 0) |
| `question_column` | string | Yes | User (Prompt) Column (DataFrame column name) |
| `save_steps_ratio` | number | No | (default: 0.25) |
| `save_strategy` | string | No | save\_strategy |
| `seq_length` | integer | No | (default: 1024) (min: 1) |
| `use_lora` | boolean | No | Use LoRA |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_generation",
"training_params": {
"answer_column": "",
"batch_size": 1,
"enable_thinking": false,
"epochs": 1,
"grad_accum": 1,
"learning_rate": 0.0001,
"logging_steps": 10,
"lora_alpha": 16,
"lora_rank": 16,
"neftune_noise_alpha": 0,
"question_column": "",
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"seq_length": 1024,
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_generation",
"training_params": {{
"answer_column": "",
"batch_size": 1,
"enable_thinking": false,
"epochs": 1,
"grad_accum": 1,
"learning_rate": 0.0001,
"logging_steps": 10,
"lora_alpha": 16,
"lora_rank": 16,
"neftune_noise_alpha": 0,
"question_column": "",
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"seq_length": 1024,
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "text_generation", "training_params": { "answer_column": "", "batch_size": 1, "enable_thinking": false, "epochs": 1, "grad_accum": 1, "learning_rate": 0.0001, "logging_steps": 10, "lora_alpha": 16, "lora_rank": 16, "neftune_noise_alpha": 0, "question_column": "", "save_steps_ratio": 0.25, "save_strategy": "epoch", "seq_length": 1024, "use_lora": true } }'
```
## Field Details
### `answer_column`
**Assistant (Response) Column**
**Type:** `string`
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `enable_thinking`
**Type:** `boolean`
**Default:** `false`
### `epochs`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `grad_accum`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `learning_rate`
**Type:** `number`
**Default:** `0.0001`
**Minimum:** `0`
### `logging_steps`
**Type:** `integer`
**Default:** `10`
**Minimum:** `1`
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `neftune_noise_alpha`
**Type:** `number`
**Default:** `0`
**Minimum:** `0`
### `question_column`
**User (Prompt) Column**
**Type:** `string`
### `save_steps_ratio`
**Type:** `number`
**Default:** `0.25`
### `save_strategy`
**Type:** `string`
**Default:** `"epoch"`
### `seq_length`
**Type:** `integer`
**Default:** `1024`
**Minimum:** `1`
### `use_lora`
**Use LoRA**
**Type:** `boolean`
Enable LoRA for faster fine-tuning and lower memory use
**Default:** `true`
# Fine-Tune: Text To Video
Source: https://docs.oxen.ai/fine-tuning-api/reference/text_to_video
Fine-tune a model to generate video from text
## Overview
This schema is used for fine-tuning models with **text to video** capabilities.
### Schema Type
When creating a fine-tune with this schema, use:
```json theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_to_video",
"training_params": {
...
}
}
```
**Key Parameters:**
* `script_type`: `text_to_video` (the fine-tune type)
* `base_model`: One of the supported model canonical names below
### Supported Models
* Wan2.1 1.3B - Text to Video (`Wan-AI/Wan2.1-T2V-1.3B-Diffusers`)
* Wan2.2 A14B - Text to Video (`Wan-AI/Wan2.2-T2V-A14B-Diffusers`)
* Wan2.1 14B - Text to Video (`Wan-AI/Wan2.1-T2V-14B-Diffusers`)
## Request Schema
### Required Fields
| Field | Type | Required | Description |
| ----------------------- | ------- | -------- | --------------------------------------- |
| `batch_size` | integer | No | (default: 1) (min: 1) |
| `caption_column` | string | Yes | caption\_column (DataFrame column name) |
| `gradient_accumulation` | integer | No | (default: 1) (min: 1) |
| `image_column` | string | Yes | image\_column (DataFrame column name) |
| `learning_rate` | number | No | (default: 0.0002) |
| `lora_alpha` | integer | No | (default: 16) (min: 1) |
| `lora_rank` | integer | No | (default: 16) (min: 1) |
| `sample_every` | integer | No | (default: 200) (min: 1) |
| `samples` | array | No | Samples (array of object) |
| `steps` | integer | No | (default: 3000) (min: 1) |
| `timestep_type` | string | No | (options: weighted, linear, sigmoid) |
| `use_lora` | boolean | No | use\_lora |
## Example Request
```json Request Body theme={null}
{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_to_video",
"training_params": {
"batch_size": 1,
"caption_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"samples": [
{
"prompt": "an ox holding a sign that says 'Oxen.ai'"
},
{
"prompt": "a herd of oxen running in a field"
}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}
}
```
```python Python theme={null}
import requests
url = "https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {{
"resource": "main/your-dataset.parquet",
"base_model": "",
"script_type": "text_to_video",
"training_params": {{
"batch_size": 1,
"caption_column": "",
"gradient_accumulation": 1,
"image_column": "",
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"samples": [
{{
"prompt": "an ox holding a sign that says 'Oxen.ai'"
}},
{{
"prompt": "a herd of oxen running in a field"
}}
],
"steps": 3000,
"timestep_type": "weighted",
"use_lora": true
}}
}}
response = requests.post(url, headers=headers, json=data)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/fine_tunes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "resource": "main/your-dataset.parquet", "base_model": "", "script_type": "text_to_video", "training_params": { "batch_size": 1, "caption_column": "", "gradient_accumulation": 1, "image_column": "", "learning_rate": 0.0002, "lora_alpha": 16, "lora_rank": 16, "sample_every": 200, "samples": [ { "prompt": "an ox holding a sign that says 'Oxen.ai'" }, { "prompt": "a herd of oxen running in a field" } ], "steps": 3000, "timestep_type": "weighted", "use_lora": true } }'
```
## Field Details
### `batch_size`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `caption_column`
**Type:** `string`
### `gradient_accumulation`
**Type:** `integer`
**Default:** `1`
**Minimum:** `1`
### `image_column`
**Type:** `string`
### `learning_rate`
**Type:** `number`
**Default:** `0.0002`
### `lora_alpha`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `lora_rank`
**Type:** `integer`
**Default:** `16`
**Minimum:** `1`
### `sample_every`
**Type:** `integer`
**Default:** `200`
**Minimum:** `1`
### `samples`
**Samples**
**Type:** `array`
Used to show progress during the fine-tuning process
**Default:** `[{"prompt": "an ox holding a sign that says 'Oxen.ai'"}, {"prompt": "a herd of oxen running in a field"}]`
### `steps`
**Type:** `integer`
**Default:** `3000`
**Minimum:** `1`
### `timestep_type`
**Type:** `string`
**Default:** `"weighted"`
**Options:** `weighted`, `linear`, `sigmoid`
### `use_lora`
**Type:** `boolean`
# Retrieve model
Source: https://docs.oxen.ai/fine-tuning-api/retrieve-model
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/models/{id}
Retrieves a model by ID or name. OpenAI-compatible.
# Search models
Source: https://docs.oxen.ai/fine-tuning-api/search-models
https://hub.oxen.ai/api/_spec/oxen_hub_api.json get /api/ai/models/search
# Fine-Tuning text generation model
Source: https://docs.oxen.ai/fine-tuning-api/tutorials/01_fine_tuning
Example of how to fine-tune text generation models on Oxen.ai using the HTTP API and `curl`.
## End-to-End Fine-Tuning of a Text Generation Model
This page walks through a complete fine-tuning run using the HTTP API and `curl`, mirroring the minimal Python example you provided.
You will:
* **Create** a fine-tune
* **Start** the fine-tune run
* **Monitor** the fine-tune until it completes
All calls are made against a specific repository, similar to this Python snippet:
* `NAMESPACE = "Tutorials"`
* `REPO = "FinancialSentiment"`
***
## Prerequisites
* **Repository** on Oxen with your training data committed, for example:
* Namespace: `Tutorials`
* Repository: `FinancialSentiment`
* **Dataset resource** inside that repo, for example:
* `main/train_financial_sentiment.parquet`
* You can view this example dataset (including columns like `text` and `sentiment`) and its repository here: [Tutorials / FinancialSentiment dataset](https://www.oxen.ai/Tutorials/FinancialSentiment/file/main/train_financial_sentiment.parquet)
* **API key** with access to the repo:
* Exported as `OXEN_API_KEY`
* **Base URL** for the Oxen API:
* Local dev example: `https://hub.oxen.ai`
* Exported as `OXEN_BASE_URL` (optional, defaults shown below)
You can set these in your shell:
```bash theme={null}
export OXEN_API_KEY="YOUR_API_KEY_HERE"
export OXEN_BASE_URL="https://hub.oxen.ai"
export OXEN_NAMESPACE="Tutorials"
export OXEN_REPO="FinancialSentiment"
```
For the examples below, we will use:
* `resource`: `main/train_financial_sentiment.parquet`
* `base_model`: `Qwen/Qwen3-0.6B`
* `script_type`: `text_generation`
Training parameters mirror the Python example:
* `question_column`: `text`
* `answer_column`: `sentiment`
* `epochs`: `1`
* `batch_size`: `1`
* `learning_rate`: `0.0001`
* `grad_accum`: `1`
* `lora_alpha`: `16`
* `lora_rank`: `16`
* `seq_length`: `1024`
* `logging_steps`: `10`
* `enable_thinking`: `false`
* `neftune_noise_alpha`: `0.0`
* `save_steps_ratio`: `0.25`
* `save_strategy`: `epoch`
* `use_lora`: `true`
***
## Step 1 β Create a Fine-Tune
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes`
**Example `curl` request** (mirrors the Python payload):
```bash theme={null}
curl --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-FinancialSentiment}/fine_tunes" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"resource": "main/train_financial_sentiment.parquet",
"base_model": "Qwen/Qwen3-0.6B",
"script_type": "text_generation",
"training_params": {
"question_column": "text",
"answer_column": "sentiment",
"epochs": 1,
"batch_size": 1,
"learning_rate": 0.0001,
"grad_accum": 1,
"lora_alpha": 16,
"lora_rank": 16,
"seq_length": 1024,
"logging_steps": 10,
"enable_thinking": false,
"neftune_noise_alpha": 0.0,
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"use_lora": true
}
}'
```
The response will include a `fine_tune` object. For example:
```json theme={null}
{
"fine_tune": {
"id": "ft_12345",
"status": "created",
"resource": "main/train_financial_sentiment.parquet",
"base_model": "Qwen/Qwen3-0.6B",
"script_type": "text_generation",
"training_params": { ... }
}
}
```
Save the `id` (for example `ft_12345`) for the next steps.
If you have `jq` installed, you can capture it directly:
```bash theme={null}
FT_ID=$(curl --silent --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-FinancialSentiment}/fine_tunes" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"resource": "main/train_financial_sentiment.parquet",
"base_model": "Qwen/Qwen3-0.6B",
"script_type": "text_generation",
"training_params": {
"question_column": "text",
"answer_column": "sentiment",
"epochs": 1,
"batch_size": 1,
"learning_rate": 0.0001,
"grad_accum": 1,
"lora_alpha": 16,
"lora_rank": 16,
"seq_length": 1024,
"logging_steps": 10,
"enable_thinking": false,
"neftune_noise_alpha": 0.0,
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"use_lora": true
}
}' | jq -r '.fine_tune.id')
echo "Created fine-tune: $FT_ID"
```
***
## Step 2 β Start the Fine-Tune Run
Once you have a `fine_tune.id`, trigger the run.
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}/actions/run`
**Example `curl` request**:
```bash theme={null}
curl --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-FinancialSentiment}/fine_tunes/${FT_ID}/actions/run" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-X POST
```
This mirrors the Python example:
```python theme={null}
run_url = f"{BASE_URL}/api/repos/{NAMESPACE}/{REPO}/fine_tunes/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
```
***
## Step 3 β Monitor Fine-Tune Status
You can poll the fine-tune to see when it completes.
**Endpoint**
* `GET /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}`
**Example `curl` loop** (bash):
```bash theme={null}
while true; do
RESP=$(curl --silent "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-FinancialSentiment}/fine_tunes/${FT_ID}" \
-H "Authorization: Bearer ${OXEN_API_KEY}")
echo "$RESP" | jq '.'
STATUS=$(echo "$RESP" | jq -r '.fine_tune.status')
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ]; then
OUTPUT_RESOURCE=$(echo "$RESP" | jq -r '.fine_tune.output_resource')
echo "Fine-tune completed! Output: $OUTPUT_RESOURCE"
break
elif [ "$STATUS" = "errored" ]; then
ERROR_MSG=$(echo "$RESP" | jq -r '.fine_tune.error')
echo "Fine-tune failed: $ERROR_MSG"
exit 1
elif [ "$STATUS" = "stopped" ]; then
echo "Fine-tune was stopped"
break
fi
# Wait 30 seconds before checking again (matches Python example)
sleep 30
done
```
This shell loop is the `curl` equivalent of the Python monitoring loop:
```python theme={null}
status_url = f"{BASE_URL}/api/repos/{NAMESPACE}/{REPO}/fine_tunes/{fine_tune_id}"
while True:
response = requests.get(status_url, headers=headers)
fine_tune = response.json()["fine_tune"]
status = fine_tune["status"]
...
time.sleep(30)
```
***
With these three steps, you have a complete end-to-end fine-tuning run using only `curl`, matching the minimal Python example but fully scriptable from the command line.
# Fine-Tuning an Image Editing Model with API
Source: https://docs.oxen.ai/fine-tuning-api/tutorials/02_fine_tuning_image
This page shows how to fine-tune an **image editing** model on Oxen.ai using **only the HTTP API and `curl`**.
With this guide, you will:
* **Create** an image-editing fine-tune
* **Start** the fine-tune run
* **Monitor** the fine-tune until it completes
* **Deploy** the fine-tuned model
* **Run inference** with the deployed model
We will use one of the Qwen image-editing models described in
[`Available Fine-Tuning Models`](/fine-tuning-api/overview):
* `base_model`: `Qwen/Qwen-Image-Edit`
* `script_type`: `image_editing`
Your dataset should follow the schema described there:
* `control_image_column` β Input/reference image to edit
* `caption_column` β Text prompt describing the desired edit
* `image_column` β Target/output image after the edit
***
## Prerequisites
* **Repository** on Oxen with your training data committed, for example:
* Namespace: `Tutorials`
* Repository: `ProductImageEdits`
* **Dataset resource** inside that repo, for example:
* `main/train_image_edits.parquet`
* Each row contains paths to the control image and edited image, plus a caption.
* **API key** with access to the repo:
* Exported as `OXEN_API_KEY`
* **Base URL** for the Oxen API:
* Cloud example: `https://hub.oxen.ai`
* Exported as `OXEN_BASE_URL` (optional, defaults shown below)
You can set these in your shell:
```bash theme={null}
export OXEN_API_KEY="YOUR_API_KEY_HERE"
export OXEN_BASE_URL="https://hub.oxen.ai"
export OXEN_NAMESPACE="Tutorials"
export OXEN_REPO="ProductImageEdits"
```
For the examples below, we will use:
* `resource`: `main/train_image_edits.parquet`
* `base_model`: `Qwen/Qwen-Image-Edit`
* `script_type`: `image_editing`
Training parameters (you can adjust these to your needs):
* `control_image_column`: `control_image`
* `caption_column`: `caption`
* `image_column`: `edited_image`
* `epochs`: `1`
* `batch_size`: `1`
* `learning_rate`: `0.0001`
* `grad_accum`: `1`
* `lora_alpha`: `16`
* `lora_rank`: `16`
* `seq_length`: `1024`
* `logging_steps`: `10`
* `enable_thinking`: `false`
* `neftune_noise_alpha`: `0.0`
* `save_steps_ratio`: `0.25`
* `save_strategy`: `epoch`
* `use_lora`: `true`
***
## Step 1 β Create an Image Editing Fine-Tune
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes`
**Example `curl` request**:
```bash theme={null}
curl --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-ProductImageEdits}/fine_tunes" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"resource": "main/train_image_edits.parquet",
"base_model": "Qwen/Qwen-Image-Edit",
"script_type": "image_editing",
"training_params": {
"control_image_column": "control_image",
"caption_column": "caption",
"image_column": "edited_image",
"epochs": 1,
"batch_size": 1,
"learning_rate": 0.0001,
"grad_accum": 1,
"lora_alpha": 16,
"lora_rank": 16,
"seq_length": 1024,
"logging_steps": 10,
"enable_thinking": false,
"neftune_noise_alpha": 0.0,
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"use_lora": true
}
}'
```
The response will include a `fine_tune` object. For example:
```json theme={null}
{
"fine_tune": {
"id": "ft_img_12345",
"status": "created",
"resource": "main/train_image_edits.parquet",
"base_model": "Qwen/Qwen-Image-Edit",
"script_type": "image_editing",
"training_params": { ... }
}
}
```
Save the `id` (for example `ft_img_12345`) for the next steps.
If you have `jq` installed, you can capture it directly:
```bash theme={null}
FT_ID=$(curl --silent --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-ProductImageEdits}/fine_tunes" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"resource": "main/train_image_edits.parquet",
"base_model": "Qwen/Qwen-Image-Edit",
"script_type": "image_editing",
"training_params": {
"control_image_column": "control_image",
"caption_column": "caption",
"image_column": "edited_image",
"epochs": 1,
"batch_size": 1,
"learning_rate": 0.0001,
"grad_accum": 1,
"lora_alpha": 16,
"lora_rank": 16,
"seq_length": 1024,
"logging_steps": 10,
"enable_thinking": false,
"neftune_noise_alpha": 0.0,
"save_steps_ratio": 0.25,
"save_strategy": "epoch",
"use_lora": true
}
}' | jq -r '.fine_tune.id')
echo "Created image-edit fine-tune: $FT_ID"
```
***
## Step 2 β Start the Fine-Tune Run
Once you have a `fine_tune.id`, trigger the run.
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}/actions/run`
**Example `curl` request**:
```bash theme={null}
curl --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-ProductImageEdits}/fine_tunes/${FT_ID}/actions/run" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-X POST
```
***
## Step 3 β Monitor Fine-Tune Status
You can poll the fine-tune to see when it completes.
**Endpoint**
* `GET /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}`
**Example `curl` loop** (bash):
```bash theme={null}
while true; do
RESP=$(curl --silent "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-ProductImageEdits}/fine_tunes/${FT_ID}" \
-H "Authorization: Bearer ${OXEN_API_KEY}")
echo "$RESP" | jq '.'
STATUS=$(echo "$RESP" | jq -r '.fine_tune.status')
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ]; then
OUTPUT_RESOURCE=$(echo "$RESP" | jq -r '.fine_tune.output_resource')
echo "Fine-tune completed! Output: $OUTPUT_RESOURCE"
break
elif [ "$STATUS" = "errored" ]; then
ERROR_MSG=$(echo "$RESP" | jq -r '.fine_tune.error')
echo "Fine-tune failed: $ERROR_MSG"
exit 1
elif [ "$STATUS" = "stopped" ]; then
echo "Fine-tune was stopped"
break
fi
# Wait 30 seconds before checking again
sleep 30
done
```
***
## Step 4 β Deploy the Fine-Tuned Image Model
Once the fine-tune completes, you can deploy it to a dedicated GPU-backed endpoint via the deploy API.
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}/deploy`
**Example `curl` request**:
```bash theme={null}
DEPLOY_RESPONSE=$(curl --silent --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-ProductImageEdits}/fine_tunes/${FT_ID}/deploy" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-X POST)
echo "$DEPLOY_RESPONSE" | jq '.'
```
The response will include information about the deployment, including the **model identifier** you can pass to the image editing inference API (for example, a slug such as `oxen:your-fine-tuned-image-edit-model`).
If the response contains a field like `model_slug`, you can capture it with `jq`:
```bash theme={null}
DEPLOYED_MODEL=$(echo "$DEPLOY_RESPONSE" | jq -r '.deployment.model_slug')
echo "Deployed model: $DEPLOYED_MODEL"
```
***
## Step 5 β Run Inference with the Deployed Model
With the deployment live, you can call the **image editing** inference endpoint using the deployed model identifier.
**Endpoint**
* `POST /api/ai/images/edit`
**Example `curl` request** (single input image):
```bash theme={null}
export DEPLOYED_MODEL="${DEPLOYED_MODEL:-oxen:your-fine-tuned-image-edit-model}"
curl -X POST \
"${OXEN_BASE_URL:-https://hub.oxen.ai}/api/ai/images/edit" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-d "{
\"model\": \"${DEPLOYED_MODEL}\",
\"input_image\": \"https://example.com/image.png\",
\"prompt\": \"Apply the same style as in my training data\",
\"num_inference_steps\": 28
}"
```
For models that support **multiple input images** (for example when using a multi-image editing base model), you can pass an array of image URLs:
```bash theme={null}
curl -X POST \
"${OXEN_BASE_URL:-https://hub.oxen.ai}/api/ai/images/edit" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-d "{
\"model\": \"${DEPLOYED_MODEL}\",
\"input_image\": [
\"https://example.com/control_image.png\",
\"https://example.com/style_reference.png\"
],
\"prompt\": \"Apply the reference style to the control image, matching the fine-tuned behavior\",
\"num_inference_steps\": 28
}"
```
These requests mirror the general image editing examples, but use your **fine-tuned image model** as the `model` value instead of a base model.
For more background on the image editing inference API, see the
[`Image Editing` examples](/examples/inference/image_editing).
With these five steps, you have a complete end-to-end image fine-tuning, deployment, and inference workflow using only `curl`, fully scriptable from the command line.
# Fine-Tuning an Image Generation Model with API
Source: https://docs.oxen.ai/fine-tuning-api/tutorials/03_fine_tuning_image_generation
Complete end-to-end guide to fine-tune an image generation model on Oxen.ai using the HTTP API and `curl`.
## Overview
This guide walks you through fine-tuning an image generation model to create images in your custom style. You'll learn how to:
* **Create** an image generation fine-tune
* **Start** the fine-tune run
* **Monitor** training progress with sample outputs
* **Deploy** the fine-tuned model
* **Run inference** to generate images in your style
We'll use one of the FLUX models, which are state-of-the-art for image generation:
* `base_model`: `black-forest-labs/FLUX.1-dev`
* `script_type`: `image_generation`
Your dataset should have two columns:
* `image_column` β Training images showing your desired style
* `caption_column` β Text descriptions of each image
For a quick minimal example, see the [Image Generation Quick Start](/fine-tuning-api/quickstart/image-generation).
***
## Prerequisites
* **Repository** on Oxen with your training data committed, for example:
* Namespace: `Tutorials`
* Repository: `CyberpunkArt`
* **Dataset resource** inside that repo, for example:
* `main/train_images.parquet`
* Each row contains an image path and descriptive caption
* Example: [view sample dataset structure](/fine-tuning-api/quickstart/image-generation#your-data)
* **API key** with access to the repo:
* Exported as `OXEN_API_KEY`
* **Base URL** for the Oxen API:
* Cloud: `https://hub.oxen.ai`
* Exported as `OXEN_BASE_URL`
Set these in your shell:
```bash theme={null}
export OXEN_API_KEY="YOUR_API_KEY_HERE"
export OXEN_BASE_URL="https://hub.oxen.ai"
export OXEN_NAMESPACE="Tutorials"
export OXEN_REPO="CyberpunkArt"
```
## Data Requirements
For best results with image generation fine-tuning:
* **Quantity**: 10-50 images minimum, 100-500 images ideal
* **Quality**: High resolution (1024x1024 or higher), consistent style
* **Captions**: Descriptive prompts that explain what makes your images unique
* **Consistency**: Images should share common elements (style, subject matter, theme)
Example dataset structure in `train_images.parquet`:
| image | caption |
| -------------- | ---------------------------------------------------- |
| images/001.jpg | a red sports car in cyberpunk style with neon lights |
| images/002.jpg | a cyberpunk city street at night with rain |
| images/003.jpg | a person wearing futuristic cyberpunk clothing |
See the [Parameter Guide](/fine-tuning-api/parameters#training-duration) to understand training duration and the [Data Requirements section](/fine-tuning-api/quickstart/image-generation#data-requirements) for detailed guidelines.
***
## Step 1 β Create an Image Generation Fine-Tune
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes`
For this example, we'll use:
* `resource`: `main/train_images.parquet`
* `base_model`: `black-forest-labs/FLUX.1-dev`
* `script_type`: `image_generation`
Training parameters:
* `image_column`: `image` (your image column name)
* `caption_column`: `caption` (your caption column name)
* `steps`: `2000` (standard training duration)
* `learning_rate`: `0.0002` (default for image models)
* `lora_rank`: `16` (balanced capacity)
* `sample_every`: `200` (generate samples every 200 steps to monitor progress)
**Example `curl` request**:
```bash theme={null}
curl --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-CyberpunkArt}/fine_tunes" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"resource": "main/train_images.parquet",
"base_model": "black-forest-labs/FLUX.1-dev",
"script_type": "image_generation",
"training_params": {
"image_column": "image",
"caption_column": "caption",
"steps": 2000,
"batch_size": 1,
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"samples": [
{
"prompt": "a sports car in cyberpunk style"
},
{
"prompt": "a futuristic city street at night"
}
],
"timestep_type": "sigmoid",
"use_lora": true
}
}'
```
The `samples` array allows you to specify test prompts that will be generated during training. This helps you monitor how well the model is learning your style.
The response will include a `fine_tune` object:
```json theme={null}
{
"fine_tune": {
"id": "ft_img_gen_12345",
"status": "created",
"resource": "main/train_images.parquet",
"base_model": "black-forest-labs/FLUX.1-dev",
"script_type": "image_generation",
"training_params": { ... }
}
}
```
**Capture the fine-tune ID** for the next steps:
```bash theme={null}
FT_ID=$(curl --silent --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-CyberpunkArt}/fine_tunes" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"resource": "main/train_images.parquet",
"base_model": "black-forest-labs/FLUX.1-dev",
"script_type": "image_generation",
"training_params": {
"image_column": "image",
"caption_column": "caption",
"steps": 2000,
"batch_size": 1,
"learning_rate": 0.0002,
"lora_alpha": 16,
"lora_rank": 16,
"sample_every": 200,
"samples": [
{
"prompt": "a sports car in cyberpunk style"
},
{
"prompt": "a futuristic city street at night"
}
],
"timestep_type": "sigmoid",
"use_lora": true
}
}' | jq -r '.fine_tune.id')
echo "Created fine-tune: $FT_ID"
```
***
## Step 2 β Start the Fine-Tune Run
Once you have the `fine_tune.id`, trigger the training run.
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}/actions/run`
**Example `curl` request**:
```bash theme={null}
curl --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-CyberpunkArt}/fine_tunes/${FT_ID}/actions/run" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-X POST
```
The fine-tune will now begin training. This typically takes 1-2 hours for 2000 steps on a GPU.
For FLUX models, expect approximately 30-60 minutes per 1000 steps, depending on GPU availability and image complexity.
***
## Step 3 β Monitor Fine-Tune Status and Sample Outputs
You can poll the fine-tune to check progress and view sample outputs generated during training.
**Endpoint**
* `GET /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}`
**Example monitoring script** (bash):
```bash theme={null}
while true; do
RESP=$(curl --silent "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-CyberpunkArt}/fine_tunes/${FT_ID}" \
-H "Authorization: Bearer ${OXEN_API_KEY}")
echo "$RESP" | jq '.'
STATUS=$(echo "$RESP" | jq -r '.fine_tune.status')
CURRENT_STEP=$(echo "$RESP" | jq -r '.fine_tune.current_step // 0')
echo "Status: $STATUS"
echo "Current Step: $CURRENT_STEP / 2000"
# Check for sample outputs (generated every 200 steps)
SAMPLES=$(echo "$RESP" | jq -r '.fine_tune.sample_outputs // empty')
if [ ! -z "$SAMPLES" ]; then
echo "Sample outputs available:"
echo "$SAMPLES" | jq -r '.[] | " - \(.url)"'
fi
if [ "$STATUS" = "completed" ]; then
OUTPUT_RESOURCE=$(echo "$RESP" | jq -r '.fine_tune.output_resource')
echo "Fine-tune completed! Output: $OUTPUT_RESOURCE"
break
elif [ "$STATUS" = "errored" ]; then
ERROR_MSG=$(echo "$RESP" | jq -r '.fine_tune.error')
echo "Fine-tune failed: $ERROR_MSG"
exit 1
elif [ "$STATUS" = "stopped" ]; then
echo "Fine-tune was stopped"
break
fi
# Wait 30 seconds before checking again
sleep 30
done
```
### Understanding Training Progress
As training progresses, you'll see:
* **Status updates**: `created` β `running` β `completed`
* **Current step**: Progress counter (e.g., 400/2000)
* **Sample outputs**: Generated images at steps 200, 400, 600, etc.
Review the sample outputs to see how well the model is learning your style. The images should progressively match your training style better as training continues.
If sample outputs aren't matching your style by step 1000, consider adjusting `learning_rate` or training for more steps. See the [Parameter Guide](/fine-tuning-api/parameters#learning-rate-and-optimization) for tuning advice.
***
## Step 4 β Deploy the Fine-Tuned Model
Once training completes, deploy your model to a GPU-backed inference endpoint.
**Endpoint**
* `POST /api/repos/{owner}/{repo}/fine_tunes/{fine_tune_id}/deploy`
**Example `curl` request**:
```bash theme={null}
DEPLOY_RESPONSE=$(curl --silent --location "${OXEN_BASE_URL:-https://hub.oxen.ai}/api/repos/${OXEN_NAMESPACE:-Tutorials}/${OXEN_REPO:-CyberpunkArt}/fine_tunes/${FT_ID}/deploy" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-X POST)
echo "$DEPLOY_RESPONSE" | jq '.'
```
The response will include deployment information with a **model identifier** you'll use for inference:
```json theme={null}
{
"deployment": {
"model_slug": "oxen:tutorials/cyberpunkart-ft_img_gen_12345",
"status": "deploying",
"endpoint": "https://hub.oxen.ai/api/ai/images/generate"
}
}
```
**Capture the model slug**:
```bash theme={null}
DEPLOYED_MODEL=$(echo "$DEPLOY_RESPONSE" | jq -r '.deployment.model_slug')
echo "Deployed model: $DEPLOYED_MODEL"
```
Deployment may take 2-5 minutes as the model is loaded onto a GPU instance. You can check deployment status by polling the fine-tune endpoint.
***
## Step 5 β Generate Images with Your Fine-Tuned Model
Now you can generate images in your custom style using the inference API.
**Endpoint**
* `POST /api/ai/images/generate`
**Example `curl` request** (text-to-image):
```bash theme={null}
curl -X POST \
"${OXEN_BASE_URL:-https://hub.oxen.ai}/api/ai/images/generate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-d "{
\"model\": \"${DEPLOYED_MODEL}\",
\"prompt\": \"a motorcycle racing through the city in cyberpunk style\",
\"num_inference_steps\": 28,
\"guidance_scale\": 7.5,
\"width\": 1024,
\"height\": 1024
}"
```
### Generate Multiple Images
You can generate multiple variations by setting `num_images`:
```bash theme={null}
curl -X POST \
"${OXEN_BASE_URL:-https://hub.oxen.ai}/api/ai/images/generate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${OXEN_API_KEY}" \
-d "{
\"model\": \"${DEPLOYED_MODEL}\",
\"prompt\": \"a futuristic building in cyberpunk style with neon signs\",
\"num_inference_steps\": 28,
\"guidance_scale\": 7.5,
\"num_images\": 4,
\"width\": 1024,
\"height\": 1024
}"
```
### Inference Parameters
| Parameter | Description | Typical Values |
| --------------------- | ---------------------------------- | ---------------------- |
| `prompt` | Text description of desired image | Any descriptive text |
| `num_inference_steps` | Quality vs speed (higher = better) | 20-50 (28 is balanced) |
| `guidance_scale` | How closely to follow prompt | 5-10 (7.5 is balanced) |
| `width` / `height` | Output resolution | 512, 768, 1024 |
| `num_images` | Number of variations to generate | 1-4 |
| `seed` | Random seed for reproducibility | Any integer |
Use higher `num_inference_steps` (40-50) for final production images, and lower values (20-28) for quick iterations during testing.
### Example Response
```json theme={null}
{
"images": [
{
"url": "https://hub.oxen.ai/api/files/...",
"width": 1024,
"height": 1024
}
],
"parameters": {
"model": "oxen:tutorials/cyberpunkart-ft_img_gen_12345",
"prompt": "a motorcycle racing through the city in cyberpunk style",
"num_inference_steps": 28,
"guidance_scale": 7.5
}
}
```
***
## Complete Python Example
Here's a complete Python script that ties everything together:
```python theme={null}
import requests
import time
BASE_URL = "https://hub.oxen.ai"
API_KEY = "YOUR_API_KEY"
NAMESPACE = "Tutorials"
REPO = "CyberpunkArt"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
# Step 1: Create fine-tune
print("Creating fine-tune...")
create_url = f"{BASE_URL}/api/repos/{NAMESPACE}/{REPO}/fine_tunes"
data = {
"resource": "main/train_images.parquet",
"base_model": "black-forest-labs/FLUX.1-dev",
"script_type": "image_generation",
"training_params": {
"image_column": "image",
"caption_column": "caption",
"steps": 2000,
"batch_size": 1,
"learning_rate": 0.0002,
"lora_rank": 16,
"lora_alpha": 16,
"sample_every": 200,
"samples": [
{"prompt": "a sports car in cyberpunk style"},
{"prompt": "a futuristic city street at night"}
],
"timestep_type": "sigmoid",
"use_lora": True
}
}
response = requests.post(create_url, headers=headers, json=data)
fine_tune_id = response.json()["fine_tune"]["id"]
print(f"Created fine-tune: {fine_tune_id}")
# Step 2: Start training
print("Starting training...")
run_url = f"{create_url}/{fine_tune_id}/actions/run"
requests.post(run_url, headers=headers)
# Step 3: Monitor progress
print("Monitoring progress...")
status_url = f"{create_url}/{fine_tune_id}"
while True:
response = requests.get(status_url, headers=headers)
fine_tune = response.json()["fine_tune"]
status = fine_tune["status"]
current_step = fine_tune.get("current_step", 0)
print(f"Status: {status}, Step: {current_step}/2000")
if status == "completed":
print(f"Training completed! Output: {fine_tune['output_resource']}")
break
elif status == "errored":
print(f"Training failed: {fine_tune.get('error')}")
exit(1)
time.sleep(30)
# Step 4: Deploy model
print("Deploying model...")
deploy_url = f"{status_url}/deploy"
response = requests.post(deploy_url, headers=headers)
deployed_model = response.json()["deployment"]["model_slug"]
print(f"Deployed: {deployed_model}")
# Wait for deployment
time.sleep(60)
# Step 5: Generate image
print("Generating image...")
generate_url = f"{BASE_URL}/api/ai/images/generate"
gen_data = {
"model": deployed_model,
"prompt": "a motorcycle racing through the city in cyberpunk style",
"num_inference_steps": 28,
"guidance_scale": 7.5,
"width": 1024,
"height": 1024
}
response = requests.post(generate_url, headers=headers, json=gen_data)
image_url = response.json()["images"][0]["url"]
print(f"Generated image: {image_url}")
```
***
## Troubleshooting
Ensure image paths in your parquet file are relative to your repository root, or use full URLs. Verify that all images are committed to your Oxen repository with `oxen status`.
Reduce `batch_size` to 1 (default). If still failing, try reducing `lora_rank` to 8. See the [Batch Size guide](/fine-tuning-api/parameters#batch-size-and-memory) for more memory optimization tips.
* Train for more steps (3000-5000 instead of 2000)
* Ensure captions clearly describe the unique aspects of your style
* Increase dataset size (100+ images recommended)
* Try adjusting `learning_rate` (see [Learning Rate guide](/fine-tuning-api/parameters#learning-rate-and-optimization))
* FLUX.1-dev takes \~1-2 hours for 2000 steps on GPU
* Start with 1000 steps for quick testing
* Consider using a faster model like `Qwen/Qwen-Image` for iteration
* See [supported models](/fine-tuning-api/reference/image_generation#supported-models)
* Ensure training images are high resolution and consistent quality
* Increase `num_inference_steps` to 40-50 during generation
* Try different `guidance_scale` values (7.0-9.0)
* Train for more steps to improve model quality
***
## Next Steps
* **Advanced Parameters**: See the [Image Generation API Reference](/fine-tuning-api/reference/image_generation) for all available parameters
* **Parameter Tuning**: Learn about [LoRA configuration](/fine-tuning-api/parameters#lora-low-rank-adaptation) and [learning rate optimization](/fine-tuning-api/parameters#learning-rate-and-optimization)
* **More Examples**: Check out the [Quick Start guide](/fine-tuning-api/quickstart/image-generation) for simplified examples
* **Other Modalities**: Explore [Image Editing](/fine-tuning-api/quickstart/image-editing) or [Video Generation](/fine-tuning-api/quickstart/video)
***
With these skills, you can now fine-tune image generation models for any visual style, brand identity, or artistic direction!
# Unfavorite a model
Source: https://docs.oxen.ai/fine-tuning-api/unfavorite-a-model
https://hub.oxen.ai/api/_spec/oxen_hub_api.json delete /api/ai/models/{id}/favorite
# Update model
Source: https://docs.oxen.ai/fine-tuning-api/update-model
https://hub.oxen.ai/api/_spec/oxen_hub_api.json put /api/ai/models/{id}
# πΏ Branches & Merging
Source: https://docs.oxen.ai/getting-started/command-line/branches
Create branches, switch between them, and merge work together.
Branches let you take a snapshot of your data, experiment freely, and merge the results back without affecting the original. The commands map closely to git.
## Create a Branch
Create a new branch and check it out in one step with `oxen checkout -b`.
```bash theme={null}
oxen checkout -b feature
```
## List Branches
To list the branches in your repository (highlighting the one you're on), use `oxen branch`.
```bash theme={null}
oxen branch
```
To see branches that exist on the remotes, add `--remote`.
```bash theme={null}
oxen branch --remote
```
To delete a branch, use `oxen branch --delete`. This fails if the branch has changes that haven't been merged.
```bash theme={null}
oxen branch --delete feature
```
Use `-D` to force-delete a branch.
```bash theme={null}
oxen branch -D feature
```
## Switch Between Branches
Use `oxen checkout` to switch branches. This restores the working directory to the HEAD commit of the branch you're checking out.
```bash theme={null}
oxen checkout main
```
You can also check out a specific commit.
```bash theme={null}
oxen checkout COMMIT_ID
```
## Merge Branches
Merge another branch into your current branch with `oxen merge`. This creates a merge commit, or fails if there are conflicts to resolve.
```bash theme={null}
oxen merge TARGET_BRANCH
```
If you're collaborating, you may instead want to open a merge request through the [Oxen.ai web UI](https://oxen.ai).
# π§ Debugging & Performance
Source: https://docs.oxen.ai/getting-started/command-line/debugging
Diagnose repository state and tune Oxen for your hardware and network.
## Oxen Tree
You can use `oxen tree` to view the current state of the merkle tree for a commit. This gives you a view of the metadata contained in the tree for each file and directory, as well as how many [VNodes](https://ghost.oxen.ai/merkle-tree-vnodes/) are present.
```bash theme={null}
oxen tree
```
Output:
```
[Commit] 74bb3ece2ccfa3f23420ef5c0be84865 (0.36.0) "Add Data" -> Daisy daisy@oxen.ai parent_ids ""
[Dir] 65d46b6d56 "" (3.5 MB) (4 files) (commit 74bb3ece2c) (3 entries)
[VNode] 4f677b731c (3 entries)
[File] d29aed6e8d "README.md" (text/markdown) 28 B [d29aed6e8d] (commit 74bb3ece2c) MetadataText(2 lines, 28 chars)
[Dir] 5be82041ff "dir" (95.1 KB) (2 files) (commit 74bb3ece2c) (2 entries)
[VNode] dca3063656 (2 entries)
[File] 17caf6d759 "binary" (application/octet-stream) 8 B [17caf6d759] (commit 74bb3ece2c)
[File] b04c7235e3 "image.png" (image/png) 95.1 KB [b04c7235e3] (commit 74bb3ece2c) MetadataImage(1798x839)
[Dir] b8fc85925c "other" (3.4 MB) (1 files) (commit 74bb3ece2c) (1 entries)
[VNode] c36eb4516e (1 entries)
[File] 1fc24f94e0 "audio.mp3" (audio/mpeg) 3.4 MB [1fc24f94e0] (commit 74bb3ece2c) MetadataVideo(2x44100 143.491s)
Time to load tree: 4.8259ms
```
## Oxen Node
`oxen node` can be used to inspect file and dir nodes in the merkle tree. This can be used to check whether nodes exist and have been created properly in the merkle tree. You can search for a node by hash or by path.
```bash theme={null}
oxen node -n 65d46b6d5616364ba975320b5768c63c
```
Output:
```
[Dir] 65d46b6d56 "" (3.5 MB) (4 files) (commit 74bb3ece2c) (3 entries)
=============
hash: 65d46b6d5616364ba975320b5768c63c
node: Directory(DirNode(0.36.0)
hash: 65d46b6d5616364ba975320b5768c63c
name:
num_bytes: 3.5 MB
num_entries: 3
num_files: 4
data_type_counts: {"audio": 1, "text": 1, "binary": 1, "image": 1}
data_type_sizes: {"audio": 3443832, "binary": 8, "text": 28, "image": 95080}
)
parent_id: 74bb3ece2ccfa3f23420ef5c0be84865
children.len(): 1
=============
[VNode] 4f677b731c (3 entries)
```
```bash theme={null}
oxen node -p dir/image.png
```
Output:
```
[File] b04c7235e3 "image.png" (image/png) 95.1 KB [b04c7235e3] (commit 74bb3ece2c) MetadataImage(1798x839)
=============
hash: b04c7235e38d8e9b4eb1f802d0fe7e60
node: File(FileNode(0.36.0)
hash: b04c7235e38d8e9b4eb1f802d0fe7e60
name: image.png
num_bytes: 95.1 KB
data_type: Image
metadata: Some(MetadataImage(MetadataImage { image: MetadataImageImpl { width: 1798, height: 839, color_space: None } }))
mime_type: image/png
extension: png
chunk_hashes: [234341058283605499288244061100413189728]
chunk_type: SingleFile
storage_backend: Disk
last_commit_id: 74bb3ece2ccfa3f23420ef5c0be84865
last_modified_seconds: 1766177016
last_modified_nanoseconds: 995509200
metadata: Some(MetadataImage(MetadataImage { image: MetadataImageImpl { width: 1798, height: 839, color_space: None } }))
)
parent_id: dca3063656d08a4ecdd1644fb64f8de
children.len(): 0
```
## Concurrency
By default, oxen will use up to 8 threads for its parallelized operations (`oxen add`, `oxen push`, etc.). This can be configured via the `OXEN_NUM_THREADS` environment variable. If `OXEN_NUM_THREADS` is set, oxen will instead use that many threads, so long as they are available on the local machine.
```bash theme={null}
export OXEN_NUM_THREADS="16"
```
## HTTP Requests
When uploading or downloading data, if any files fail to transfer, oxen will wait and retry the request. By default, oxen will allow up to 5 retries before cancelling the operation. This can be configured via the enviornment variable `OXEN_NUM_RETRIES`
```bash theme={null}
export OXEN_NUM_RETRIES="10"
```
All HTTP requests oxen makes timeout after 120 seconds by default. You can configure this the OXEN\_TIMEOUT\_SECS variable.
```bash theme={null}
export OXEN_TIMEOUT_SECS="100"
```
## Chunk Size
Under the hood, oxen groups files into small files and large files for more efficient transfer in `oxen push`, `oxen workspace add`, etc. Files are considered large if they're larger than `AVG_CHUNK_SIZE`, which is set to 10 MB by default. This can be configured via the `AVG_CHUNK_SIZE` environment variable
```bash theme={null}
export AVG_CHUNK_SIZE="20_000_000"
```
## Debug Logs
The oxen codebase contains plenty of debug logs, which you can turn on with the RUST\_LOG variable.
```bash theme={null}
export RUST_LOG="debug"
```
You can also set RUST\_LOG to `info` or `warn` for more restrictive debug logs
# π§Ή Maintenance
Source: https://docs.oxen.ai/getting-started/command-line/maintenance
Reclaim disk space by removing unreferenced data from your repository.
The `oxen prune` command removes orphaned nodes and version files that are not referenced by any commit in your repository. This helps reclaim disk space by cleaning up unreferenced data that accumulates over time.
## Basic Usage
To prune your repository and remove orphaned files:
```bash theme={null}
oxen prune
```
This will scan your repository, identify unreferenced nodes and version files, and remove them to free up disk space.
In large repositories, the prune operation can take a while to run as it needs to scan all nodes and version files. Consider using `--dry-run` first to estimate the scope of the operation.
## Dry Run Mode
Before actually removing files, you can preview what would be deleted using the `--dry-run` flag:
```bash theme={null}
oxen prune --dry-run
```
Or using the short form:
```bash theme={null}
oxen prune -n
```
This is useful to see how much space would be freed without making any changes to your repository.
## Understanding Prune Statistics
After running `oxen prune`, you'll see detailed statistics about the operation:
Output:
```
Prune Statistics:
Nodes:
Scanned: 1250
Kept: 1100
Removed: 150
Version Files:
Scanned: 3400
Kept: 3200
Removed: 200
Disk Space Freed: 1.2 GB
```
* **Nodes**: Internal data structures that track file metadata
* **Scanned**: Total number of nodes examined
* **Kept**: Nodes still referenced by commits
* **Removed**: Orphaned nodes that were deleted
* **Version Files**: Actual file content stored in the repository
* **Scanned**: Total number of version files examined
* **Kept**: Files still referenced by commits
* **Removed**: Orphaned files that were deleted
* **Disk Space Freed**: Total amount of storage reclaimed
## When to Use Prune
You should consider running `oxen prune` when:
* You've deleted or modified many files across multiple commits
* You've rebased or reset your commit history
* You've removed large files from your repository
* Your `.oxen` directory is taking up more space than expected
* You want to optimize repository storage before sharing or archiving
## Safety
The prune operation only removes files that are not referenced by any commit in your repository. It will never delete:
* Files referenced by any commit
* Files in your working directory
* Staged files
* The `.oxen` directory structure itself
This makes it safe to run without worrying about losing committed data.
## Example Workflow
A typical workflow for pruning your repository:
```bash theme={null}
# First, check what would be removed
oxen prune --dry-run
# Review the statistics
# If everything looks good, run the actual prune
oxen prune
# Verify your repository is still intact
oxen status
oxen log
```
# βοΈ Setup & Authentication
Source: https://docs.oxen.ai/getting-started/command-line/setup
Configure your local Oxen identity and authenticate with a remote.
## Setup User
To use Oxen, you'll need to set up a local Oxen username and email. This is what will show up in `oxen log` or in the OxenHub dashboard for who changed what.
```bash theme={null}
oxen config --name "YOUR_NAME" --email "YOUR_EMAIL"
```
This will save the user to `~/.config/oxen/user_config.toml` for future use.
## Auth Tokens
Pushing data or cloning private repositories requires a valid API Key. You can obtain one by creating an account on and going to your profile.
The token can then be set with `oxen config --auth`.
```bash theme={null}
oxen config --auth 'hub.oxen.ai' YOUR_AUTH_TOKEN
```
This will write the auth token to `~/.config/oxen/auth_config.toml`.
To push or access repositories on [Oxen.ai](https://oxen.ai), set the host as `hub.oxen.ai`. If you set up your own [oxen-server](/getting-started/oxen-server), you can generate custom auth tokens there.
# π Start a Repository
Source: https://docs.oxen.ai/getting-started/command-line/start_repository
Create a new repository, clone an existing one, or download specific files.
Most Oxen workflows begin in one of three ways: initializing a fresh local repository, cloning an existing one from a remote, or pulling down specific files without setting up a full repo.
## Initialize a Local Repository
Create a new Oxen repository in the current directory with `oxen init`.
```bash theme={null}
oxen init
```
This creates a `.oxen/` directory in your working directory containing the repository metadata. As you add and commit files, each commit's [Merkle Tree](https://ghost.oxen.ai/merkle-tree-101/) is stored under `.oxen/`.
## Clone a Remote Repository
There are a few ways to clone an Oxen repository, depending on how much data you want to transfer. The default `oxen clone` with no flags downloads the *latest commit* from the `main` branch.
```bash theme={null}
oxen clone https://hub.oxen.ai/ox/CatDogBBox
```
This creates a new directory `CatDogBBox` containing the files from the latest commit, plus a `.oxen/` folder with the [Merkle Tree](https://ghost.oxen.ai/merkle-tree-101/) for the branch's history.
### Clone a Specific Branch
Use the `-b` flag to clone from a branch other than `main`.
```bash theme={null}
oxen clone https://hub.oxen.ai/ox/CatDogBBox -b my-pets
```
### Clone All Branches
To clone the commit history for every branch (useful when migrating a repo to a new remote), use `--all`.
```bash theme={null}
oxen clone https://hub.oxen.ai/ox/CatDogBBox --all
```
### Clone a Subtree
If you only need a subset of the repository, use `--filter` and `--depth` to limit the clone. `--filter` selects which directories to clone, while `--depth` limits how many levels of subdirectories are recursed into.
```bash theme={null}
oxen clone https://hub.oxen.ai/ox/CatDogBBox --filter annotations --depth 1
```
This clones only the subtree starting at the `annotations` directory, without recursing into any new subdirectories.
### Remote Mode
If the repository is larger than you can store locally, you can clone it in remote mode to download the commit Merkle trees without the file contents.
```bash theme={null}
oxen clone --remote https://hub.oxen.ai/ox/CatDogBBox
```
In a remote-mode repository, you can download individual files or directories on demand with `oxen restore`.
```bash theme={null}
oxen restore path/to/file
```
This is useful for inspecting the state of a repository without waiting for all its files to download.
## Configure a Remote
If you initialized a repository locally, you can point it at a remote with `oxen config --set-remote`. This is what enables `oxen push`, `oxen pull`, and `oxen fetch`.
```bash theme={null}
oxen config --set-remote origin https://hub.oxen.ai/ox/CatDogBBox
```
Specify a remote name (commonly `origin`) and the URL of the remote repository. Cloned repositories already have `origin` set automatically.
A repo can have multiple remotes β most commands default to `origin` if no remote is specified.
### Create a Remote from the CLI
If the remote repository doesn't exist yet, you can create it from the CLI with `oxen create-remote`.
```bash theme={null}
oxen create-remote --host hub.oxen.ai --scheme https --name ox/SampleRepo
```
You can also create remotes through the [Oxen.ai web UI](https://oxen.ai).
## Download Specific Files
If you only need specific files or directories β without cloning the whole repository β use `oxen download`.
```bash theme={null}
oxen download ox/CatDogBBox test.csv
```
To download from a specific branch or commit, pass `--revision`.
```bash theme={null}
oxen download ox/CatDogBBox path/to/folder --revision commit_or_branch_name
```
# π Sync with a Remote
Source: https://docs.oxen.ai/getting-started/command-line/sync_remote
Push, pull, and fetch changes between your local repository and a remote.
Once your repository has a remote configured (see [Start a Repository](/getting-started/command-line/start_repository#configure-a-remote)), you can push your work and pull collaborators' changes.
## Push Changes
Once you've committed changes locally, push them to a remote with `oxen push`.
```bash theme={null}
oxen push origin main
```
If you don't supply a remote name or branch, they default to `origin` and the current branch.
```bash theme={null}
oxen push
```
### Resume a Push
If a push is cancelled partway through, use `--missing-files` to resume and upload only the files the remote is missing.
```bash theme={null}
oxen push --missing-files
```
This is a fast repair. It uploads content the remote doesn't have, but it does not detect corrupted files. To find and fix corruption too, use `--revalidate`.
### Revalidate a Remote
`--revalidate` runs a full integrity check on the remote. It removes corrupted files from the remote and then pushes all missing files. This is the right option to use if you suspect there are any corrupted files on the remote.
```bash theme={null}
oxen push --revalidate
```
## Pull Changes
To pull the latest commits for a branch β downloading their files and Merkle trees, then checking out the latest commit β use `oxen pull`.
```bash theme={null}
oxen pull origin main
```
If no arguments are provided, the remote defaults to `origin` and the branch defaults to the current branch.
```bash theme={null}
oxen pull
```
As with `clone`, you can pull all branches with `--all`.
```bash theme={null}
oxen pull --all
```
## Fetch Changes
To fetch the latest changes without checking them out in the working directory, use `oxen fetch`.
```bash theme={null}
oxen fetch
```
This is useful when you want to inspect what's new on the remote before deciding whether to merge or check it out.
## View Configured Remotes
`oxen remote` lists the remotes configured for your repository.
```bash theme={null}
oxen remote
```
Output:
```
origin
```
Use `--verbose` to also see each remote's URL.
```bash theme={null}
oxen remote --verbose
```
Output:
```
origin https://hub.oxen.ai/ox/CatDogBBox
local_dev http://localhost:3000/ox/CatDogBBox
```
To add or change a remote's URL, see [Configure a Remote](/getting-started/command-line/start_repository#configure-a-remote) on the Start a Repository page.
# π Track Changes
Source: https://docs.oxen.ai/getting-started/command-line/track_changes
Stage, commit, inspect, and undo changes in your local repository.
The day-to-day Oxen workflow follows the same shape as git: stage what's changed, commit it, and inspect the history when you need to.
## Stage Files
Add files to a repository with `oxen add`. This copies the files' contents to the repository's version store and stages the changes for commit. You can use absolute paths or paths relative to the repo root.
```bash theme={null}
oxen add path/to/file.txt
```
```bash theme={null}
oxen add images/
```
You can also stage matching files with glob patterns and wildcards. This stages everything that matches the pattern and isn't excluded by `.oxenignore`.
```bash theme={null}
# Adds all paths starting with an 'f' in the images dir
oxen add images/f*
```
```bash theme={null}
# Adds everything in the current directory
oxen add .
```
`oxen add` handles new, modified, and removed files and directories.
Oxen lets you version any data type β text, images, audio, video, parquet, etc. β in the same repository, and you interact with all of them through the same commands. Under the hood, Oxen stores type-specific [file metadata](/concepts/file_metadata) to power richer features.
## View Status
To see what is tracked, staged, modified, removed, or not yet added, use `oxen status`.
```bash theme={null}
oxen status
```
Output:
```
On branch main -> e76dd52a4fc13a6f
Directories to be committed
added: images with added 8108 files
Files to be committed:
new file: images/000000000042.jpg
new file: images/000000000074.jpg
new file: images/000000000109.jpg
new file: images/000000000307.jpg
new file: images/000000000309.jpg
new file: images/000000000394.jpg
new file: images/000000000400.jpg
new file: images/000000000443.jpg
new file: images/000000000490.jpg
new file: images/000000000575.jpg
... and 8098 others
Untracked Directories
(use "oxen add ..." to update what will be committed)
annotations/ (3 items)
```
Because Oxen is built for large datasets with many files, `status` rolls up directory-level changes and summarizes them.
You can paginate through staged files with the `-s` (skip) and `-l` (limit) flags. Run `oxen status --help` for the full list.
## Commit Changes
Once changes are staged, commit them with a message.
```bash theme={null}
oxen commit -m "Some informative commit message"
```
This creates a new commit on the current branch. If the repository was previously empty, this also creates the `main` branch.
After a commit, a copy of each file's contents lives in the repository's version store (by default `.oxen/versions/files`). File and directory metadata are stored in the [Merkle Tree](https://ghost.oxen.ai/merkle-tree-101/), which mirrors the working directory structure.
## View History
Show the commit history of your current branch with `oxen log`.
```bash theme={null}
oxen log
```
Output:
```
commit 6b958e268656b0c5
Author: Ox
Date: Fri, 21 Oct 2022 16:08:39 -0700
adding 10,000 training images
commit e76dd52a4fc13a6f
Author: Ox
Date: Fri, 21 Oct 2022 16:05:22 -0700
Initialized Repo π
```
## View Diffs
Oxen can compute and display diffs between files using the [oxen diff](/concepts/diffs) command.
```bash theme={null}
oxen diff dataset.csv
```
This compares `dataset.csv` in the working directory with its version in the HEAD commit. You can also diff different files against each other, files across revisions, or whole revisions against each other. See the [diff concepts page](/concepts/diffs) for the full set of options.
## View File Contents
To print the raw bytes of a file as they exist at a revision, use `oxen cat`. The contents are written to stdout, so you can pipe them into another tool or redirect them to a file without restoring the file into your working directory.
```bash theme={null}
oxen cat README.md
```
Output:
```
# My Dataset
A collection of training images and their annotations.
```
By default `oxen cat` reads from `HEAD`. Pass `--revision` (or `-r`) to read the file as it existed at a specific branch or commit.
```bash theme={null}
oxen cat README.md --revision my-branch
```
`oxen cat` streams the exact stored bytes, so it works on any file type, including binary data. Pipe the output into another tool rather than printing binary straight to your terminal, which can garble it.
```bash theme={null}
oxen cat data/train.csv | head
```
To get a file's *metadata* (hash, size, data type) instead of its contents β pairing with `oxen cat` β use `oxen info`. Pass `--revision` (or `-r`) to describe the file as it existed at a specific branch or commit, and `--json` for machine-readable output. Without `--revision`, `oxen info` describes the file in your working tree.
```bash theme={null}
oxen info data/train.csv --revision my-branch --json
```
Output:
```json theme={null}
{"filename": "train.csv", "last_updated": {"id": "a1b2c3d4e5f6a7b8", "parent_ids": ["9f8e7d6c5b4a3210"], "message": "Add training data", "author": "Bloxy", "email": "bloxy@oxen.ai", "timestamp": "2026-06-23T18:09:25.412889Z"}, "hash": "5d41402abc4b2a76b9719d911017c592", "size": 40960, "data_type": "tabular", "mime_type": "text/csv", "extension": "csv"}
```
## Restore Files
To revert changes you've made to a file in the working directory, use `oxen restore`. This restores the file to its version in the HEAD commit, and works on both modified and deleted files.
```bash theme={null}
oxen restore path/to/file.txt
```
You can also restore directories β `oxen restore` will recursively restore the files inside.
To restore from a specific commit or branch, pass `--source`.
```bash theme={null}
oxen restore path/to/file.txt --source COMMIT_ID
```
Like git, you can also unstage files (without changing the working directory) using `--staged`.
```bash theme={null}
oxen restore --staged path/to/dir
```
## Remove Files
To stage a file to be removed from the next commit, use `oxen rm`.
```bash theme={null}
oxen rm path/to/file.txt
```
The file must already be committed for this to work. If you want to remove a file that has not been committed yet, just use your shell's `rm` command.
To recursively remove a directory, use the `-r` flag.
```bash theme={null}
oxen rm -r path/to/dir
```
You can also remove entries from the staging area only β without deleting the file from the working directory β using `--staged`.
```bash theme={null}
oxen rm --staged -r path/to/dir
```
# ποΈ Workspaces
Source: https://docs.oxen.ai/getting-started/command-line/workspaces
Stage and commit changes directly against a remote without a full local clone.
A workspace lets you stage changes against a remote branch without first copying its files to a local repository. This makes it ideal for bulk imports, automation, and any case where you don't need a local working copy.
For the conceptual overview, see [Workspaces](/examples/data/workspaces). For the Python interface, see [`python-api/workspace`](/python-api/workspace).
## Create a Workspace
Create a workspace on the current branch with `oxen workspace create`.
```bash theme={null}
oxen workspace create
```
This returns a workspace ID you'll use for subsequent commands.
## Stage Files in a Workspace
Add files to the workspace with `oxen workspace add`. The file contents are uploaded directly to the remote and staged for commit.
```bash theme={null}
oxen workspace add images --workspace-id 117abd2d-3363-497d-ac93-a5cb3c280234
```
## Commit a Workspace
Once your changes are staged, commit them with `oxen workspace commit`.
```bash theme={null}
oxen workspace commit -m "Uploading Images" --workspace-id 117abd2d-3363-497d-ac93-a5cb3c280234
```
The commit lands on the remote branch directly β no local push step required.
# Repositories on Oxen.ai
Source: https://docs.oxen.ai/getting-started/data
Oxen.ai allows you to version and store your data in repositories. Think of it like git for large data.
When using models on Oxen.ai, by default we store the model inputs, outputs, and metadata in a repository. Every piece of data is versioned so you can trace the provenance of your data and models.
In order to version data at scale, we built an [open source version control system](https://github.com/Oxen-AI/Oxen) that can scale to monorepos with millions of files and terabytes of data.
## Key Concepts
* **[Repository](/examples/data/versioning)**: A collection of files and folders that is versioned together.
* **Commit**: A snapshot of a repository at a given time.
* **Branch**: A named pointer to a commit.
* **[Dataset](/examples/data/datasets)**: A tabular file within an oxen repository that can be indexed and searched. ie csv, jsonl, parquet, etc.
* **[Workspace](/examples/data/workspaces)**: The equivalent of a working directory on the remote server where files can be added in an uncommitted state.
Follow along with the [Version Control](/examples/data/versioning) guide to learn how to version your data.
# Fine-Tuning Models on Oxen.ai
Source: https://docs.oxen.ai/getting-started/fine-tuning
Oxen.ai allows you to fine-tune text, image, and video models with a few clicks.
Simply [upload your data](/examples/data/datasets), and we will provision GPU infrastructure and run the fine-tune. When it's done, Oxen.ai will save the fine-tuned model weights directly to your repository, and we spin down the GPU for you. No worrying about run away costs or having to manage your own infrastructure.
Once the fine-tuning process is complete, you can deploy your model to a dedicated endpoint and use the [inference endpoints](/getting-started/inference) to integrate it into your application.
Oxen.ai automatically [versions](/examples/data/versioning) and manages the raw model weights and datasets, so that you can always track the data that was used to train the model, or download the model to run locally.
## Why Fine-Tune?
Fine-tuning is a great tool to reach for when basic prompting and context engineering fall short. You may need to fine-tune when:
* **Quality** is critical and the model isn't consistently producing correct outputs.
* **Proprietary Data** gives you a unique advantage that generic models can't capture.
* **Latency** is a deal breaker and you need real-time responses.
* **Throughput** limitations are bottlenecking your application's scalability.
* **Ownership** of the model is important and you want to control your own destiny.
* **Cost** if a foundation model is too expensive for your use case or you want to deploy a smaller model to the edge.
With Oxen.ai, we make it easy to automate the fine-tuning process of LLMs on your own data.
## Modalities
Oxen.ai supports many data types and tasks for fine-tuning.
Fine-tune a model to take a user input as text and generate a single response as text.
Fine-tune a model on chat messages to have a conversation with a user.
Fine-tune a model to go from text descriptions to images.
Fine-tune a model to take a prompt and a reference image and generate a new image.
Fine-tune a model to take in a prompt and generate a video.
## Start by Uploading a Dataset
To get started, you'll need to create a new repository on Oxen.ai. Once you've created a repository, you can upload your data. The dataset can be in any tabular format including `csv`, `jsonl`, or `parquet`.
Once you have your dataset uploaded, you can query, explore, and make sure that the data is high quality before kicking off the fine-tuning process. Your model will only be as good as the data you train it on.
When you feel confident that your dataset is ready, use the "Actions" button to select the model you want to fine-tune.
## Selecting a Model
This will take you to a form where you can select the model you want to fine-tune and the columns you want to use for the fine-tuning process. We support fine-tuning for [text generation](/examples/fine-tuning/text_generation), [image generation](/examples/fine-tuning/image_generation), [image editing](/examples/fine-tuning/image_editing), and [video generation](/examples/fine-tuning/video_generation) with a variety of models.
If you want support for any specific models, data formats, training methods contact us at [hello@oxen.ai](mailto:hello@oxen.ai) and we are happy to help you get started. We are actively working on support for new models and distributed training.
## Monitoring the Fine-Tune
Once you have started the fine-tuning process, you can monitor its progress. The dashboard will show you loss over time and token accuracy processed.
If you are fine-tuning an [image](/examples/fine-tuning/image_generation) or [video](/examples/fine-tuning/video_generation) generation model, you can view the generated images or videos in the "Samples" tab to get a feel for the model's performance.
Click on the "Info" tab to see the fine-tuning configuration and all the hyper-parameters used. This will include a link to the [dataset version](/examples/data/versioning) you used and the raw model weights for downloading and running locally.
## Deploying the Model
Once the model is fine-tuned, you can deploy it to a dedicated endpoint or in the playground.
This will give you a `/ai/chat/completions` api and a playground that you can use to test out the model.
Start by using the "playgroud" button.
if the model is not loaded you'll see an "inactive" button on the top right of the playgroud.
use the activate button to load the model.
To use the API Swap out the model name with the name of the model you want to use.
```bash theme={null}
curl https://hub.oxen.ai/api/ai/chat/completions -H "Content-Type: application/json" -d '{
"model":"oxen:my-model-name",
"messages": [{"role": "user", "content": "What is the best name for a friendly ox?"}],
}'
```
## Using the Model
Once the model is deployed, you can also chat with it using the Oxen.ai chat interface at the playgroud. Learn more about the [chat interface here](/getting-started/inference).
For image and video generation, you can use the [playground](https://oxen.ai/ai/models) to generate images and videos.
## Downloading the Model
If you want access to the raw model weights, you can download them from the repository using the Oxen.ai [Python Library](/python-api) or the [CLI](/getting-started/command-line/start_repository).
Follow the instructions for [installing oxen](/getting-started/install) if you haven't already.
```bash CLI theme={null}
oxen download my-username/my-repo models/ox-artistic-cyan-elephant/model.safetensors --revision models/ox-artistic-cyan-elephant
```
```python Python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("my-username/my-repo")
repo.download("models/ox-artistic-cyan-elephant/model.safetensors", revision="models/ox-artistic-cyan-elephant")
```
## Need Custom Infrastructure?
If you need custom or private deployments in your own VPC or want to train a larger model on distributed infrastructure, contact us at [hello@oxen.ai](mailto:hello@oxen.ai) and we can give you a custom deployment.
# Using Models on Oxen.ai
Source: https://docs.oxen.ai/getting-started/inference
Oxen.ai exposes API endpoints and a playground for a variety of models and modalities, including text, image, and video generation.
## Model API
Oxen.ai's API allows you to start building on top of the [latest models](https://oxen.ai/ai/models) and deploy [fine-tuned models](/getting-started/fine-tuning) with a single API. If a model is too slow, costly, inaccurate, or if you want full control of the weights, you can use our [one-click interface to fine-tune](/getting-started/fine-tuning) and deploy a custom model using the same interface.
## All your modalities, in one place
Whether you want to generate text, images, or videos, Oxen.ai has you covered. If you want any other modality or model, reach out at [support@oxen.ai](mailto:support@oxen.ai) and we'll be happy to add your use-case to the platform.
Checkout the documentation for each modality to learn more about how to use them.
Generate a response based on a user text prompt.
Generate images based on a user prompt.
Edit an image based on a user prompt and a reference image.
Generate a video based on a user prompt.
# βοΈ Installation
Source: https://docs.oxen.ai/getting-started/install
How to install the Oxen client, server, or python package.
## Command Line Tools
The Oxen client can be installed on MacOS via [homebrew](https://brew.sh/) or by downloading the relevant binaries for Linux or Windows.
You can find the source code for the client [here](https://github.com/Oxen-AI/Oxen) and can also build from source for your platform.
All binaries for MacOS, Linux, Windows and Docker are available on [GitHub Releases](https://github.com/Oxen-AI/Oxen/releases).
### Mac
```bash theme={null}
brew install oxen
```
### Linux
#### Ubuntu
We provide .deb packages that can be installed directly on Debian-based systems such as Ubuntu.
First, download the release for your system's architecture.
For x86-64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-linux-x86_64.deb
```
For ARM64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-linux-arm64.deb
```
Then run
```bash theme={null}
sudo dpkg -i oxen-linux-*.deb
```
#### Other distributions
We also provide distributions-agnostic binaries that can be installed on any Linux system.
First, download the release for your system's architecture.
For x86-64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-linux-x86_64.tar.gz
```
For ARM64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-linux-arm64.tar.gz
```
```bash theme={null}
tar -xzvf oxen-linux-*.tar.gz
chmod +x oxen
mv oxen /usr/local/bin
```
### Windows
We provide Windows binaries as a .exe.
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-windows-x86_64.exe.zip
```
## Python Package
The easiest way to get started with the Oxen Python library is to use `uv`. [Install uv](https://docs.astral.sh/uv/getting-started/installation/).
Install a supported Python version
```bash theme={null}
uv python install 3.13
```
Then, create and init a new uv project.
```bash theme={null}
mkdir my-python-script/ && cd my-python-script/
uv init
```
This will create a virtual environment with the latest installed Python version.
Next, add the oxen library to the project
```bash theme={null}
uv add oxenai
```
Then, to test that everything is working, update `main.py` with the following code.
```python theme={null}
import oxen
oxen.clone("ox/SpanishToEnglish")
```
Then run the script with `uv run` so it executes in the virtual environment.
```bash theme={null}
uv run main.py
```
Note that this will only install the Python library and not the command line tool.
### Installing Oxen through Jupyter Notebooks or Google Colab
Create and run this cell:
```python theme={null}
!pip install oxenai
```
### Docker
We build many binary wheels for the Python library (and we're working on adding more), but if your container image doesn't work with one of our binary wheels, pip will have to build it from source. Here is a minimal Dockerfile for a Debian-based image that installs the prerequisites for building the Oxen library from source:
```Dockerfile theme={null}
FROM python:3.12-slim-bookworm
RUN apt update
RUN apt install -y clang pkg-config libssl-dev curl
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN pip install oxenai
```
## Server Install
The Oxen server binary can be deployed where ever you want to store and backup your data. It is an HTTP server that the client communicates with to enable collaboration.
### Mac
```bash theme={null}
brew tap Oxen-AI/oxen-server
brew install oxen-server
```
### Docker
First, download the Docker image based on the host architecture.
For x86-64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-docker-x86_64.tar
```
For ARM64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-docker-arm64.tar
```
Then, load the image into Docker.
```bash theme={null}
docker load < oxen-server-docker-*.tar
```
Finally, run a new container.
```bash theme={null}
docker run -d -v /var/oxen/data:/var/oxen/data -p 80:3001 oxen/oxen-server:latest
```
### Linux
#### Ubuntu
We provide .deb packages that can be installed directly on Debian-based systems such as Ubuntu.
First, download the release for your system's architecture.
For x86-64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-linux-x86_64.deb
```
For ARM64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-linux-arm64.deb
```
Then run
```bash theme={null}
sudo dpkg -i oxen-server-linux-*.deb
```
#### Other distributions
First, download the release for your system's architecture.
For x86-64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-linux-x86_64.tar.gz
```
For ARM64
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-linux-arm64.tar.gz
```
```bash theme={null}
tar -xzvf oxen-server-linux-*.tar.gz
chmod +x oxen-server
mv oxen-server /usr/local/bin
```
### Windows
`oxen-server` is **not supported on Windows**. Host the server on Linux, macOS, or Docker (see the sections above). The Windows `oxen` CLI and the Python package still work as clients against a server running on a supported platform.
To get up and running using the client and server, you can follow the [getting started docs](https://github.com/Oxen-AI/oxen).
## Building from Source
To build the command line tool from source, you can follow these steps.
1. Install rustup via the instructions at [https://rustup.rs/](https://rustup.rs/)
2. Clone the repository [https://github.com/Oxen-AI/Oxen](https://github.com/Oxen-AI/Oxen)
```bash theme={null}
git clone git@github.com:Oxen-AI/Oxen.git
```
3. `cd` into the cloned repository
```bash theme={null}
cd Oxen/oxen-rust
```
4. Run this command (the release flag is recommended but not necessary):
```bash theme={null}
cargo build --release
```
5. After the build has finished, the `oxen` binary will be in `Oxen/oxen-rust/target/release` (or, if you did not use the --release flag, `Oxen/oxen-rust/target/debug`).
Now, to make it usable from a terminal window, you have the option to add it to create a symlink or to add it to your `PATH`.
6. To add oxen to your `PATH`:
Add this line to your `.bashrc` (or equivalent, e.g. `.zshrc`)
```bash theme={null}
export PATH="$PATH:/path/to/Oxen/oxen-rust/target/release"
```
7. Alternatively, to create a symlink, run the following command:
```bash theme={null}
sudo ln -s /path/to/Oxen/oxen-rust/target/release/oxen /usr/local/bin/oxen
```
Note that if you did not use the `--release` flag when building Oxen, you will have to change the path.
# π Oxen.ai
Source: https://docs.oxen.ai/getting-started/intro
The platform for building AI on your own data.
Oxen.ai gives developers, creators, and teams easy access to the latest AI models, plus the data infrastructure to organize, version, collaborate on, and customize the data behind them.
Use 200+ image, video, audio, and language models through one API. Save prompts, reference assets, generations, metadata, and training data into collaborative repositories. Track every change, branch experiments, and fine-tune custom models on your proprietary data.
Your models are only as good as the data behind them. Oxen helps teams turn raw inputs and generated outputs into organized, reusable, version-controlled assets.
## β‘οΈ Use Any Model
Access 200+ models through a unified API instead of integrating with each provider separately. Build your own product experiences on top of Oxen while keeping model inputs, outputs, and metadata stored in a data repository for auditability, reproducibility, and collaboration.
Oxen.ai has models of every modality (text, images, videos, audio) from the major labs, and more. Explore the [list of supported models](https://oxen.ai/ai/models) to see what you can build.
View API documentation
## πΎ Customize Your Models
Customization can start simple: managing prompts, context, reference images, and generated outputs. If prompting isn't enough, fine-tune the model weights themselves on your proprietary datasets. In both cases, your data is what makes the model yours.
Fine-tune open source models for many modalities (text, images, videos) on your proprietary data. Oxen gives you the tools to version, collaborate, and customize the data behind your models.
Learn how to train your own model
## πΎ Version Your Data
Track the provenance of prompts, images, videos, audio, text, labels, metadata, generations, and training examples in repositories built for large datasets. Oxen gives you git-like version control for data that can scale to terabytes.
We built the version control system to be [blazing fast](/examples/data/performance), [open source](https://github.com/Oxen-AI/Oxen), and extensible for anyone to build upon. It can be used to version any type of data, not just machine learning datasets. It scales up to monorepos with [millions of files and terabytes of data](/examples/data/performance).
Get started with versioning
## π€ Collaborate With Your Team
Built on top of the [open source](https://github.com/Oxen-AI/Oxen) Oxen version control system, Oxen.ai gives your team a [web hub](https://oxen.ai) to work with your data, prompts, and generations at scale. Browse datasets, review generated outputs, and experiment across branches. Every contribution is versioned, so you can see who changed what, when, and why, and discard changes when an experiment doesn't pan out.
Sign up for free
For teams with stricter requirements around data residency, compliance, or IP, Oxen.ai offers private deployments in your VPC or fully on-prem. Your proprietary data, prompts, and model weights stay in your environment, while your team gets the same collaborative tooling. Reach out to [hello@oxen.ai](mailto:hello@oxen.ai) to learn more about private deployments.
## π€ Own Your AI
At Oxen.ai, we believe you should **own your AI**. Owning your AI means making the model uniquely yours. For image and video generation, that might mean consistent style, characters, products, or brand identity. For language models, it might mean better accuracy, lower cost, stronger privacy, or deeper domain expertise. It also means owning the data behind the model. Your prompts, reference images, generations, labels, and training data live in a versioned repository, and you can read or write any of it through the [Python API](/python-api/index), [HTTP API](/http-api/index), [command line](/getting-started/install), or [open source server](/getting-started/oxen-server). No matter what kind of model you are building with, you should be able to train it, version it, deploy it, and improve it on your terms.
## πΎ Why Build Oxen?
Oxen was built by a team of machine learning engineers, who have spent countless hours in their careers managing datasets and training models. We have used many different tools, but none of them were as easy to use and as ergonomic as we would like.
Production grade AI applications are constantly juggling models, datasets, and code, and it's easy to get lost. Let alone the late nights installing the proper cuda and pytorch versions. If you have every been stuck dumping massive model weights and datasets to S3 in tarballs with little visibility, we feel your pain.
Oxen is the tool we wish we had to abstract away the infrastructure and focus on the fun parts of building AI applications.
## π Why the name Oxen?
"Oxen" comes from the fact that we take care of the grunt work of the infrastructure for you. Oxen love will plow, maintain, and version your data and models like a good farmer tends to their fields πΎ. During the agricultural revolution, the oxen pulling plows offloaded work and helped people specialize and start working on other important societal tasks. Let Oxen take care of the heavy infrastructure work so you can focus on solving the higher-level problems that matter to your product.
## π Learn More
For practical guides, fine-tuning walkthroughs, model comparisons, and research paper breakdowns from our [ArXiv Dives](https://www.oxen.ai/community) paper club, check out the [Oxen.ai blog](https://www.oxen.ai/blog).
# π‘ Oxen Server
Source: https://docs.oxen.ai/getting-started/oxen-server
`oxen-server` is the storage backend for Oxen. It is where the merkle tree, commit history, and other metadata is stored.
You can deploy your own `oxen-server` instance on your own infrastructure, or use the hosted version on [OxenHub](https://oxen.ai). If you want to kick the tires of Oxen in the privacy of your own infrastructure, we recommend you setup a local server.
```bash theme={null}
oxen-server start -p 3000 -i 0.0.0.0
```
The hosted solution comes with a [UI](https://oxen.ai) and the benefits of not having to setup infrastructure yourself. [Sign up here](https://oxen.ai/register) to get started.
## βοΈ Install
To setup a local Oxen Server instance, first install the `oxen-server` binary.
`oxen-server` is supported on **Linux, macOS, and Docker**. It is **not supported on Windows**. The `oxen` CLI and Python client *are* supported on Windows and can connect to a server hosted on a supported platform.
### Mac OS
On Mac-OS you can use [Homebrew](https://brew.sh/) to install the binary.
```bash theme={null}
brew tap Oxen-AI/oxen-server
brew install oxen-server
```
### Ubuntu
On Ubuntu you can download the latest .deb file from our [GitHub Releases](https://github.com/Oxen-AI/Oxen/releases) and install it.
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-ubuntu-latest.deb
sudo dpkg -i oxen-server-ubuntu-latest.deb
```
### Docker
To run the server in a docker container, download the latest .tar file from our [GitHub Releases](https://github.com/Oxen-AI/Oxen/releases) and run the following commands.
```bash theme={null}
wget https://github.com/Oxen-AI/Oxen/releases/latest/download/oxen-server-docker.tar
docker load < oxen-server-docker.tar
docker run -d -v /path/to/my/data:/var/oxen/data -p 80:3001 oxen/oxen-server:latest
```
To install on other platforms, follow the [installation instructions](/getting-started/install).
## ποΈ Start Server
The server can be run with access token authentication turned on or off. The server runs with no authentication by default.
```bash theme={null}
oxen-server start
```
To enable authentication, generate a token to give it to the user to access to the server
```bash theme={null}
oxen-server add-user --email YOUR_EMAIL --name YOUR_NAME
```
Output:
```
User access token created:
XXXXXXXX
To give user access have them run the command `oxen config --auth `
```
You may have different authentication tokens for different hosts. From the client side, you can setup an auth token per host with the `config` command. If you ever need to debug or edit the tokens manually, they are stored in the `~/.config/oxen/auth_config.toml` file.
```bash theme={null}
oxen config --auth
cat ~/.config/oxen/auth_config.toml
```
To run the server with authentication, use the `-a` flag
```bash theme={null}
oxen-server start -a
```
## ποΈ Sync Directory
The default directory that Oxen stores data is `/tmp/oxen_sync`, which is not a good idea for production. To change it set the `SYNC_DIR` environment variable to a path.
```bash theme={null}
export SYNC_DIR=/var/oxen/data
oxen-server start -a
```
Output:
```
Running π server on 0.0.0.0:3000
Syncing to directory: /var/oxen/data
[2022-06-08T10:00:48Z INFO actix_server::builder] Starting 8 workers
[2022-06-08T10:00:48Z INFO actix_server::server] Actix runtime found; starting in Actix runtime
```
If you want to change the default `IP ADDRESS` and `PORT` you can do so by passing them in with the `-i` and `-p` parameters.
```bash theme={null}
oxen-server start -i 0.0.0.0 -p 4321
```
## π Debug Logs
The oxen server provides debug logs, which are off by default. You can turn these on with the RUST\_LOG variable.
```bash theme={null}
export RUST_LOG="debug"
```
You can also set RUST\_LOG to `info` or `warn` for more restrictive debug logs. Be aware, turning on debug logs can significantly slow down some metadata-heavy operations like `oxen commit`
## π Create a Repository
Assuming you have already installed the `oxen` CLI, you can create a remote repository on the server.
```bash theme={null}
oxen create-remote --name my_namespace/repo_name --host localhost:3000 --scheme http
```
Note: The host and scheme are optional and default to `hub.oxen.ai` and `https` respectively. If you are running a local server, you can set the host to `localhost:3000` and the scheme to `http`.
You can either clone data from this remote repository, or push data to it.
## ποΈ File Storage
When you create a remote repository, Oxen will create a directory for it on the server. The directory structure is `$SYNC_DIR///.oxen`.
```bash theme={null}
ls /var/oxen/data/my_namespace/repo_name/.oxen
```
Output:
```
config.toml
history/
refs/
tree/
versions/
```
All of the metadata and versioned files for a repository are stored in the `.oxen` directory. This directory mirrors the `.oxen` directory in your local repository, so that logic can be reused between the client and server.
## πΎ Configureable Storage Backend
Oxen allows you to configure the storage backend of self-hosted oxen servers. By default, a repository's version files are stored in the `.oxen/versions/files` folder, but this can be changed by using the `--storage-backend` and `--storage-backend-path` parameters
```bash theme={null}
oxen create-remote --name ox/test_repo --host localhost:3000 --scheme http --storage-backend local --storage-backend-path ~/mountpoint/ox/test_repo/version/files
```
This is useful if you have a large amount of data and you want to store it on a virtual file system. If you set `--storage-backend-path` to a location on your VFS, files that you push to the remote will be stored there. Depending on your VFS, this can slow down some upload and download operations.
## β¬οΈ Upload Data
To upload data to the server, you can use the `oxen` CLI to initialize a local repository, add data to it, and push it to the server.
```bash theme={null}
# Create a directory for the new dataset
mkdir my-dataset
cd my-dataset
# Initialize a local repository
oxen init
# Add data to the repository
echo "prompt,response" > data.csv
oxen add data.csv
# Commit the changes
oxen commit -m "Initial commit"
```
If you look in your local repository, you will see the `.oxen` directory.
```bash theme={null}
ls .oxen
```
Output:
```
config.toml
history/
refs/
tree/
versions/
```
You can set the remote to the server by running the following command. This will update the `config.toml` file in your local repository.
```bash theme={null}
oxen config --set-remote origin http://localhost:3000/my_namespace/repo_name
```
If you look at the `config.toml` file, you will see the remote set.
```bash theme={null}
cat .oxen/config.toml
```
Output:
```toml theme={null}
remote_name = "origin"
[[remotes]]
name = "origin"
url = "http://localhost:3000/my_namespace/repo_name"
```
Once a remote is set you can push your changes to the server.
```bash theme={null}
oxen push origin main
```
You can change the remote (origin) and the branch (main) to whichever remote and branch you want to push.
## β¬οΈ Clone Data
Clone the empty repository:
```bash theme={null}
oxen clone http:///my_namespace/repo_name
```
## API Spec
The server has a REST API that can be used to interact with the server. The API is documented [here](/http-api).
# Create a new branch
Source: https://docs.oxen.ai/http-api/branches/create-a-new-branch
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/branches
Create a new branch from another branch name or commit ID. Returns existing branch if name already exists.
# Delete a branch
Source: https://docs.oxen.ai/http-api/branches/delete-a-branch
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/branches/{branch_name}
Force delete a branch by name.
# Get all versions of a file on a branch
Source: https://docs.oxen.ai/http-api/branches/get-all-versions-of-a-file-on-a-branch
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/branches/{branch_name}/versions/{path}
List paginated historical versions of a file across commits on a branch, including schema hash for tabular files.
# Get an existing branch
Source: https://docs.oxen.ai/http-api/branches/get-an-existing-branch
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/branches/{branch_name}
Get a branch by name, returning its details and current commit.
# List all branches
Source: https://docs.oxen.ai/http-api/branches/list-all-branches
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/branches
List all branches in the repository with their current commit information.
# Merge a commit into a branch
Source: https://docs.oxen.ai/http-api/branches/merge-a-commit-into-a-branch
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/branches/{branch_name}/merge
Merge a client commit into a branch during push. Returns merge commit on success, or original server commit if conflicts occur.
# Update a branch to a new commit
Source: https://docs.oxen.ai/http-api/branches/update-a-branch-to-a-new-commit
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/branches/{branch_name}
Update a branch to point to a different commit ID.
# Download commit entries DB
Source: https://docs.oxen.ai/http-api/commits/download-commit-entries-db
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/{commit_or_branch}/commit_entries_db
Download the commit entries database for a specific commit as a tarball.
# Download commits DB
Source: https://docs.oxen.ai/http-api/commits/download-commits-db
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits_db
Download the commits database as a compressed tarball for cloning.
# Download dir hashes DB
Source: https://docs.oxen.ai/http-api/commits/download-dir-hashes-db
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/{base_head}/dir_hashes_db
Download directory hashes database for a commit range as a tarball.
# Get a commit's parents
Source: https://docs.oxen.ai/http-api/commits/get-a-commits-parents
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/{commit_or_branch}/parents
Get the parent commits of a specific commit or the tip of a branch.
# Get commit
Source: https://docs.oxen.ai/http-api/commits/get-commit
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/{commit_id}
Get details of a specific commit by its ID.
# Get root commit
Source: https://docs.oxen.ai/http-api/commits/get-root-commit
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/root
Get the root (initial) commit of the repository, or None if empty.
# List all commits
Source: https://docs.oxen.ai/http-api/commits/list-all-commits
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/all
List all commits in a repository
# List commit history
Source: https://docs.oxen.ai/http-api/commits/list-commit-history
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/history/{resource}
List paginated commit history for a revision or file path. Supports revision ranges (base..head) and path-specific history.
# List missing commits
Source: https://docs.oxen.ai/http-api/commits/list-missing-commits
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/missing
From a list of commit hashes, list the ones not present on the server
# List missing files from commits
Source: https://docs.oxen.ai/http-api/commits/list-missing-files-from-commits
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/commits/missing_files
List files that are referenced in a commit but not present on the server. Accept a commit range.
# Mark commits as synced
Source: https://docs.oxen.ai/http-api/commits/mark-commits-as-synced
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/commits/mark_commits_as_synced
DEPRECATED - This operation is a no-op that echoes the hashes from the request, and will be removed in a future release.
# Notify upload complete
Source: https://docs.oxen.ai/http-api/commits/notify-upload-complete
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/commits/{commit_id}/complete
Notify the server that the commit has finished uploading.
# Upload commit
Source: https://docs.oxen.ai/http-api/commits/upload-commit
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/commits
Upload a commit to a branch on the server. This creates an empty commit. Its merkle tree nodes are uploaded separately through the tree nodes endpoint.
# Upload commits DB
Source: https://docs.oxen.ai/http-api/commits/upload-commits-db
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/commits/upload
Upload the commits database tarball to the server during push.
# Upload data chunk
Source: https://docs.oxen.ai/http-api/commits/upload-data-chunk
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/commits/upload_chunk
Upload a chunk of file data for use in large file uploads.
# Get Derived Data Frame
Source: https://docs.oxen.ai/http-api/compare/get-derived-data-frame
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}/diff
Get the derived diff data frame rows with pagination and optional filtering.
# Get diff tree
Source: https://docs.oxen.ai/http-api/compare/get-diff-tree
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/{base_head}/tree
Get a tree structure of directories that have changed between two revisions.
# Get file diff
Source: https://docs.oxen.ai/http-api/compare/get-file-diff
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/{base_head}/file/{resource}
Get the detailed diff for a specific file between two revisions.
# List changed files
Source: https://docs.oxen.ai/http-api/compare/list-changed-files
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/{base_head}/dir/{dir}/entries
List the files and sub-directories within a directory that have changed within a provided commit range.
# List commits between two revisions
Source: https://docs.oxen.ai/http-api/compare/list-commits-between-two-revisions
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/{base_head}/commits
List commits between a 'base' and 'head' commit. Two dots list what head added; three dots list the commits on either side but not both, each tagged with the side it came from.
# List file and directory entries changed between base and head
Source: https://docs.oxen.ai/http-api/compare/list-file-and-directory-entries-changed-between-base-and-head
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/{base_head}/entries
List paginated entries (files and directories) that changed between two revisions with change counts.
# Create a tabular data frame diff
Source: https://docs.oxen.ai/http-api/data-frames/create-a-tabular-data-frame-diff
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/compare/data_frames
Create and cache a tabular diff comparing two data frames with configurable keys and target columns.
# Create data frame from directory
Source: https://docs.oxen.ai/http-api/data-frames/create-data-frame-from-directory
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/data_frames/from_directory/{resource}
Create a data frame by scanning directory contents and commit it to a branch.
# Delete DF Diff
Source: https://docs.oxen.ai/http-api/data-frames/delete-df-diff
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}
Delete a cached tabular diff comparison by its ID.
# Get a cached tabular data frame diff
Source: https://docs.oxen.ai/http-api/data-frames/get-a-cached-tabular-data-frame-diff
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}
Retrieve a previously cached tabular diff by its comparison ID.
# Get data frame slice
Source: https://docs.oxen.ai/http-api/data-frames/get-data-frame-slice
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/data_frames/{resource}
Get a paginated slice of a tabular data frame with optional filtering and transformations.
# Start data frame indexing
Source: https://docs.oxen.ai/http-api/data-frames/start-data-frame-indexing
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/data_frames/{resource}/index
Start indexing a tabular file for queryable access. Creates a workspace if the file is not already indexed.
# Update tabular data frame diff
Source: https://docs.oxen.ai/http-api/data-frames/update-tabular-data-frame-diff
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}
Update an existing cached tabular diff comparison with new configuration.
# List directory contents
Source: https://docs.oxen.ai/http-api/directories/list-directory-contents
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/dir/{resource}
List paginated contents of a directory at a specific revision, with optional workspace support.
# File Upload Example
Source: https://docs.oxen.ai/http-api/example
Complete guide to uploading files to Oxen repositories via HTTP API
This guide demonstrates how to upload files to an Oxen repository using the HTTP API. We'll break down the process into simple, manageable steps.
## Overview
Uploading a file to Oxen involves three main steps:
1. **Create a repository** (if it doesn't exist)
2. **Upload the file** to a specific branch
3. **Optionally create additional branches** from the uploaded content
## Prerequisites
Before you begin, you'll need:
* **API Key**: Authentication token for your Oxen account
* **Server URL**: The Oxen server endpoint (e.g., `https://hub.oxen.ai` or your local server)
* **File to upload**: The local file you want to add to your repository
## Configuration
First, set up your configuration variables:
```python Python theme={null}
# Server Configuration
SERVER_URL = "https://hub.oxen.ai"
API_KEY = "your-api-key-here"
# Repository Configuration
NAMESPACE = "your-username"
REPO_NAME = "my-dataset"
DESCRIPTION = "Example dataset created via HTTP API"
# User Configuration (for commits)
USER_NAME = "Your Name"
USER_EMAIL = "your.email@example.com"
# Branch Configuration
SOURCE_BRANCH = "main"
# File Upload Configuration
LOCAL_FILE_PATH = "./example.txt"
REMOTE_FILE_PATH = "data/example.txt"
COMMIT_MESSAGE = "Add file via HTTP API"
```
## Step 1: Create a Repository
Create a new repository to store your files. If the repository already exists, this step will return a 409 status code and you can continue.
### Endpoint
```
POST /api/repos
```
### Example
```python Python theme={null}
import requests
def create_repository(base_url, api_key, namespace, name, user_name, user_email, description):
url = f"{base_url}/api/repos"
headers = {
'Authorization': f'Bearer {api_key}'
}
payload = {
"namespace": namespace,
"name": name,
"user": {
"name": user_name,
"email": user_email
},
"description": description
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
print("β Repository created successfully")
return response.json()
elif response.status_code == 409:
print("β Repository already exists")
return response.json()
else:
print(f"β Failed: {response.status_code}")
print(response.text)
return None
# Usage
create_repository(
base_url="https://hub.oxen.ai",
api_key="your-api-key",
namespace="your-username",
name="my-dataset",
user_name="Your Name",
user_email="your.email@example.com",
description="Example dataset"
)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/repos \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "your-username",
"name": "my-dataset",
"user": {
"name": "Your Name",
"email": "your.email@example.com"
},
"description": "Example dataset created via HTTP API"
}'
```
### Request Body
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------- |
| `namespace` | string | Yes | Your username or organization name |
| `name` | string | Yes | Repository name |
| `user.name` | string | Yes | Author name for commits |
| `user.email` | string | Yes | Author email for commits |
| `description` | string | No | Repository description |
### Response
```json theme={null}
{
"namespace": "your-username",
"name": "my-dataset",
"description": "Example dataset created via HTTP API",
"created_at": "2026-01-08T21:28:00Z"
}
```
## Step 2: Upload a File
Upload a file to your repository. This creates a commit on the specified branch with your file.
### Endpoint
```
PUT /api/repos/{namespace}/{repo_name}/file/{branch}/{path}
```
### Path Parameters
| Parameter | Description | Example |
| ----------- | ------------------------------ | ------------------ |
| `namespace` | Your username or organization | `your-username` |
| `repo_name` | Repository name | `my-dataset` |
| `branch` | Target branch name | `main` |
| `path` | Destination path in repository | `data/example.txt` |
### Example
```python Python theme={null}
import os
import requests
def upload_file(base_url, api_key, namespace, repo_name, branch,
local_file_path, remote_file_path, commit_message,
author_name, author_email):
# Check if file exists
if not os.path.exists(local_file_path):
print(f"β File not found: {local_file_path}")
return None
# Construct URL with branch and directory path
resource = f"{branch}/{remote_file_path}"
url = f"{base_url}/api/repos/{namespace}/{repo_name}/file/{resource}"
headers = {
'Authorization': f'Bearer {api_key}'
}
# Prepare file for upload
files = [
("file", (os.path.basename(local_file_path), open(local_file_path, 'rb')))
]
payload = {
'message': commit_message,
'name': author_name,
'email': author_email
}
response = requests.put(url, data=payload, files=files, headers=headers)
# Close the file
files[0][1][1].close()
if response.status_code == 200:
result = response.json()
print(f"β File uploaded successfully")
if 'commit' in result:
print(f" Commit ID: {result['commit'].get('id', 'N/A')}")
return result
else:
print(f"β Failed: {response.status_code}")
print(response.text)
return None
# Usage
upload_file(
base_url="https://hub.oxen.ai",
api_key="your-api-key",
namespace="your-username",
repo_name="my-dataset",
branch="main",
local_file_path="./example.txt",
remote_file_path="data/example.txt",
commit_message="Add example file",
author_name="Your Name",
author_email="your.email@example.com"
)
```
```bash cURL theme={null}
curl -X PUT \
"https://hub.oxen.ai/api/repos/your-username/my-dataset/file/main/data/example.txt" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@./example.txt" \
-F "message=Add example file" \
-F "name=Your Name" \
-F "email=your.email@example.com"
```
```python Multiple Files theme={null}
import requests
namespace = "your-username"
repo_name = "my-dataset"
api_key = "your-api-key"
resource = "main/images" # branch/directory
url = f"https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/file/{resource}"
files = [
("file", ("image_1.jpg", open("image_1.jpg", "rb"))),
("file", ("image_2.jpg", open("image_2.jpg", "rb"))),
("file", ("image_3.jpg", open("image_3.jpg", "rb")))
]
payload = {
"email": "your.email@example.com",
"message": "Adding images",
"name": "Your Name"
}
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.put(url, data=payload, files=files, headers=headers)
print(response.text)
```
### Request Body (Multipart Form Data)
| Field | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------------------------------------------- |
| `file` | file | Yes | The file(s) to upload. Can be multiple files with the same field name |
| `message` | string | Yes | Commit message |
| `name` | string | Yes | Author name |
| `email` | string | Yes | Author email |
**Note:** You can upload multiple files in a single request by including multiple `file` fields with different filenames.
### Response
```json theme={null}
{
"commit": {
"id": "abc123def456...",
"message": "Add example file",
"author": {
"name": "Your Name",
"email": "your.email@example.com"
},
"timestamp": "2026-01-08T21:28:00Z"
},
"file": {
"path": "data/example.txt",
"size": 1024,
"hash": "sha256:..."
}
}
```
## Step 3: Create a Branch (Optional)
After uploading files, you can create additional branches from your main branch.
### Endpoint
```
POST /api/repos/{namespace}/{repo_name}/branches
```
### Example
```python Python theme={null}
import requests
def create_branch(base_url, api_key, namespace, repo_name,
new_branch_name, from_branch):
url = f"{base_url}/api/repos/{namespace}/{repo_name}/branches"
headers = {
'Authorization': f'Bearer {api_key}'
}
payload = {
"new_name": new_branch_name,
"from_name": from_branch
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
print(f"β Branch '{new_branch_name}' created")
return response.json()
else:
print(f"β Failed: {response.status_code}")
print(response.text)
return None
# Usage
create_branch(
base_url="https://hub.oxen.ai",
api_key="your-api-key",
namespace="your-username",
repo_name="my-dataset",
new_branch_name="development",
from_branch="main"
)
```
```bash cURL theme={null}
curl -X POST \
https://hub.oxen.ai/api/repos/your-username/my-dataset/branches \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"new_name": "development",
"from_name": "main"
}'
```
### Request Body
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------- |
| `new_name` | string | Yes | Name of the new branch |
| `from_name` | string | Yes | Source branch to create from |
### Response
```json theme={null}
{
"name": "development",
"commit_id": "abc123def456...",
"created_at": "2026-01-08T21:28:00Z"
}
```
## Complete Example
Here's a complete Python script that ties everything together:
```python theme={null}
import os
import sys
import requests
# Configuration
SERVER_URL = "https://hub.oxen.ai"
API_KEY = "your-api-key-here"
NAMESPACE = "your-username"
REPO_NAME = "my-dataset"
USER_NAME = "Your Name"
USER_EMAIL = "your.email@example.com"
DESCRIPTION = "Example dataset created via HTTP API"
# File configuration
LOCAL_FILE_PATH = "./example.txt"
REMOTE_FILE_PATH = "data/example.txt"
COMMIT_MESSAGE = "Add file via HTTP API"
SOURCE_BRANCH = "main"
def main():
print("=" * 70)
print("Oxen File Upload Pipeline")
print("=" * 70)
base_url = SERVER_URL.rstrip('/')
# Step 1: Create Repository
print(f"\n[Step 1/2] Creating repository: {NAMESPACE}/{REPO_NAME}")
create_repository(
base_url=base_url,
api_key=API_KEY,
namespace=NAMESPACE,
name=REPO_NAME,
user_name=USER_NAME,
user_email=USER_EMAIL,
description=DESCRIPTION
)
# Step 2: Upload File
print(f"\n[Step 2/2] Uploading file to '{SOURCE_BRANCH}' branch")
print(f" {LOCAL_FILE_PATH} β {REMOTE_FILE_PATH}")
upload_file(
base_url=base_url,
api_key=API_KEY,
namespace=NAMESPACE,
repo_name=REPO_NAME,
branch=SOURCE_BRANCH,
local_file_path=LOCAL_FILE_PATH,
remote_file_path=REMOTE_FILE_PATH,
commit_message=COMMIT_MESSAGE,
author_name=USER_NAME,
author_email=USER_EMAIL
)
print("\n" + "=" * 70)
print("β Upload completed successfully!")
print("=" * 70)
print(f"\nRepository: {SERVER_URL}/{NAMESPACE}/{REPO_NAME}")
print(f"File location: {SOURCE_BRANCH} β {REMOTE_FILE_PATH}")
print("=" * 70 + "\n")
if __name__ == '__main__':
main()
```
## Authentication
All API requests require authentication using a Bearer token in the Authorization header:
```
Authorization: Bearer YOUR_API_KEY
```
### Getting an API Key
1. Log in to your Oxen account at [hub.oxen.ai](https://hub.oxen.ai)
2. Navigate to your account settings
3. Generate a new API key
## Advanced Usage
### Uploading Multiple Files
You can upload multiple files in a single request by including multiple `file` fields:
```python theme={null}
import requests
namespace = "your-username"
repo_name = "my-dataset"
api_key = "your-api-key"
resource = "main/data" # branch/directory
url = f"https://hub.oxen.ai/api/repos/{namespace}/{repo_name}/file/{resource}"
# Multiple files in one request
files = [
("file", ("file1.txt", open("./data/file1.txt", "rb"))),
("file", ("file2.csv", open("./data/file2.csv", "rb"))),
("file", ("photo.jpg", open("./images/photo.jpg", "rb")))
]
payload = {
"email": "your.email@example.com",
"message": "Add multiple files",
"name": "Your Name"
}
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.put(url, data=payload, files=files, headers=headers)
# Close all files
for _, (_, file_obj) in files:
file_obj.close()
if response.status_code == 200:
print("β All files uploaded successfully")
print(response.json())
else:
print(f"β Upload failed: {response.status_code}")
print(response.text)
```
Or upload files one at a time:
```python theme={null}
files_to_upload = [
("./data/file1.txt", "data/file1.txt"),
("./data/file2.csv", "data/file2.csv"),
("./images/photo.jpg", "images/photo.jpg"),
]
for local_path, remote_path in files_to_upload:
print(f"Uploading {local_path}...")
upload_file(
base_url=SERVER_URL,
api_key=API_KEY,
namespace=NAMESPACE,
repo_name=REPO_NAME,
branch="main",
local_file_path=local_path,
remote_file_path=remote_path,
commit_message=f"Add {os.path.basename(local_path)}",
author_name=USER_NAME,
author_email=USER_EMAIL
)
```
### Uploading to Different Branches
```python theme={null}
# Upload to development branch
upload_file(
base_url=SERVER_URL,
api_key=API_KEY,
namespace=NAMESPACE,
repo_name=REPO_NAME,
branch="development", # Different branch
local_file_path="./experiment.txt",
remote_file_path="experiments/test1.txt",
commit_message="Add experimental data",
author_name=USER_NAME,
author_email=USER_EMAIL
)
```
### Organizing Files in Directories
Files are automatically organized based on the `remote_file_path`:
```python theme={null}
# Creates nested directory structure
upload_paths = {
"./raw_data.csv": "datasets/raw/data.csv",
"./processed.csv": "datasets/processed/data.csv",
"./model.pt": "models/v1/checkpoint.pt",
"./readme.md": "docs/README.md",
}
```
## Next Steps
* [Download Files](/http-api/files/download-file) - Learn how to retrieve files
* [List Files](/http-api/directories/list-directory-contents) - Browse repository contents
* [Workspaces](/http-api/workspaces/list-workspaces) - Work with remote data without downloading
* [Branches](/http-api/branches/list-all-branches) - Manage repository branches
## Related Resources
* [HTTP API Overview](/http-api)
* [Python SDK](/python-api/remote_repo)
* [Authentication Guide](/getting-started/auth)
# Export resource as a zip
Source: https://docs.oxen.ai/http-api/export/export-resource-as-a-zip
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/export/download/{resource}
Download a directory as a zip archive. Subject to size limits.
# Delete file
Source: https://docs.oxen.ai/http-api/files/delete-file
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/file/{resource}
Remove a file from the repository. Stage the file as removed to a workspace and commit the removal. Can remove files or directories.
# Download File
Source: https://docs.oxen.ai/http-api/files/download-file
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/file/{resource}
Download a file from the repository. Supports image resizing and video thumbnail generation via query parameters.
# Move/Rename file
Source: https://docs.oxen.ai/http-api/files/moverename-file
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json patch /api/repos/{namespace}/{repo_name}/file/{resource}
Move or rename a file within the repository and commit the change.
# Upload files
Source: https://docs.oxen.ai/http-api/files/upload-files
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/file/{resource}
Upload files via multipart form and commit them. Use `files[]` for directory uploads, or `file` for a single full-path upload. For backward compatibility, `file` also uploads into the target directory when `{resource}` already resolves to a directory.
# Check Oxen server status
Source: https://docs.oxen.ai/http-api/health/check-oxen-server-status
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/version
Check if the Oxen server is running and responsive.
# Import file from URL
Source: https://docs.oxen.ai/http-api/import/import-file-from-url
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/import/{resource}
Import a file from a remote URL and commit it to the repository.
# Upload zip archive
Source: https://docs.oxen.ai/http-api/import/upload-zip-archive
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/import/upload/{resource}
Upload and decompress a zip archive into the repository and commit the contents.
# Repository API
Source: https://docs.oxen.ai/http-api/index
The Repository API drives all the versioning (VCS) functionality for [oxen.ai](https://oxen.ai) and the open-source [oxen-server](/getting-started/oxen-server).
It handles managing repositories, branches, and files among other things. Browse
the API endpoints in the sidebar to the left to see what's available. You can
also look at the [File Upload Example](/http-api/example) to see one possible
workflow for chaining these requests together.
# Large File Upload
Source: https://docs.oxen.ai/http-api/large-file-upload
Upload files of any size in chunks, and optionally stage them into a workspace without committing
The [File Upload Example](/http-api/example) posts a whole file in one request. That
works until the file is bigger than what a single request can carry β a proxy, gateway,
or serverless function in front of your client will usually cap a request body long
before Oxen does.
The chunked upload API removes that ceiling. It's the same protocol `oxen push` uses for
large files: you hash the file, upload it in pieces, and ask the server to put those
pieces back together. Because each piece is its own request, the file's size stops
mattering β only the size of one chunk does.
It also does something the single-request upload can't: **stage the assembled file
directly into a [workspace](/getting-started/command-line/workspaces)**, so it's ready to commit β or to read
back β without ever committing it.
## The three steps
| Step | Endpoint |
| ---------------------- | ------------------------------------------------------------------------------------- |
| 1. Announce the upload | `POST /api/repos/{namespace}/{repo_name}/versions/{version_id}/create` |
| 2. Upload each chunk | `PUT /api/repos/{namespace}/{repo_name}/versions/{version_id}/chunks?offset={offset}` |
| 3. Reassemble | `POST /api/repos/{namespace}/{repo_name}/versions/{version_id}/complete` |
All three take your API key as a bearer token:
```
Authorization: Bearer YOUR_API_KEY
```
## The version id is the file's hash
`{version_id}` is not an id you invent. It is the **XXH3-128 hash of the file's complete
contents**, written as lowercase hexadecimal **with leading zeros stripped** β the format
Rust's `{:x}` produces for a `u128`.
This matters because step 3 re-hashes the bytes it assembled and compares them to
`{version_id}`. Get the hash wrong and the upload is rejected; get it right and you have
a guarantee that what the server stored is exactly what you sent.
```python Python theme={null}
import xxhash # pip install xxhash
with open(LOCAL_FILE_PATH, "rb") as f:
version_id = xxhash.xxh128(f.read()).hexdigest().lstrip("0")
```
```javascript JavaScript theme={null}
import { createXXHash128 } from "hash-wasm"; // npm install hash-wasm
const hasher = await createXXHash128();
hasher.init();
hasher.update(new Uint8Array(await file.arrayBuffer()));
const versionId = hasher.digest("hex").replace(/^0+/, "");
```
For files large enough that you don't want them in memory, hash incrementally β every
XXH3 implementation supports feeding it the file in pieces. Reuse the same pieces you're
about to upload and you read the file only once.
## Step 1: Announce the upload
Tell the server what's coming. `dst_dir` is optional and only used in step 3.
### Request
```
POST /api/repos/{namespace}/{repo_name}/versions/{version_id}/create
Content-Type: application/json
```
```json theme={null}
{
"hash": "9f3a1c77b2e4d8a6013f5c2e7a94bd10",
"file_name": "example.pdf",
"size": 20971520,
"dst_dir": "documents"
}
```
A `200` means go ahead and upload chunks. A rejection means this content already exists
in the version store β file contents are addressed by their hash, so there is nothing
left to upload and you can skip to step 3.
## Step 2: Upload the chunks
Send the file in slices. The body is the **raw bytes** of that slice β not multipart, not
JSON β and `offset` is the slice's byte position in the complete file.
### Request
```
PUT /api/repos/{namespace}/{repo_name}/versions/{version_id}/chunks?offset={offset}
Content-Type: application/octet-stream
```
```bash theme={null}
curl -X PUT \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @chunk-0.part \
"$SERVER_URL/api/repos/$NAMESPACE/$REPO/versions/$VERSION_ID/chunks?offset=0"
```
Output:
```json theme={null}
{"status":"success","status_message":"resource_created","oxen_version":"0.52.4"}
```
Because every chunk carries its own offset, chunks are independent: send them in any
order, in parallel, and retry any one of them on its own without restarting the upload.
Keep each chunk at or below **10 MiB**, the server's transfer segment size. Below that,
pick a size that fits whatever limit sits between you and the server β a chunk still has
to survive one ordinary HTTP request.
## Step 3: Reassemble
Ask the server to join the chunks into the version file. It verifies the count, then
verifies the hash.
### Request
```
POST /api/repos/{namespace}/{repo_name}/versions/{version_id}/complete
Content-Type: application/json
```
```json theme={null}
{
"files": [
{
"hash": "9f3a1c77b2e4d8a6013f5c2e7a94bd10",
"file_name": "example.pdf",
"dst_dir": "documents",
"num_chunks": 6
}
],
"workspace_id": "my-workspace"
}
```
| Field | Description |
| -------------------- | ------------------------------------------------------------------- |
| `files` | Exactly one file. More than one is rejected. |
| `files[].hash` | The same XXH3-128 hash as `{version_id}`. |
| `files[].file_name` | The name the file gets when staged. |
| `files[].dst_dir` | Optional directory to stage it under. Omit for the repository root. |
| `files[].num_chunks` | How many chunks you uploaded. Must match what the server holds. |
| `workspace_id` | Optional. Provide it to stage the file into that workspace. |
### Staging without committing
`workspace_id` is what makes this more than an upload. Provide it and the assembled file
is staged into that workspace at `dst_dir/file_name` β it shows up in
`GET /workspaces/{workspace_id}/changes`, it can be read back through
`GET /workspaces/{workspace_id}/files/{path}`, and it becomes part of the next commit
you make from that workspace.
Leave `workspace_id` out and the bytes simply live in the version store, addressed by
their hash, for you to reference later.
This means a large file can be uploaded, inspected, and even discarded without ever
entering the repository's history.
## Errors
| Response | Cause |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Invalid integer: invalid digit found in string` | `{version_id}` isn't hexadecimal. It must be a hex `u128`, not an arbitrary string. |
| `Number of chunks does not match expected number of chunks: 5 != 6` | `num_chunks` disagrees with what the server received. A chunk was lost, or two chunks shared an offset and overwrote each other. |
| `Hash mismatch writing ...` | The reassembled bytes don't hash to `{version_id}`. Usually the wrong hash algorithm or format β check that you used XXH3-128 with leading zeros stripped. |
## Complete example
Uploads a file in 4 MB chunks and stages it into a workspace.
```python Python theme={null}
import os
import requests
import xxhash
SERVER_URL = "https://hub.oxen.ai"
API_KEY = "your-api-key-here"
NAMESPACE = "your-username"
REPO_NAME = "my-dataset"
WORKSPACE_ID = "my-workspace"
LOCAL_FILE_PATH = "./example.pdf"
DST_DIR = "documents"
CHUNK_SIZE = 4 * 1024 * 1024
headers = {"Authorization": f"Bearer {API_KEY}"}
base = f"{SERVER_URL}/api/repos/{NAMESPACE}/{REPO_NAME}/versions"
file_name = os.path.basename(LOCAL_FILE_PATH)
size = os.path.getsize(LOCAL_FILE_PATH)
# The version id is the file's XXH3-128, hashed incrementally.
hasher = xxhash.xxh128()
with open(LOCAL_FILE_PATH, "rb") as f:
while block := f.read(CHUNK_SIZE):
hasher.update(block)
version_id = hasher.hexdigest().lstrip("0")
# 1. announce
requests.post(
f"{base}/{version_id}/create",
headers=headers,
json={"hash": version_id, "file_name": file_name, "size": size, "dst_dir": DST_DIR},
).raise_for_status()
# 2. upload every chunk, each addressed by its byte offset
num_chunks = 0
with open(LOCAL_FILE_PATH, "rb") as f:
offset = 0
while chunk := f.read(CHUNK_SIZE):
requests.put(
f"{base}/{version_id}/chunks",
headers={**headers, "Content-Type": "application/octet-stream"},
params={"offset": offset},
data=chunk,
).raise_for_status()
offset += len(chunk)
num_chunks += 1
# 3. reassemble and stage into the workspace
requests.post(
f"{base}/{version_id}/complete",
headers=headers,
json={
"files": [
{
"hash": version_id,
"file_name": file_name,
"dst_dir": DST_DIR,
"num_chunks": num_chunks,
}
],
"workspace_id": WORKSPACE_ID,
},
).raise_for_status()
print(f"Staged {file_name} ({size} bytes) in {num_chunks} chunks")
```
Output:
```
Staged example.pdf (20971520 bytes) in 5 chunks
```
From here, commit the workspace to turn the staged file into history:
```bash theme={null}
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"message":"Add example.pdf","author":"Your Name","email":"you@example.com"}' \
"$SERVER_URL/api/repos/$NAMESPACE/$REPO_NAME/workspaces/$WORKSPACE_ID/merge/main"
```
## When to use this
Reach for chunked upload when any of these is true:
* **The file is larger than one request can carry.** The most common reason. Serverless
platforms in particular cap request bodies at a few megabytes.
* **You want the upload to be resumable.** Chunks are independent, so a failure costs you
one chunk, not the whole file.
* **You want parallelism.** Offsets make the order irrelevant.
* **You want the file staged but not committed.** `workspace_id` puts it in a workspace,
where you can read it back or throw it away without touching history.
For small files where none of this applies, the single-request
[file upload](/http-api/example) is simpler.
# Check if branches are mergeable
Source: https://docs.oxen.ai/http-api/merge/check-if-branches-are-mergeable
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/merge/{base_head}
Check if two branches can be merged and list any conflicts.
# Merge branches
Source: https://docs.oxen.ai/http-api/merge/merge-branches
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/merge/{base_head}
Merge the head branch into the base branch, creating a merge commit.
# Get file metadata
Source: https://docs.oxen.ai/http-api/metadata/get-file-metadata
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/metadata/{resource}
Get metadata for a file or directory entry, with optional workspace support.
# Update file metadata
Source: https://docs.oxen.ai/http-api/metadata/update-file-metadata
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/metadata/{resource}
Update metadata for a file version in the version store.
# Get namespace
Source: https://docs.oxen.ai/http-api/namespaces/get-namespace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/namespaces/{namespace}
Get details of a specific namespace by name.
# List namespaces
Source: https://docs.oxen.ai/http-api/namespaces/list-namespaces
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/namespaces
List all namespaces on the server.
# Create repository
Source: https://docs.oxen.ai/http-api/repositories/create-repository
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos
Create a new repository, optionally with initial files via JSON or multipart form.
# Delete repository
Source: https://docs.oxen.ai/http-api/repositories/delete-repository
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}
Delete a repository. Deletion runs in the background.
# Get repository details
Source: https://docs.oxen.ai/http-api/repositories/get-repository-details
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}
Get repository details including size and data types from the main branch.
# Get repository size
Source: https://docs.oxen.ai/http-api/repositories/get-repository-size
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/size
Get the cached size of the repository in bytes.
# Get repository stats
Source: https://docs.oxen.ai/http-api/repositories/get-repository-stats
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/stats
Get the total number of files, the total size of the files, and the number of different file types.
# List repositories
Source: https://docs.oxen.ai/http-api/repositories/list-repositories
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}
List all repositories in a namespace.
# Transfer repository namespace
Source: https://docs.oxen.ai/http-api/repositories/transfer-repository-namespace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json patch /api/repos/{namespace}/{repo_name}/transfer
Transfer a repository to a different namespace.
# Update repository size
Source: https://docs.oxen.ai/http-api/repositories/update-repository-size
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/size
Recalculate and update the cached repository size.
# Batch download files (Tarball)
Source: https://docs.oxen.ai/http-api/version-files/batch-download-files-tarball
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/versions/batch-download
Download multiple files as a gzipped tarball by providing their hashes.
# Batch upload files (Multipart)
Source: https://docs.oxen.ai/http-api/version-files/batch-upload-files-multipart
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/versions
Upload multiple files via multipart form, identified by their content hashes.
# Download version file
Source: https://docs.oxen.ai/http-api/version-files/download-version-file
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/versions/{resource}
Download a file by its resource path, with optional image resizing.
# Get version file metadata
Source: https://docs.oxen.ai/http-api/version-files/get-version-file-metadata
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/versions/{version_id}/metadata
Get metadata for a specific file version by its hash.
# Post apirepos workspaces data framescolumnsschemametadata
Source: https://docs.oxen.ai/http-api/workspace-data-frames/post-apirepos-workspaces-data_framescolumnsschemametadata
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/data_frames/columns/schema/metadata/{path}
Set the metadata on a single column of a data frame, the HTTP equivalent of `oxen schemas add -c -m '{...}'`. Staged into the workspace, so no commit is required. Replaces that column's existing metadata wholesale.
# Set the metadata on a data frame's schema itself β the file-level
counterpart to the per-column metadata endpoint. Staged into the
workspace, so no commit is required.
Source: https://docs.oxen.ai/http-api/workspace-data-frames/set-the-metadata-on-a-data-frames-schema-itself-β-the-file-levelcounterpart-to-the-per-column-metadata-endpoint-staged-into-theworkspace-so-no-commit-is-required
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/data_frames/schema/{path}
Set the metadata on a data frame's schema itself, the HTTP equivalent of `oxen schemas add -m '{...}'`. Staged into the workspace, so no commit is required. Replaces any existing schema metadata wholesale.
# Add files to workspace
Source: https://docs.oxen.ai/http-api/workspace-files/add-files-to-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files/{path}
Upload and stage files to a workspace. Accept a multipart with either gzipped or uncompressed file parts. Use the filename from the file part and compute the file hash from the content.
# Get file from workspace
Source: https://docs.oxen.ai/http-api/workspace-files/get-file-from-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files/{path}
Get a file from a workspace.
# List staged changes in a workspace
Source: https://docs.oxen.ai/http-api/workspace-files/list-staged-changes-in-a-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/changes
List the staged changes (added, modified, and removed files) in a workspace. The added, modified, and removed lists are each paginated independently, with the same page and page_size applied to each list.
# List staged changes under a directory in a workspace
Source: https://docs.oxen.ai/http-api/workspace-files/list-staged-changes-under-a-directory-in-a-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/changes/{path}
List the staged changes (added, modified, and removed files) under a directory in a workspace. The added, modified, and removed lists are each paginated independently, with the same page and page_size applied to each list.
# Stage files for removal
Source: https://docs.oxen.ai/http-api/workspace-files/stage-files-for-removal
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files
Stage files for removal from the repository. Accepts both files and directories.
# Stage files to workspace
Source: https://docs.oxen.ai/http-api/workspace-files/stage-files-to-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files/batch/{directory}
Stage file nodes to a workspace. Do not upload file contents to the repository.
# Unstage a file from the workspace
Source: https://docs.oxen.ai/http-api/workspace-files/unstage-a-file-from-the-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/changes/{path}
Unstage a file from workspace staging
# Unstage files
Source: https://docs.oxen.ai/http-api/workspace-files/unstage-files
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/changes
Unstage files from a workspace. Accepts both files and directories.
# Check workspace mergeability
Source: https://docs.oxen.ai/http-api/workspaces/check-workspace-mergeability
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/merge/{branch}
Checks if a workspace can be committed and merged onto a branch
# Clear workspaces for repo
Source: https://docs.oxen.ai/http-api/workspaces/clear-workspaces-for-repo
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/workspaces/clear
Deletes all workspaces for the repo
# Delete workspace
Source: https://docs.oxen.ai/http-api/workspaces/delete-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json delete /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}
Delete a workspace by ID
# Get or create workspace
Source: https://docs.oxen.ai/http-api/workspaces/get-or-create-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json put /api/repos/{namespace}/{repo_name}/workspaces/get_or_create
Create a workspace. If the workspace exists, return it
# Get workspace
Source: https://docs.oxen.ai/http-api/workspaces/get-workspace
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}
Get an existing workspace by ID
# List workspaces
Source: https://docs.oxen.ai/http-api/workspaces/list-workspaces
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json get /api/repos/{namespace}/{repo_name}/workspaces
List workspaces in the repository
# Merge workspace into branch
Source: https://docs.oxen.ai/http-api/workspaces/merge-workspace-into-branch
https://hub.oxen.ai/api/_spec/oxen_server_openapi.json post /api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/merge/{branch}
Commit and merge workspace into the specified branch
# Inference API Overview
Source: https://docs.oxen.ai/inference-api/overview
Run inference on hundreds of AI models through a unified API for text, image, and video generation
## What is the Inference API?
The Inference API gives you access to hundreds of AI models through a single, consistent interface. Generate text, images, and videos without managing infrastructure or juggling multiple provider SDKs.
**Capabilities:**
* **Text Generation**: Chat completions, tool calling, vision, audio, documents (PDFs), structured output
* **Image Generation**: Text-to-image, image-to-image editing
* **Video Generation**: Text-to-video, image-to-video, reference-to-video, video-to-video editing
## Where to find things
| Looking for... | Go to |
| --------------------------------------- | ----------------------------------------------------------------- |
| General API info | [Keep reading below](#authentication) |
| Getting started fast | [Quick Starts](#quick-starts) |
| Endpoint specs and parameters | [API Reference](#api-reference) |
| Running inference with a specific model | [Model API References](/inference-api/reference/model-references) |
| Model discovery | [Models page](https://www.oxen.ai/ai/models) |
## Quick Starts
Text generation in minutes
Text-to-image in minutes
Text-to-video in minutes
Generate in background
## API Reference
Text generation, vision, audio, documents, tool calling
Text-to-image generation
Edit images with text prompts
Text-to-video, image-to-video, multi-shot
Background image/video generation
List, search, and manage models
## Individual Model API References
Sample requests, parameter tables, and workbench links for every model.
## Individual Model Walkthroughs
Multi-shot with references
Text-guided video edits
Mixed-reference video
Upscale and restore to 4K
***
## Authentication
All requests require a bearer token:
```bash theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://hub.oxen.ai/api/ai/...
```
Get your API key from your [account settings](https://oxen.ai/settings/profile).
## Base URL
All inference endpoints live under:
```
https://hub.oxen.ai/api/ai
```
If you're using the OpenAI SDK, set the base URL to `https://hub.oxen.ai/api/ai`. The SDK appends `/chat/completions` automatically.
## Endpoints
| Endpoint | Method | Description |
| --------------------------- | ------ | ---------------------------------------------------------- |
| `/ai/chat/completions` | POST | Text generation (chat, vision, documents, tool use) |
| `/ai/images/generate` | POST | Image generation |
| `/ai/images/edit` | POST | Image editing |
| `/ai/videos/generate` | POST | Video generation |
| `/ai/queue` | POST | Async image/video generation |
| `/ai/queue` | GET | List generations (active by default, filterable by status) |
| `/ai/queue/:generation_id` | GET | Get generation status and result |
| `/ai/queue/:generation_id` | DELETE | Cancel a queued generation |
| `/ai/models` | GET | List available models |
| `/ai/models/:id` | GET | Get model details and parameter schema |
| `/ai/models/search` | GET | Search models by name |
| `/ai/models/:id/activate` | POST | Activate a custom model deployment |
| `/ai/models/:id/deactivate` | POST | Deactivate a custom model deployment |
## Common Parameters
These parameters are accepted across multiple endpoints:
| Parameter | Type | Description |
| ------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | Required. The model to use (e.g. `claude-sonnet-4-6`, `flux-2-dev`, `kling-video-o3-pro-reference-to-video`). |
| `response_format` | string | `"url"` (default) returns a hosted URL. `"b64_json"` returns base64-encoded bytes inline. Supported on image and video endpoints. |
| `target_namespace` | string | Namespace to save results and bill to. Defaults to your user. Can be an organization name. |
## Discovering Models
List all models, optionally filtered by developer:
```bash theme={null}
# All models
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models"
# Search by name
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models/search?search=kling"
```
Get full details for a specific model (including its parameter schema):
```bash theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models/kling-video-o3-pro-reference-to-video"
```
The response includes a `request_schema` field with the complete parameter definitions, types, defaults, and constraints for that model.
## Pricing
Pricing varies by model:
| Method | How it works | Examples |
| ------------------------- | ------------------------------- | -------------------------- |
| `token` | Per input/output token | GPT, Claude, Gemini |
| `time` | Per second of compute time | Custom models, Llama, Qwen |
| `per_image` | Fixed cost per image | FLUX, DALL-E |
| `per_video_output_second` | Cost per second of output video | Kling, Sora |
Check the [model detail endpoint](/inference-api/reference/models/overview#retrieve-model) for exact pricing. Relevant fields: `input_cost_per_token`, `output_cost_per_token`, `cost_per_image`, `cost_per_second`, `cost_per_second_with_audio`, `cost_per_second_high_res`.
## Error Format
Errors use one of two formats:
```json theme={null}
{
"error": {
"type": "invalid_params",
"title": "Invalid parameters supplied, please check your request and try again.",
"detail": "Specific error details"
},
"status": "error",
"status_message": "invalid_params"
}
```
```json theme={null}
{
"error": {
"message": "Model not found: bad-model-name"
}
}
```
Common error types: `unauthenticated`, `invalid_params`, `resource_not_found`, `unknown_error`.
Need help? Join our [Discord community](https://discord.com/invite/s3tBEn7Ptg).
# Async Queue
Source: https://docs.oxen.ai/inference-api/quickstart/async-queue
Enqueue image and video generations that run in the background
## Overview
Video generation can take minutes. The async queue returns immediately with a generation ID so you can avoid long-lived HTTP connections, run generations in parallel, and build progress-tracking UIs. You can either poll the queue or use SSE to receive events when generations complete.
## Enqueue a Job
```python Python theme={null}
import requests
API_KEY = "YOUR_API_KEY"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A sunset timelapse over the ocean",
"duration": 5,
},
)
generations = response.json()["generations"]
print(f"Enqueued {len(generations)} generation(s)")
for g in generations:
print(g["generation_id"], g["status"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A sunset timelapse over the ocean",
"duration": 5
}'
```
## Poll Until Done
Poll a generation by ID until its `status` reaches a terminal value (`succeeded`, `failed`, or `cancelled`). The response includes `result_url` on success and `error_message` on failure.
```python Python theme={null}
import time
generation_id = generations[0]["generation_id"]
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
print(f"Status: {data['status']}")
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Done! Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/GENERATION_ID"
```
You can also list all active generations with `GET /ai/queue` to see how many are still in progress:
```bash theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://hub.oxen.ai/api/ai/queue?model=kling-video-v2-6-pro-text-to-video"
```
## Fetch a Single Job
Use the generation ID to get a specific job's full status, including `result_url` on success:
```bash theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/GENERATION_ID"
```
## What's Next
* [Async Queue Reference](/inference-api/reference/async_queue) for the full parameter list, polling details, and cancellation
* [Video Generation Quick Start](/inference-api/quickstart/video-generation) for the synchronous counterpart
* [Model API References](/inference-api/reference/model-references) for per-model parameters you can pass through the queue
# Chat Completions
Source: https://docs.oxen.ai/inference-api/quickstart/chat
Generate text with language models in minutes
## Overview
Generate text responses from language models using the OpenAI-compatible chat completions API. Supports streaming, vision, audio, documents (PDFs), tool calling, and structured output.
## Minimal Example
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "What is Oxen.ai?"}],
max_tokens=200,
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{"role": "user", "content": "What is Oxen.ai?"}],
"max_tokens": 200
}'
```
## With Streaming
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
stream = client.chat.completions.create(
model="gemini-3-1-flash-lite-preview",
messages=[{"role": "user", "content": "Write a haiku about data"}],
stream=True,
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
print()
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-flash-lite-preview",
"messages": [{"role": "user", "content": "Write a haiku about data"}],
"stream": true
}'
```
## With a Document (PDF)
Attach a PDF with a `file` content part. Oxen.ai translates the document to each
provider's native format, so it works the same regardless of which model you
call.
```python Python theme={null}
import base64
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
with open("report.pdf", "rb") as f:
file_data = "data:application/pdf;base64," + base64.standard_b64encode(f.read()).decode()
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "report.pdf", "file_data": file_data}},
{"type": "text", "text": "Summarize the key findings."},
],
}
],
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
FILE_DATA="data:application/pdf;base64,$(base64 < report.pdf | tr -d '\n')"
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "report.pdf", "file_data": "'"$FILE_DATA"'"}},
{"type": "text", "text": "Summarize the key findings."}
]
}]
}'
```
### Reference a document by URL
You can also point at a PDF by URL (handy for files generated in a
[workspace](/concepts/workspaces)):
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
pdf_url = "https://arxiv.org/pdf/1706.03762"
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [
{"type": "file", "file": {"file_url": pdf_url}},
{"type": "text", "text": "Summarize the key findings."},
],
}
],
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{
"role": "user",
"content": [
{"type": "file", "file": {"file_url": "https://arxiv.org/pdf/1706.03762"}},
{"type": "text", "text": "Summarize the key findings."}
]
}]
}'
```
The URL must be publicly accessible (and unexpired, if presigned). Only
`application/pdf` is supported, and a document must be 24 MB or smaller;
other inputs return a `400`. See the
[reference](/inference-api/reference/chat_completions) for details.
## What's Next
* [Chat Completions Reference](/inference-api/reference/chat_completions) for the full parameter list
* [Image Generation Quick Start](/inference-api/quickstart/image-generation) to generate images
* [Video Generation Quick Start](/inference-api/quickstart/video-generation) to generate videos
# Image Generation
Source: https://docs.oxen.ai/inference-api/quickstart/image-generation
Generate images from text prompts in minutes
## Overview
Generate images from text descriptions using models like FLUX and DALL-E.
## Minimal Example (Synchronous)
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/images/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A red cube on a white background, minimal",
},
)
data = response.json()
print("Image URL:", data["images"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/images/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A red cube on a white background, minimal"
}'
```
## With Base64 Response
Set `response_format` to `"b64_json"` to get the image bytes directly instead of a URL:
```python Python theme={null}
import requests
import base64
response = requests.post(
"https://hub.oxen.ai/api/ai/images/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A blue sphere on grey background",
"response_format": "b64_json",
},
)
data = response.json()
image_bytes = base64.b64decode(data["images"][0]["b64_json"])
with open("output.png", "wb") as f:
f.write(image_bytes)
print("Saved to output.png")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/images/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A blue sphere on grey background",
"response_format": "b64_json"
}'
```
## Async Generation (Recommended)
For longer-running image generation jobs, using the async queue avoids long-lived HTTP connections:
```python Python theme={null}
import requests
import time
API_KEY = "YOUR_API_KEY"
MODEL = "black-forest-labs-flux-2-klein-4b"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
# 1. Enqueue
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": MODEL,
"prompt": "A watercolor painting of a mountain landscape",
},
)
generation_id = response.json()["generations"][0]["generation_id"]
print(f"Enqueued generation: {generation_id}")
# 2. Poll until done
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
print(f"Status: {data['status']}")
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Done! Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
# 1. Enqueue
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A watercolor painting of a mountain landscape"
}'
# 2. Poll until done (replace GENERATION_ID with the id from the enqueue response)
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/GENERATION_ID"
```
## What's Next
* [Image Generation Reference](/inference-api/reference/image_generation) for the full parameter list
* [Image Editing Reference](/inference-api/reference/image_editing) to edit existing images
* [Async Queue](/inference-api/reference/async_queue) to generate multiple images in parallel
# Video Generation
Source: https://docs.oxen.ai/inference-api/quickstart/video-generation
Generate videos from text prompts in minutes
## Overview
Generate videos from text descriptions, reference images, or existing videos.
## Minimal Example (Synchronous)
Note: Synchronous generation can take 5-15 minutes to complete for certain models, we recommend using the async queue and polling for long-running jobs (See Below).
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A red balloon floating upward through blue sky",
"duration": 5,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A red balloon floating upward through blue sky",
"duration": 5
}'
```
## Async Generation (Recommended)
For video generation, using the async queue avoids long-lived HTTP connections:
```python Python theme={null}
import requests
import time
API_KEY = "YOUR_API_KEY"
MODEL = "kling-video-v2-6-pro-text-to-video"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
# 1. Enqueue
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": MODEL,
"prompt": "A sunset timelapse over the ocean",
"duration": 5,
},
)
generation_id = response.json()["generations"][0]["generation_id"]
print(f"Enqueued generation: {generation_id}")
# 2. Poll until done
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
print(f"Status: {data['status']}")
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Done! Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
# 1. Enqueue
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A sunset timelapse over the ocean",
"duration": 5
}'
# 2. Poll until done
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://hub.oxen.ai/api/ai/queue?model=kling-video-v2-6-pro-text-to-video"
```
## What's Next
* [Video Generation Reference](/inference-api/reference/video_generation) for the full parameter list
* [Kling O3 Pro: Reference to Video](/inference-api/reference/models/kling-video-o3-pro-reference-to-video) for multi-shot video with reference images
* [Async Queue Reference](/inference-api/reference/async_queue) for batch generation
# Async Queue
Source: https://docs.oxen.ai/inference-api/reference/async_queue
Enqueue image and video generation jobs that process in the background
## Why Use the Async Queue
The synchronous endpoints (`/ai/images/generate`, `/ai/videos/generate`) block until the result is ready. For video generation, this can be 1-10+ minutes. The async queue returns immediately with generation IDs so you can:
* Run many generations in parallel (up to 4 per request, no limit on total queued)
* Avoid long-lived HTTP connections
* Build progress-tracking UIs
* Query completed generations and their output URLs at any time
## Workflow
```
1. POST /ai/queue β Get generation IDs (status: queued)
2. GET /ai/queue or /ai/queue/:id β Poll until status is succeeded or failed
3. Read result_url from the completed generation
```
Generations persist after reaching a terminal state (`succeeded`, `failed`, or `cancelled`), so you can retrieve results at any time.
A Server-Sent Events stream at `GET /api/events` can also deliver completion notifications with the output file URL. See [Completion Events](#completion-events).
***
## Enqueue
```
POST /api/ai/queue
```
Submit an async image or video generation job.
### Required Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------------------------------------------------- |
| `model` | string | Must be an image or video model. Text-only models are rejected. |
### Additional Parameters
| Parameter | Type | Default | Description |
| ------------------ | ------- | ------------- | -------------------------------------------------------------------------------------------------------- |
| `num_generations` | integer | `1` | How many generations to enqueue per request. Range: 1-4. Call the endpoint multiple times to queue more. |
| `target_namespace` | string | your username | Namespace to store results and bill to. |
All other parameters (e.g. `prompt`, `multi_prompt`, `input_image`, `input_video`, `aspect_ratio`, `duration`, `seed`, `generate_audio`, `response_format`) are passed through to the model. Consult the model's `request_schema` via `GET /api/ai/models/:id` for supported parameters and their constraints.
### Response
```json theme={null}
{
"generations": [
{"generation_id": "bb8f5eb7-361e-4e13-ab73-67457bc8057e", "status": "queued"},
{"generation_id": "e9bede09-cd0e-46cf-bbcd-cb1a50099351", "status": "queued"}
]
}
```
### Validation
The API validates at enqueue time that:
* The model exists and has image or video output capability
* `num_generations` is 1-4
* The user is authenticated with sufficient credits
Model-specific parameter validation (prompt content, duration ranges, aspect ratio values) happens **when the generation runs**, not at enqueue time. If a parameter is invalid, the generation transitions to `failed` status with an `error_message`. To validate parameters and get immediate error feedback, use `/ai/images/generate` or `/ai/videos/generate` instead.
***
## List Generations
```
GET /api/ai/queue
```
Lists generations for the authenticated user's namespace. By default, only active generations (`queued` or `processing`) are returned. Pass an explicit `status` filter to include terminal states.
### Query Parameters
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `namespace` | string | no | Namespace to query. Defaults to the authenticated user. |
| `model` | string | no | Filter by model name. |
| `status` | string | no | Filter by status: `queued`, `processing`, `succeeded`, `failed`, or `cancelled`. When omitted, only active generations are returned. |
| `media_type` | string | no | Filter by `image` or `video`. |
| `repo` | string | no | Filter by target repository name. |
| `folder` | string | no | Filter by target directory. |
### Response
```json theme={null}
{
"count": 2,
"generations": [
{
"generation_id": "7cf9b23a-1234-5678-9abc-def012345678",
"model_name": "kling-video-o3-pro-reference-to-video",
"prompt": "An astronaut walking on Mars",
"media_type": "video",
"status": "processing",
"result_url": null,
"error_message": null,
"enqueued_at": 1775091431,
"started_at": 1775091432,
"completed_at": null,
"aspect_ratio": "16:9",
"duration": 10
},
{
"generation_id": "bb8f5eb7-361e-4e13-ab73-67457bc8057e",
"model_name": "black-forest-labs-flux-2-klein-4b",
"prompt": "Abstract geometric pattern in blue and gold",
"media_type": "image",
"status": "queued",
"result_url": null,
"error_message": null,
"enqueued_at": 1775091440,
"started_at": null,
"completed_at": null
}
]
}
```
| Field | Always Present | Description |
| --------------- | -------------- | ----------------------------------------------------------------------- |
| `count` | yes | Number of generations in the response |
| `generation_id` | yes | Unique ID for this generation |
| `model_name` | yes | Model name |
| `prompt` | yes | Text prompt (from original request parameters) |
| `media_type` | yes | `"image"` or `"video"` |
| `status` | yes | `"queued"`, `"processing"`, `"succeeded"`, `"failed"`, or `"cancelled"` |
| `result_url` | yes | Output file URL when succeeded, otherwise `null` |
| `error_message` | yes | Error details when failed, otherwise `null` |
| `enqueued_at` | yes | Unix timestamp when the job was enqueued |
| `started_at` | yes | Unix timestamp when processing began, or `null` |
| `completed_at` | yes | Unix timestamp when the job reached a terminal state, or `null` |
| `seed` | if submitted | Random seed |
| `aspect_ratio` | if submitted | Aspect ratio |
| `duration` | if submitted | Video duration |
Any additional parameters from the original enqueue request (e.g. `seed`, `aspect_ratio`, `duration`, `input_image`) are included in the response alongside the fields above.
### Polling Strategy
* **Image generation** (FLUX, etc.): typically completes in 5-30 seconds. Poll every 2-5 seconds.
* **Video generation** (Kling, etc.): typically takes 1-5 minutes. Poll every 10-30 seconds.
Poll until every generation's `status` is a terminal value (`succeeded`, `failed`, or `cancelled`), or until `count` reaches 0 when using the default active-only filter.
***
## Get Generation
```
GET /api/ai/queue/:generation_id
```
Retrieves metadata for a single generation. Includes `result_url` when the generation has succeeded and `error_message` when it has failed.
### Path Parameters
| Parameter | Type | Required | Description |
| --------------- | ------------- | -------- | --------------------------------------------------- |
| `generation_id` | string (UUID) | **yes** | The generation ID returned by the enqueue endpoint. |
### Response (in progress)
```json theme={null}
{
"generation_id": "7cf9b23a-1234-5678-9abc-def012345678",
"model_name": "kling-video-o3-pro-reference-to-video",
"prompt": "An astronaut walking on Mars",
"media_type": "video",
"status": "processing",
"result_url": null,
"error_message": null,
"enqueued_at": 1775091431,
"started_at": 1775091432,
"completed_at": null,
"aspect_ratio": "16:9",
"duration": 10
}
```
### Response (succeeded)
```json theme={null}
{
"generation_id": "7cf9b23a-1234-5678-9abc-def012345678",
"model_name": "kling-video-o3-pro-reference-to-video",
"prompt": "An astronaut walking on Mars",
"media_type": "video",
"status": "succeeded",
"result_url": "https://hub.oxen.ai/api/repos/...",
"error_message": null,
"enqueued_at": 1775091431,
"started_at": 1775091432,
"completed_at": 1775091590,
"aspect_ratio": "16:9",
"duration": 10
}
```
### Response (failed)
```json theme={null}
{
"generation_id": "7cf9b23a-1234-5678-9abc-def012345678",
"model_name": "kling-video-o3-pro-reference-to-video",
"prompt": "An astronaut walking on Mars",
"media_type": "video",
"status": "failed",
"result_url": null,
"error_message": "Insufficient credits",
"enqueued_at": 1775091431,
"started_at": 1775091432,
"completed_at": 1775091435
}
```
### Response (not found)
Returns 404 when the generation ID does not exist:
```json theme={null}
{
"error": {
"type": "resource_not_found",
"title": "The requested resource could not be found"
},
"status": "error",
"status_message": "resource_not_found"
}
```
***
## Cancel Generation
```
DELETE /api/ai/queue/:generation_id
```
Cancels a queued or in-progress generation.
### Path Parameters
| Parameter | Type | Required | Description |
| --------------- | ------------- | -------- | ---------------------------- |
| `generation_id` | string (UUID) | **yes** | The generation ID to cancel. |
### Response (success)
```json theme={null}
{
"status": "success",
"generation_id": "bb8f5eb7-361e-4e13-ab73-67457bc8057e"
}
```
### Response (not found)
Returns 404 when the generation ID does not exist:
```json theme={null}
{
"error": {
"type": "resource_not_found",
"title": "The requested resource could not be found"
},
"status": "error",
"status_message": "resource_not_found"
}
```
You can only cancel generations that are still active (`queued` or `processing`). Cancelling a generation that has already reached a terminal state (`succeeded`, `failed`, or `cancelled`) has no effect.
***
## Completion Events
```
GET /api/events
```
Server-Sent Events stream that emits `media_generation_completed` events when generations reach a terminal state.
### Connect
```
GET /api/events
Authorization: Bearer $OXEN_API_KEY
```
Response is `Content-Type: text/event-stream`. The server sends `: keep-alive\n\n` every 15 seconds when idle.
Events are broadcast to currently-connected subscribers with no buffering. Anything that fires before you connect is lost. Subscribe before calling `POST /ai/queue` to avoid missing events.
### Event: media\_generation\_completed
Fires once per generation on terminal state.
Success wire format:
```
event: media_generation_completed
data: {"generation_id":"bb8f5eb7-...","status":"succeeded","media_type":"video","model":"kling-video-o3-pro-reference-to-video","url":"https://hub.oxen.ai/api/repos/..."}
```
Failure:
```
event: media_generation_completed
data: {"generation_id":"bb8f5eb7-...","status":"failed","media_type":"video","error":"Insufficient credits"}
```
### Fields
| Field | succeeded | failed | Description |
| --------------- | ------------- | ---------- | --------------------------------------------------------------- |
| `generation_id` | yes | yes | Matches the ID returned by `POST /ai/queue` |
| `status` | `"succeeded"` | `"failed"` | Only these two values appear |
| `media_type` | yes | yes | `"image"` or `"video"` |
| `model` | yes | no | Model name |
| `url` | yes | no | Presigned URL to the output file. Expires after a limited time. |
| `error` | no | yes | Human-readable failure reason |
### Other events on this stream
`GET /api/events` is a user-scoped stream that may carry unrelated event types (e.g. deployment events). Filter on the `event:` line and ignore anything other than `media_generation_completed`.
### Example
```python Python theme={null}
import json
import requests
import threading
import time
API_KEY = "YOUR_API_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def listen(results):
with requests.get(
"https://hub.oxen.ai/api/events",
headers=HEADERS,
stream=True,
) as resp:
event_name = None
for line in resp.iter_lines(decode_unicode=True):
if line is None or line == "":
event_name = None
continue
if line.startswith(":"):
continue # keep-alive comment
if line.startswith("event:"):
event_name = line[6:].strip()
elif line.startswith("data:") and event_name == "media_generation_completed":
payload = json.loads(line[5:].strip())
results[payload["generation_id"]] = payload
# Start listening BEFORE enqueuing
results = {}
threading.Thread(target=listen, args=(results,), daemon=True).start()
print("SSE listener connected, waiting for events...")
# Enqueue
prompt = "A red cube"
model = "black-forest-labs-flux-2-klein-4b"
print(f"\nEnqueuing {model}")
print(f" prompt: \"{prompt}\"")
resp = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"model": model,
"prompt": prompt,
},
)
gen_id = resp.json()["generations"][0]["generation_id"]
print(f" generation_id: {gen_id}")
# Wait for completion event
start = time.time()
while gen_id not in results:
elapsed = time.time() - start
print(f" Waiting for SSE completion event... ({elapsed:.1f}s)")
time.sleep(2)
elapsed = time.time() - start
event = results[gen_id]
if event["status"] == "succeeded":
print(f"\nGeneration succeeded in {elapsed:.1f}s")
print(f" URL: {event['url']}")
else:
print(f"\nGeneration failed after {elapsed:.1f}s")
print(f" Error: {event['error']}")
```
```bash cURL theme={null}
curl -N -H "Authorization: Bearer $OXEN_API_KEY" \
https://hub.oxen.ai/api/events
```
### Terminal states without events
`media_generation_completed` does not fire for cancelled generations (you called `DELETE /ai/queue/:id`). You can still retrieve the final status of any generation via `GET /ai/queue/:id`.
***
## Examples
### Batch image generation with polling
```python Python theme={null}
import requests
import time
API_KEY = "YOUR_API_KEY"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
# Enqueue 4 images
model = "black-forest-labs-flux-2-klein-4b"
prompt = "Abstract geometric pattern in blue and gold"
print(f"Enqueuing 4 generations of {model}")
print(f" prompt: \"{prompt}\"")
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": model,
"prompt": prompt,
"num_generations": 4,
},
)
gen_ids = [g["generation_id"] for g in response.json()["generations"]]
for gid in gen_ids:
print(f" generation_id: {gid}")
# Poll individual generations until all reach a terminal status
terminal = {"succeeded", "failed", "cancelled"}
start = time.time()
while True:
statuses = {}
for gid in gen_ids:
resp = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{gid}",
headers=HEADERS,
).json()
statuses[gid] = resp["status"]
done = sum(1 for s in statuses.values() if s in terminal)
elapsed = time.time() - start
print(f" [{elapsed:5.1f}s] {done}/{len(gen_ids)} complete")
if done == len(gen_ids):
break
time.sleep(5)
elapsed = time.time() - start
print(f"\nAll {len(gen_ids)} images generated in {elapsed:.1f}s!")
for gid, s in statuses.items():
print(f" {gid}: {s}")
```
```bash cURL theme={null}
# Enqueue 4 images
curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "Abstract geometric pattern in blue and gold",
"num_generations": 4
}'
# Poll until all active generations are done
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue?model=black-forest-labs-flux-2-klein-4b")
COUNT=$(echo "$STATUS" | python3 -c "import json,sys; print(json.load(sys.stdin)['count'])")
echo "Active: $COUNT"
[ "$COUNT" -eq 0 ] && break
sleep 5
done
echo "All images generated!"
```
### Async video generation
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "Aerial view of waves crashing on a rocky shore", "duration": 5},
{"prompt": "Camera pulls back to reveal the full coastline", "duration": 5},
],
"aspect_ratio": "16:9",
},
)
print(response.json())
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "Aerial view of waves crashing on a rocky shore", "duration": 5},
{"prompt": "Camera pulls back to reveal the full coastline", "duration": 5}
],
"aspect_ratio": "16:9"
}'
```
### Poll a single generation by ID
```python Python theme={null}
import requests
import time
API_KEY = "YOUR_API_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# After enqueuing, grab a generation ID
generation_id = "YOUR_GENERATION_ID"
print(f"Polling generation {generation_id}")
terminal = {"succeeded", "failed", "cancelled"}
start = time.time()
poll_count = 0
while True:
poll_count += 1
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
elapsed = time.time() - start
status = data["status"]
if status in terminal:
print(f" [{elapsed:5.1f}s] Generation {status}")
if status == "succeeded":
print(f" result_url: {data['result_url']}")
elif status == "failed":
print(f" error: {data['error_message']}")
break
print(f" [{elapsed:5.1f}s] Poll #{poll_count} β {status} ({data['model_name']}, {data['media_type']})")
time.sleep(10)
```
```bash cURL theme={null}
# Check a specific generation
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GENERATION_ID"
# Cancel a generation
curl -X DELETE -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GENERATION_ID"
```
### End-to-end: enqueue, wait for SSE, download
```python Python theme={null}
import json
import queue
import requests
import threading
import time
API_KEY = "YOUR_API_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def listen(events):
with requests.get(
"https://hub.oxen.ai/api/events",
headers=HEADERS,
stream=True,
) as resp:
event_name = None
for line in resp.iter_lines(decode_unicode=True):
if not line:
event_name = None
continue
if line.startswith(":"):
continue
if line.startswith("event:"):
event_name = line[6:].strip()
elif line.startswith("data:") and event_name == "media_generation_completed":
events.put(json.loads(line[5:]))
# Subscribe before enqueuing so no events are missed
events = queue.Queue()
threading.Thread(target=listen, args=(events,), daemon=True).start()
print("SSE listener connected, waiting for events...")
# Enqueue
model = "black-forest-labs-flux-2-klein-4b"
prompt = "A cathedral in the clouds"
print(f"\nEnqueuing {model}")
print(f" prompt: \"{prompt}\"")
resp = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"model": model,
"prompt": prompt,
},
).json()
gen_id = resp["generations"][0]["generation_id"]
print(f" generation_id: {gen_id}")
# Wait for the matching completion event
start = time.time()
print(f"\nWaiting for SSE completion event...")
while True:
try:
event = events.get(timeout=3)
if event["generation_id"] == gen_id:
break
print(f" [{time.time() - start:5.1f}s] Received event for different generation, skipping...")
except queue.Empty:
print(f" [{time.time() - start:5.1f}s] Still waiting...")
elapsed = time.time() - start
if event["status"] == "succeeded":
print(f"\nGeneration succeeded in {elapsed:.1f}s")
print(f" Downloading {event['url']}")
with open("output.png", "wb") as f:
f.write(requests.get(event["url"]).content)
print(" Saved output.png")
else:
print(f"\nGeneration failed after {elapsed:.1f}s")
print(f" Error: {event['error']}")
```
## Errors
| Condition | Error |
| ------------------------------ | ------------------------------------------------------ |
| `num_generations` out of range | `"num_generations must be an integer between 1 and 4"` |
| Model not found | `"Model not found: "` |
| Text-only model | `":unsupported_media_type"` |
| 404 on GET/DELETE | Generation ID does not exist |
# Chat Completions
Source: https://docs.oxen.ai/inference-api/reference/chat_completions
Generate text responses from language models with support for streaming, vision, audio, documents, and tool calling
## Endpoint
```
POST /api/ai/chat/completions
```
Compatible with the OpenAI chat completions format. Supports streaming, multimodal input (images, video, audio, and documents), tool calling, and structured output.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| --------------------- | ------------- | -------- | ------- | -------------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | Model name (e.g. `claude-sonnet-4-6`, `gpt-5-4-2026-03-05`, `gemini-3-1-flash-lite-preview`) |
| `messages` | array | **yes** | -- | Array of message objects. Must not be empty. |
| `stream` | boolean | no | `false` | Stream the response as server-sent events. |
| `max_tokens` | integer | no | varies | Maximum tokens in the response. |
| `temperature` | number | no | varies | Sampling temperature (0-2). |
| `top_p` | number | no | -- | Nucleus sampling parameter. |
| `frequency_penalty` | number | no | -- | Penalize repeated tokens. |
| `presence_penalty` | number | no | -- | Penalize tokens already present. |
| `tools` | array | no | -- | Tool/function definitions for tool calling. |
| `tool_choice` | string/object | no | -- | Control tool selection behavior. |
| `parallel_tool_calls` | boolean | no | -- | Allow parallel tool calls. |
| `response_format` | object | no | -- | Constrain response format (e.g. `{"type": "json_object"}`). Support varies by provider. |
## Message Format
Each message has a `role` and `content`:
```json theme={null}
[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi there!"}
]
```
### Vision (multimodal)
Use a content array to include images or video:
```json theme={null}
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}}
]
}
```
Video input:
```json theme={null}
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this video"},
{"type": "video_url", "video_url": {"url": "https://example.com/clip.mp4"}}
]
}
```
Image and video URLs must be publicly accessible.
### Audio understanding
Send audio to a model that supports audio input with an `audio_url` content
part:
```json theme={null}
{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/audio/DoOrDoNot.m4a"}},
{"type": "text", "text": "What is said in this clip?"}
]
}
```
You may also inline the audio as a base64
[data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs):
```json theme={null}
{"type": "audio_url", "audio_url": {"url": "data:audio/mp3;base64,SUQzBAAAAAA..."}}
```
The URL must be publicly accessible (and unexpired, if presigned). Audio must be
20 MB or smaller; larger files return a `400`. Place the audio part before the
text part for the best results.
**Supported formats vary by provider.** OpenAI audio models (e.g. `gpt-audio`)
accept only `wav` and `mp3`; an unsupported format returns a `400`. Gemini models
(e.g. `gemini-3-1-pro-preview`) additionally accept `m4a`, `aac`, `ogg`, and
`flac`.
### Files and documents (PDFs)
Attach a document to a message with a `file` content part. Pass the file inline
as a base64 [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)
in `file.file_data`:
```json theme={null}
{
"role": "user",
"content": [
{
"type": "file",
"file": {
"filename": "report.pdf",
"file_data": "data:application/pdf;base64,JVBERi0xLjQK..."
}
},
{"type": "text", "text": "Summarize the key findings in this document."}
]
}
```
You may also reference a document by URL, useful for files generated in a
[workspace](/concepts/workspaces):
```json theme={null}
{
"type": "file",
"file": {"file_url": "https://arxiv.org/pdf/1706.03762"}
}
```
Pass exactly one of `file_data` (a base64 data URL) or `file_url`. The
URL must be publicly accessible (and unexpired, if presigned). A document must be
24 MB or smaller; larger files return a `400`. Place the document before the text
part for the best results.
**Supported file types:** `application/pdf`. Requesting an unsupported file type
returns a `400` error. Referencing files by OpenAI `file_id` is not supported.
Inline the file with `file_data` or pass `file_url`.
A 400 response for an unsupported type looks like:
```json theme={null}
{
"error": {
"type": "invalid_file_input",
"title": "The provided file could not be used.",
"detail": "Unsupported file type 'image/tiff'. Supported file types: application/pdf."
},
"status": "error",
"status_message": "invalid_file_input",
"status_description": "The provided file could not be used."
}
```
## Examples
### Basic text generation
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Say hello in exactly 3 words."}],
max_tokens=50,
temperature=0.1,
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Say hello in exactly 3 words."}],
"max_tokens": 50,
"temperature": 0.1
}'
```
### Response
```json theme={null}
{
"id": "chatcmpl-97eab7db-fe67-4b29-900c-ed5260c654d4",
"object": "chat.completion",
"created": 1775090332,
"model": "claude-sonnet-4-6",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello, how are you?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 5,
"total_tokens": 20
}
}
```
### Analyze a PDF
```python Python theme={null}
import base64
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
with open("report.pdf", "rb") as f:
file_data = "data:application/pdf;base64," + base64.standard_b64encode(f.read()).decode()
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "report.pdf", "file_data": file_data}},
{"type": "text", "text": "Summarize the key findings in this document."},
],
}
],
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
FILE_DATA="data:application/pdf;base64,$(base64 < report.pdf | tr -d '\n')"
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "report.pdf", "file_data": "'"$FILE_DATA"'"}},
{"type": "text", "text": "Summarize the key findings in this document."}
]
}]
}'
```
### Understand audio
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="gemini-3-1-pro-preview",
messages=[
{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/audio/DoOrDoNot.m4a"}},
{"type": "text", "text": "Transcribe this clip and summarize it in one sentence."},
],
}
],
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-pro-preview",
"messages": [{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/audio/DoOrDoNot.m4a"}},
{"type": "text", "text": "Transcribe this clip and summarize it in one sentence."}
]
}]
}'
```
To send a local file, base64-encode it into a `data:` URL:
```python Python theme={null}
import base64
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
with open("clip.mp3", "rb") as f:
audio_url = "data:audio/mp3;base64," + base64.standard_b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gemini-3-1-pro-preview",
messages=[
{
"role": "user",
"content": [
{"type": "audio_url", "audio_url": {"url": audio_url}},
{"type": "text", "text": "What is said in this clip?"},
],
}
],
)
print(response.choices[0].message.content)
```
### Streaming
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
stream = client.chat.completions.create(
model="gemini-3-1-flash-lite-preview",
messages=[{"role": "user", "content": "Say hello"}],
stream=True,
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
print()
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-1-flash-lite-preview",
"messages": [{"role": "user", "content": "Say hello"}],
"stream": true
}'
```
Returns server-sent events. Each chunk has a `delta` instead of a full `message`:
```
data: {"choices":[{"delta":{"content":"Hello"},"finish_reason":null,"index":0}],"created":1775090334,"id":"chatcmpl-...","model":"gemini-3-1-flash-lite-preview","object":"chat.completion.chunk"}
data: {"choices":[{"delta":{"content":" there"},"finish_reason":null,"index":0}],...}
data: [DONE]
```
### Tool calling
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="gpt-5-4-2026-03-05",
messages=[
{"role": "system", "content": "Use tools when appropriate."},
{"role": "user", "content": "What is the weather in San Francisco?"},
],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}],
)
tool_call = response.choices[0].message.tool_calls[0]
print(f"{tool_call.function.name}({tool_call.function.arguments})")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5-4-2026-03-05",
"messages": [
{"role": "system", "content": "Use tools when appropriate."},
{"role": "user", "content": "What is the weather in San Francisco?"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
}
}]
}'
```
When the model uses a tool, `finish_reason` is `"tool_calls"`:
```json theme={null}
{
"choices": [{
"finish_reason": "tool_calls",
"message": {
"content": null,
"role": "assistant",
"tool_calls": [{
"id": "call_GRNwPXnbuQW4Sa3QNB3FYkYw",
"index": 0,
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"San Francisco\"}"
}
}]
}
}]
}
```
### Structured output (JSON mode)
```python Python theme={null}
from openai import OpenAI
client = OpenAI(
base_url="https://hub.oxen.ai/api/ai",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="gpt-5-4-2026-03-05",
messages=[{"role": "user", "content": "List 3 colors as a JSON array"}],
response_format={"type": "json_object"},
max_tokens=100,
)
print(response.choices[0].message.content)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/chat/completions \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5-4-2026-03-05",
"messages": [{"role": "user", "content": "List 3 colors as a JSON array"}],
"response_format": {"type": "json_object"},
"max_tokens": 100
}'
```
## Errors
| Condition | Error |
| -------------------- | ------------------------------------ |
| No model specified | `"You must specify a model to call"` |
| Model not found | `"Model not found: "` |
| Empty messages | `"Messages array cannot be empty"` |
| Insufficient credits | Credit-related error message |
# Image Editing
Source: https://docs.oxen.ai/inference-api/reference/image_editing
Edit images using text prompts
## Endpoint
```
POST /api/ai/images/edit
```
Edit an existing image using a text prompt. The request blocks until the edited image is ready.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| ------------------ | ------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | Image editing model (e.g. `qwen-image-edit`, `nano-banana-2-edit`, `xai-grok-imagine-image-edit`) |
| `prompt` | string | **yes** | -- | Description of the edit to apply. |
| `input_image` | string/array | **yes** | -- | URL(s) of the image to edit. Some models accept an array; see [per-model reference](/inference-api/reference/model-references). |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded image bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
## Examples
### Basic edit
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/images/edit",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "qwen-image-edit",
"prompt": "Make the background blue",
"input_image": "https://example.com/my-photo.jpg",
},
)
data = response.json()
print("Image URL:", data["images"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/images/edit \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen-image-edit",
"prompt": "Make the background blue",
"input_image": "https://example.com/my-photo.jpg"
}'
```
### Multi-image input
Some models accept an array of URLs in `input_image`. Consult the per-model reference for how each model uses them:
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/images/edit",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "gpt-image-2-edit",
"prompt": "Combine these references into a single scene",
"input_image": [
"https://example.com/reference-a.png",
"https://example.com/reference-b.png",
],
},
)
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/images/edit \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2-edit",
"prompt": "Combine these references into a single scene",
"input_image": [
"https://example.com/reference-a.png",
"https://example.com/reference-b.png"
]
}'
```
Whether a model accepts a single image or an array of images is part of its schema. See each model's [API reference](/inference-api/reference/model-references) for the exact type.
### Response
Same format as `/ai/images/generate`:
```json theme={null}
{
"model": "qwen-image-edit",
"created": 1775090400,
"images": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../image.png?..."
}
]
}
```
## Errors
| Condition | Error |
| ------------------------ | ------------------------------------------------ |
| Image URL not accessible | `"... 403 Client Error: Forbidden for url: ..."` |
| Model not found | `"Model not found: "` |
The `input_image` URL must be publicly downloadable. URLs that require authentication or block automated access will fail. Data URIs (`data:image/...;base64,...`) work as an alternative but aren't recommended for production.
# Image Generation
Source: https://docs.oxen.ai/inference-api/reference/image_generation
Generate images from text prompts
## Endpoint
```
POST /api/ai/images/generate
```
Generates images synchronously. The request blocks until the image is ready (typically 5-30 seconds depending on the model).
## Request Parameters
| Parameter | Type | Required | Default | Description |
| --------------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | Image model name (e.g. `black-forest-labs-flux-2-klein-4b`, `flux-2-dev`) |
| `prompt` | string | **yes** | -- | Text description of the image to generate. |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded image bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
| `aspect_ratio` | string | no | -- | Aspect ratio (e.g. `"1:1"`, `"16:9"`, `"9:16"`, `"4:3"`, `"3:4"`). |
| `num_inference_steps` | integer | no | -- | Number of denoising steps. |
| `seed` | integer | no | -- | Reproducibility seed. |
Available parameters vary by model. Use the [model detail endpoint](/inference-api/reference/models/overview#retrieve-model) (`GET /api/ai/models/:id`) to see the `request_schema` for model-specific parameters.
## Examples
### Basic generation
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/images/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A red cube on a white background",
"aspect_ratio": "1:1",
"seed": 42,
},
)
data = response.json()
print("Image URL:", data["images"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/images/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "black-forest-labs-flux-2-klein-4b",
"prompt": "A red cube on a white background",
"aspect_ratio": "1:1",
"seed": 42
}'
```
### Response (`response_format: "url"`)
```json theme={null}
{
"model": "black-forest-labs-flux-2-klein-4b",
"created": 1775090372,
"images": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../image.png?..."
}
]
}
```
The URL is a temporary link that expires after a period of time.
### Response (`response_format: "b64_json"`)
```json theme={null}
{
"model": "black-forest-labs-flux-2-klein-4b",
"created": 1775090372,
"images": [
{
"b64_json": ""
}
]
}
```
## Errors
| Condition | Error |
| --------------- | --------------------------- |
| No prompt | `"Prompt cannot be empty"` |
| Model not found | `"Model not found: "` |
# Model API References
Source: https://docs.oxen.ai/inference-api/reference/model-references
Browse the full list of models available in the Oxen.AI inference API.
Every model exposed by the Oxen.AI inference API has a dedicated reference page with a sample request, Python snippet, and the full request schema. Models are grouped below by developer.
List, search, and fetch model details programmatically via `GET /api/ai/models`.
Filter by modality, search by name, and preview every model side-by-side before diving into a reference page.
## Alibaba Wan
* [Happy Horse - Image to Video](/inference-api/reference/models/happyhorse-1_0-i2v)
* [Happy Horse - Reference to Video](/inference-api/reference/models/happyhorse-1_0-r2v)
* [Happy Horse - Text to Video](/inference-api/reference/models/happyhorse-1_0-t2v)
* [Happy Horse - Video Edit](/inference-api/reference/models/happyhorse-1_0-video-edit)
* [Happy Horse 1.1 - Image to Video](/inference-api/reference/models/happyhorse-1_1-i2v)
* [Happy Horse 1.1 - Reference to Video](/inference-api/reference/models/happyhorse-1_1-r2v)
* [Happy Horse 1.1 - Text to Video](/inference-api/reference/models/happyhorse-1_1-t2v)
* [Wan2.1 1.3B - Text to Video](/inference-api/reference/models/wan-ai-wan2-1-t2v-1-3b-diffusers)
* [Wan2.1 14B - Text to Video](/inference-api/reference/models/wan-ai-wan2-1-t2v-14b-diffusers)
* [Wan2.2 A14B - Text to Video](/inference-api/reference/models/wan-ai-wan2-2-t2v-a14b-diffusers)
* [Wan2.2 5B - Text to Video](/inference-api/reference/models/wan-ai-wan2-2-ti2v-5b-diffusers)
* [WAN 2.6 - Image to Video](/inference-api/reference/models/wan-v2-6-image-to-video)
* [WAN 2.6 - Video to Video](/inference-api/reference/models/wan-v2-6-reference-to-video)
* [WAN 2.7 - Edit Video](/inference-api/reference/models/wan-v2-7-edit-video)
* [WAN 2.7 - Image to Video](/inference-api/reference/models/wan-v2-7-image-to-video)
* [WAN 2.7 - Reference to Video](/inference-api/reference/models/wan-v2-7-reference-to-video)
* [WAN 2.7 - Text to Video](/inference-api/reference/models/wan-v2-7-text-to-video)
* [WAN 3.0](/inference-api/reference/models/wan-v3-0-video)
## Anthropic
* [Claude Fable 5](/inference-api/reference/models/claude-fable-5)
* [Claude Opus 4.1](/inference-api/reference/models/claude-opus-4-1-20250805)
* [Claude Opus 4.5](/inference-api/reference/models/claude-opus-4-5-20251101)
* [Claude Opus 4.6](/inference-api/reference/models/claude-opus-4-6)
* [Claude Opus 4.7](/inference-api/reference/models/claude-opus-4-7)
* [Claude Opus 4.8](/inference-api/reference/models/claude-opus-4-8)
* [Claude Sonnet 4.5](/inference-api/reference/models/claude-sonnet-4-5)
* [Claude Sonnet 4.6](/inference-api/reference/models/claude-sonnet-4-6)
* [Claude Sonnet 5](/inference-api/reference/models/claude-sonnet-5)
## Black Forest Labs
* [FLUX.2 Klein 4B](/inference-api/reference/models/black-forest-labs-flux-2-klein-4b)
* [FLUX.2 Klein 9B](/inference-api/reference/models/black-forest-labs-flux-2-klein-9b)
* [FLUX.1 \[dev\]](/inference-api/reference/models/flux-1-dev)
* [FLUX.2 \[dev\]](/inference-api/reference/models/flux-2-dev)
* [FLUX.2 \[flex\]](/inference-api/reference/models/flux-2-flex)
* [FLUX.2 \[pro\]](/inference-api/reference/models/flux-2-pro)
* [FLUX 3 Video](/inference-api/reference/models/flux-3-video)
* [FLUX.1-Kontext \[dev\]](/inference-api/reference/models/flux-kontext-dev)
## ByteDance
* [Depth Anything Video](/inference-api/reference/models/bytedance-depth-anything-video)
* [Seed Audio 1.0](/inference-api/reference/models/bytedance-seed-audio-1-0)
* [Seedance 2.0 Fast - Image to Video](/inference-api/reference/models/bytedance-seedance-2-0-fast-image-to-video)
* [Seedance 2.0 Fast - Reference to Video](/inference-api/reference/models/bytedance-seedance-2-0-fast-reference-to-video)
* [Seedance 2.0 Fast - Text to Video](/inference-api/reference/models/bytedance-seedance-2-0-fast-text-to-video)
* [Seedance 2.0 - Image to Video](/inference-api/reference/models/bytedance-seedance-2-0-image-to-video)
* [Seedance 2.0 Mini - Image to Video](/inference-api/reference/models/bytedance-seedance-2-0-mini-image-to-video)
* [Seedance 2.0 Mini - Reference to Video](/inference-api/reference/models/bytedance-seedance-2-0-mini-reference-to-video)
* [Seedance 2.0 Mini - Text to Video](/inference-api/reference/models/bytedance-seedance-2-0-mini-text-to-video)
* [Seedance 2.0 - Reference to Video](/inference-api/reference/models/bytedance-seedance-2-0-reference-to-video)
* [Seedance 2.0 - Text to Video](/inference-api/reference/models/bytedance-seedance-2-0-text-to-video)
* [Seedance 2.5 - Image to Video](/inference-api/reference/models/bytedance-seedance-2-5-image-to-video)
* [Seedance 2.5 - Reference to Video](/inference-api/reference/models/bytedance-seedance-2-5-reference-to-video)
* [Seedance 2.5 - Text to Video](/inference-api/reference/models/bytedance-seedance-2-5-text-to-video)
* [Seedream 4.0](/inference-api/reference/models/bytedance-seedream-4)
* [Seedream 4.5](/inference-api/reference/models/bytedance-seedream-4-5)
* [Seedream 5.0 Lite](/inference-api/reference/models/bytedance-seedream-5-lite)
* [Seedream 5.0 Pro](/inference-api/reference/models/bytedance-seedream-5-pro)
## DeepSeek
* [DeepSeek V4 Flash](/inference-api/reference/models/deepseek-v4-flash)
* [DeepSeek V4 Pro](/inference-api/reference/models/deepseek-v4-pro)
## Google
* [Gemini 2.5 Flash](/inference-api/reference/models/gemini-2-5-flash)
* [Gemini 2.5 Pro](/inference-api/reference/models/gemini-2-5-pro)
* [Gemini 3.1 Pro Preview](/inference-api/reference/models/gemini-3-1-pro-preview)
* [Gemini 3 Flash](/inference-api/reference/models/gemini-3-flash-preview)
* [Gemini Omni Flash](/inference-api/reference/models/gemini-omni-flash-preview)
* [Gemma 4 31B](/inference-api/reference/models/gemma-4-31b-it)
* [Nano Banana Pro](/inference-api/reference/models/google-nano-banana-pro)
* [Veo 3.0](/inference-api/reference/models/google-veo-3)
* [Veo 3.1](/inference-api/reference/models/google-veo-3-1)
* [Veo 3.1 Fast](/inference-api/reference/models/google-veo-3-1-fast)
* [Veo 3.1 Lite](/inference-api/reference/models/google-veo-3-1-lite)
* [Nano Banana](/inference-api/reference/models/nano-banana)
* [Nano Banana 2](/inference-api/reference/models/nano-banana-2)
* [Nano Banana 2 Lite](/inference-api/reference/models/nano-banana-2-lite)
## Ideogram
* [Ideogram V4](/inference-api/reference/models/ideogram-v4)
## Kling
* [Kling O1 - Image to Video](/inference-api/reference/models/kling-video-o1-image-to-video)
* [Kling O1 - Reference to Video](/inference-api/reference/models/kling-video-o1-reference-to-video)
* [Kling O1 Edit - Video to Video](/inference-api/reference/models/kling-video-o1-video-to-video-edit)
* [Kling O3 Omni](/inference-api/reference/models/kling-video-o3-omni)
* [Kling v2.5 - Image to Video](/inference-api/reference/models/kling-video-v2-5-turbo-pro-image-to-video)
* [Kling 2.6 Pro - Image to Video](/inference-api/reference/models/kling-video-v2-6-pro-image-to-video)
* [Kling 2.6 Pro - Text to Video](/inference-api/reference/models/kling-video-v2-6-pro-text-to-video)
* [Kling 3.0 Pro: Motion Control](/inference-api/reference/models/kling-video-v3-pro-motion-control)
## Krea
* [Krea 2 Large](/inference-api/reference/models/krea-v2-large-text-to-image)
## Lightricks
* [LTX 2.3](/inference-api/reference/models/ltx-2-3)
* [LTX 2.3 Extend](/inference-api/reference/models/ltx-2-3-extend)
* [LTX-2.3 Pro](/inference-api/reference/models/ltx-2-3-pro)
* [LTX-2.3 Pro 22B IC-LoRA Union Control](/inference-api/reference/models/ltx-2-3-pro-22b-IC-LoRA-Union-Control)
* [LTX 2.3 Quality: Video to HDR](/inference-api/reference/models/ltx-2-3-quality-hdr)
* [LTX 2.3 Reframe](/inference-api/reference/models/ltx-2-3-reframe)
* [LTX 2.3 Retake](/inference-api/reference/models/ltx-2-3-retake)
* [LTX 2.5](/inference-api/reference/models/ltx-2-5)
## Luma AI
* [Luma Ray 3.2 - Image to Video](/inference-api/reference/models/luma-ray-v3-2-image-to-video)
* [Luma Ray 3.2 - Reframe](/inference-api/reference/models/luma-ray-v3-2-reframe)
* [Luma Ray 3.2 - Text to Video](/inference-api/reference/models/luma-ray-v3-2-text-to-video)
* [Luma Ray 3.2 - Video to Video](/inference-api/reference/models/luma-ray-v3-2-video-to-video)
## Meta
* [Muse Spark 1.1](/inference-api/reference/models/muse-spark-1-1)
* [Segment Anything 3 - Image](/inference-api/reference/models/sam-3-image)
* [Segment Anything 3 - Video](/inference-api/reference/models/sam-3-video)
## MiniMax
* [MiniMax H3](/inference-api/reference/models/minimax-h3)
## Mistral AI
* [Ministral 3B](/inference-api/reference/models/ministral-3b-latest)
* [Ministral 8B](/inference-api/reference/models/ministral-8b-latest)
* [Mistral Large 2](/inference-api/reference/models/mistral-large-2407)
* [Mistral Small 3.1](/inference-api/reference/models/mistral-small-2503)
* [Mistral 7B](/inference-api/reference/models/open-mistral-7b)
* [Mixtral 8x22B](/inference-api/reference/models/open-mixtral-8x22b)
* [Mixtral 8x7B](/inference-api/reference/models/open-mixtral-8x7b)
* [Pixtral 12B](/inference-api/reference/models/pixtral-12b)
## Moonshot AI
* [Kimi K3](/inference-api/reference/models/kimi-k3)
* [Kimi K2.5](/inference-api/reference/models/moonshotai-kimi-k2-5)
## NVIDIA
* [Nemotron 3 Super](/inference-api/reference/models/nvidia-nemotron-120b-a12b)
## OpenAI
* [GPT 4.1](/inference-api/reference/models/gpt-4-1-2025-04-14)
* [GPT 4.1 mini](/inference-api/reference/models/gpt-4-1-mini-2025-04-14)
* [GPT 4.1 nano](/inference-api/reference/models/gpt-4-1-nano-2025-04-14)
* [GPT 4o](/inference-api/reference/models/gpt-4o)
* [GPT 4o mini](/inference-api/reference/models/gpt-4o-mini)
* [GPT 5.1](/inference-api/reference/models/gpt-5-1-2025-11-13)
* [GPT 5.2](/inference-api/reference/models/gpt-5-2-2025-12-11)
* [GPT 5.2 Chat](/inference-api/reference/models/gpt-5-2-chat-latest)
* [GPT 5](/inference-api/reference/models/gpt-5-2025-08-07)
* [GPT 5.3 Chat](/inference-api/reference/models/gpt-5-3-chat-latest)
* [GPT 5.4](/inference-api/reference/models/gpt-5-4-2026-03-05)
* [GPT 5.4 Mini](/inference-api/reference/models/gpt-5-4-mini)
* [GPT 5.5](/inference-api/reference/models/gpt-5-5-2026-04-23)
* [GPT 5.5 Pro](/inference-api/reference/models/gpt-5-5-pro-2026-04-23)
* [GPT 5.6 Luna](/inference-api/reference/models/gpt-5-6-luna)
* [GPT 5.6 Sol](/inference-api/reference/models/gpt-5-6-sol)
* [GPT 5.6 Terra](/inference-api/reference/models/gpt-5-6-terra)
* [GPT 5 Mini](/inference-api/reference/models/gpt-5-mini)
* [GPT 5 Nano](/inference-api/reference/models/gpt-5-nano)
* [GPT Audio](/inference-api/reference/models/gpt-audio)
* [GPT Image 1.5](/inference-api/reference/models/gpt-image-1-5)
* [GPT Image 2](/inference-api/reference/models/gpt-image-2)
* [OpenAI/GPT-OSS-120B](/inference-api/reference/models/gpt-oss-120b)
* [o1](/inference-api/reference/models/o1)
* [o3](/inference-api/reference/models/o3-2025-04-16)
* [o3 mini](/inference-api/reference/models/o3-mini)
* [o4 mini](/inference-api/reference/models/o4-mini-2025-04-16)
* [OpenAI/GPT-OSS-20B](/inference-api/reference/models/openai-gpt-oss-20b)
* [Sora 2 Pro](/inference-api/reference/models/openai-sora-2-pro)
## Perplexity
* [Perplexity Sonar](/inference-api/reference/models/sonar)
* [Perplexity Sonar Deep Research](/inference-api/reference/models/sonar-deep-research)
* [Perplexity Sonar Pro](/inference-api/reference/models/sonar-pro)
* [Perplexity Sonar Reasoning Pro](/inference-api/reference/models/sonar-reasoning-pro)
## Qwen
* [Qwen Image](/inference-api/reference/models/qwen-image)
* [Qwen Image 2.0 Pro](/inference-api/reference/models/qwen-image-2)
* [Qwen Image - 2512](/inference-api/reference/models/qwen-image-2512)
* [Qwen Image Edit](/inference-api/reference/models/qwen-image-edit)
* [Qwen Image Edit - 2511](/inference-api/reference/models/qwen-image-edit-2511)
* [Qwen Image Edit - 2509](/inference-api/reference/models/qwen-image-edit-plus)
* [Qwen3.6 Plus](/inference-api/reference/models/qwen3-6-plus)
* [Qwen3.8 Max](/inference-api/reference/models/qwen3-8-max)
* [Qwen3 VL 2B - Instruct](/inference-api/reference/models/qwen3-vl-2b-instruct)
* [Qwen3 VL 4B - Instruct](/inference-api/reference/models/qwen3-vl-4b-instruct)
## Tongyi-MAI
* [Z-Image-Turbo](/inference-api/reference/models/z-image-turbo)
## Topaz Labs
* [Topaz Astra 2](/inference-api/reference/models/topazlabs-astra-2-video)
* [Topaz Bloom 2](/inference-api/reference/models/topazlabs-bloom-2-image)
* [Topaz Bloom](/inference-api/reference/models/topazlabs-bloom-image)
* [Topaz Hyperion 2](/inference-api/reference/models/topazlabs-hyperion-2-video)
* [Topaz Hyperion HDR](/inference-api/reference/models/topazlabs-hyperion-hdr-video)
* [Topaz Image Upscaler](/inference-api/reference/models/topazlabs-image-upscale)
* [Topaz Iris - Face Detail Video Upscaler](/inference-api/reference/models/topazlabs-iris-mq-video)
* [Topaz Proteus - Versatile Video Upscaler](/inference-api/reference/models/topazlabs-proteus-video)
* [Topaz Rhea - Fine Detail Video Upscaler](/inference-api/reference/models/topazlabs-rhea-video)
* [Topaz Starlight Precise 2.5](/inference-api/reference/models/topazlabs-upscale-starlight-2-5-video)
* [Topaz Video Upscaler](/inference-api/reference/models/topazlabs-video-upscale)
* [Topaz Wonder 3.5](/inference-api/reference/models/topazlabs-wonder-3-5-image)
* [Topaz Wonder 3](/inference-api/reference/models/topazlabs-wonder-3-image)
## xAI
* [Grok Imagine - Text to Image](/inference-api/reference/models/xai-grok-imagine-image)
* [Grok Imagine - Image Edit](/inference-api/reference/models/xai-grok-imagine-image-edit)
* [Grok Imagine Video 1.5 - Image to Video](/inference-api/reference/models/xai-grok-imagine-video-1-5-image-to-video)
* [Grok Imagine - Video Edit](/inference-api/reference/models/xai-grok-imagine-video-edit-video)
* [Grok Imagine - Image to Video](/inference-api/reference/models/xai-grok-imagine-video-image-to-video)
## Z AI
* [GLM 5](/inference-api/reference/models/zai-org-glm-5)
* [GLM 5.1](/inference-api/reference/models/zai-org-glm-5-1)
* [GLM 5.2](/inference-api/reference/models/zai-org-glm-5-2)
# Models
Source: https://docs.oxen.ai/inference-api/reference/models/overview
List, search, and manage AI models available for inference and fine-tuning
Sample requests, parameter tables, and workbench links for every model.
Filter by modality, search by name, and preview every model side-by-side.
## List Models
```
GET /api/ai/models
```
Lists all available models. OpenAI-compatible.
### Query Parameters
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | ---------------------------------------------------------- |
| `developer_name` | string | no | Filter by developer (e.g. `openai`, `anthropic`, `google`) |
| `action` | string | no | Filter by fine-tuning action type |
### Response
```json theme={null}
{
"object": "list",
"data": [
{
"id": "claude-sonnet-4-6",
"object": "model",
"created": 1750000000,
"owned_by": "oxen",
"display_name": "Claude Sonnet 4.6",
"description": "Anthropic's balanced model for a wide range of tasks",
"summary": "Balanced performance and speed",
"model_type": "base",
"endpoint": "/chat/completions",
"capabilities": {
"input": ["text", "image"],
"output": ["text"]
},
"pricing": {
"method": "token",
"input_cost_per_token": 3e-6,
"output_cost_per_token": 1.5e-5
},
"fine_tuning": null,
"deployments": [],
"developer": {
"name": "Anthropic",
"logo": "https://..."
},
"source_model": null,
"image_url": null,
"released_at": "2025-06-19T00:00:00Z",
"request_schema": null
}
]
}
```
### Examples
```python Python theme={null}
import requests
response = requests.get(
"https://hub.oxen.ai/api/ai/models",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
for model in response.json()["data"]:
print(f"{model['id']} ({model['endpoint']})")
```
```bash cURL theme={null}
# All models
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models"
# Filter by developer
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models?developer_name=openai"
```
***
## Search Models
```
GET /api/ai/models/search
```
Search models by name.
### Query Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------ |
| `search` | string | **yes** | Search query |
### Examples
```python Python theme={null}
import requests
response = requests.get(
"https://hub.oxen.ai/api/ai/models/search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"search": "kling"},
)
for model in response.json()["data"]:
print(f"{model['id']} - {model['display_name']}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models/search?search=kling"
```
***
## Retrieve Model
```
GET /api/ai/models/:id
```
Retrieves a model by name. Returns the full model object including the `request_schema` describing model-specific parameters.
### Path Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------------------------- |
| `id` | string | **yes** | Model name (e.g. `claude-sonnet-4-6`, `flux-2-dev`) |
### Query Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------ | -------- | -------------------------------------------------------------------------- |
| `deployment_status` | string | no | Pass `"live"` to refresh deployment status from provider before responding |
### Examples
```python Python theme={null}
import requests
response = requests.get(
"https://hub.oxen.ai/api/ai/models/kling-video-o3-pro-reference-to-video",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
model = response.json()
print(f"Endpoint: {model['endpoint']}")
print(f"Pricing: {model['pricing']}")
# Get the model's parameter schema
if model.get("request_schema"):
print(f"Parameters: {model['request_schema']}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models/kling-video-o3-pro-reference-to-video"
# With live deployment status refresh
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/models/my-custom-model?deployment_status=live"
```
***
## Activate Model
```
POST /api/ai/models/:id/activate
```
Activates an inactive custom model deployment. Base models are always active and do not need activation.
### Path Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `id` | string | **yes** | Model name |
### Examples
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/models/my-custom-model/activate",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
model = response.json()
print(f"Status: {model['deployments'][0]['status']}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/models/my-custom-model/activate \
-H "Authorization: Bearer $OXEN_API_KEY"
```
***
## Deactivate Model
```
POST /api/ai/models/:id/deactivate
```
Deactivates an active custom model deployment to stop billing.
### Path Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `id` | string | **yes** | Model name |
### Examples
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/models/my-custom-model/deactivate",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
model = response.json()
print(f"Status: {model['deployments'][0]['status']}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/models/my-custom-model/deactivate \
-H "Authorization: Bearer $OXEN_API_KEY"
```
***
## Model Object
Every endpoint above returns one or more model objects with this schema:
| Field | Type | Description |
| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Model identifier used in API calls (e.g. `claude-sonnet-4-6`) |
| `object` | string | Always `"model"` |
| `created` | integer | Unix timestamp when the model was registered |
| `owned_by` | string | `"oxen"` for base models, owner namespace for custom models |
| `display_name` | string | Human-readable name |
| `description` | string/null | Full description |
| `summary` | string/null | Brief summary |
| `model_type` | string | `"base"` or `"custom"` |
| `endpoint` | string | API endpoint to call: `"/chat/completions"`, `"/images/generate"`, or `"/videos/generate"` |
| `capabilities` | object | Input/output modalities, e.g. `input: ["text", "image"]`, `output: ["text"]` |
| `pricing` | object | See [Pricing](#pricing) below |
| `fine_tuning` | object/null | Fine-tuning config with `actions` and `cost_per_second`, or `null` if not fine-tuneable |
| `deployments` | array | Deployment status objects. Possible statuses: `active`, `inactive`, `deploying`, `deactivating`, `error`, `unknown`. Empty for base models. |
| `developer` | object/null | Developer info with `name` and `logo` fields |
| `source_model` | string/null | Base model this was fine-tuned from |
| `image_url` | string/null | Image asset URL |
| `released_at` | string/null | Release timestamp |
| `request_schema` | object/null | JSON Schema describing model-specific request parameters |
### Pricing
The `pricing` object describes how the model is billed:
| Field | Type | Description |
| ---------------------------- | ----------- | ------------------------------------------------------------------ |
| `method` | string | `"token"`, `"time"`, `"per_image"`, or `"per_video_output_second"` |
| `input_cost_per_token` | number/null | Cost per input token (token-based models) |
| `output_cost_per_token` | number/null | Cost per output token (token-based models) |
| `cost_per_second` | number/null | Cost per second (time-based models) |
| `cost_per_image` | number/null | Fixed cost per image (image generation models) |
| `cost_per_second_high_res` | number/null | High-resolution video cost per second |
| `cost_per_second_with_audio` | number/null | Video with audio cost per second |
## Errors
| Condition | Status | Error |
| ----------------- | ------ | --------------------------- |
| Model not found | 404 | `"Model not found: "` |
| Not authenticated | 401 | `"unauthenticated"` |
# Kling O3 Pro: Reference to Video
Source: https://docs.oxen.ai/inference-api/reference/models/walkthroughs/kling_o3_pro_reference_to_video
Generate videos from text prompts with optional reference images, multi-shot support, and native audio
Transforms reference images into dynamic video sequences. Preserves identity, layout, and text from reference images while adding realistic motion, camera movements, and scene progression. Supports multi-shot generation with per-shot prompts and durations, and optional native audio (Chinese/English).
**Model name:** `kling-video-o3-pro-reference-to-video`
## Endpoint
```
POST /api/ai/videos/generate
```
Video generation is synchronous, the request blocks until the video is ready (typically 1-5 minutes). It is recommended to use [`/ai/queue`](/inference-api/reference/async_queue) instead for long-running jobs, so that you don't have long running http requests.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| ------------------ | ---------------- | ---------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `model` | string | **yes** | -- | `"kling-video-o3-pro-reference-to-video"` |
| `prompt` | string | **one of** | -- | Single prompt for the video. Use this or `multi_prompt`, not both. Max 512 characters. |
| `multi_prompt` | array | **one of** | -- | Multi-shot prompts. See [multi\_prompt](#multi_prompt) below. |
| `duration` | integer | no | 5 | Duration in seconds when using `prompt`. |
| `input_image` | array of URIs | no | -- | Reference images for style/appearance (max 4 combined with elements). Reference in prompts as `@Image1`, `@Image2`, etc. |
| `start_image_url` | string (URI) | no | -- | First frame of the video. The model extends from this image. |
| `tail_image_url` | string (URI) | no | -- | Last frame of the video. Requires `start_image_url`. The model fills in between the frames. |
| `elements` | array of objects | no | -- | Structured element references for characters/objects. See [elements](#elements) below. |
| `negative_prompt` | string | no | `"blur, distort, and low quality"` | Text describing what to avoid in the generated video. |
| `aspect_ratio` | string | no | `"16:9"` | `"9:16"`, `"1:1"`, or `"16:9"`. |
| `generate_audio` | boolean | no | `false` | Generate native audio. Supports Chinese and English voice output. |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded video bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
### prompt vs multi\_prompt
Use **either** `prompt` or `multi_prompt`, not both. Sending both returns:
```
"Cannot provide both 'prompt' and 'multi_prompt'."
```
Sending neither (or an empty `multi_prompt: []`) returns:
```
"Either 'prompt' or 'multi_prompt' must be provided."
```
When using `prompt`, the duration defaults to 5 seconds. Override with `duration`:
```json theme={null}
{"model": "kling-video-o3-pro-reference-to-video", "prompt": "A flower blooming in timelapse", "duration": 10}
```
### multi\_prompt
Array of shot objects. Each shot generates a segment of the video.
| Field | Type | Required | Default | Description |
| ---------- | ------- | -------- | ------- | ----------------------------------------- |
| `prompt` | string | **yes** | -- | Prompt for this shot. Max 512 characters. |
| `duration` | integer | no | 5 | Duration of this shot in seconds (1-15). |
### Duration Constraints
| Constraint | Value |
| ---------------------- | -------------- |
| Minimum total duration | **3 seconds** |
| Maximum total duration | **15 seconds** |
| Maximum per shot | 15 seconds |
| Default per shot | 5 seconds |
Individual shots can be as short as 1 second, as long as the total across all shots is between 3 and 15 seconds.
| Configuration | Total | Result |
| ---------------------------------------------------------- | ----- | --------- |
| Single shot, `duration: 1` | 1s | **Fails** |
| Single shot, `duration: 2` | 2s | **Fails** |
| Single shot, `duration: 3` | 3s | Works |
| Two shots: `duration: 2` + `duration: 1` | 3s | Works |
| Two shots: `duration: 1` + `duration: 1` | 2s | **Fails** |
| Single shot, `duration: 15` | 15s | Works |
| Three shots: `duration: 5` + `duration: 5` + `duration: 5` | 15s | Works |
| Three shots: `duration: 5` + `duration: 5` + `duration: 6` | 16s | **Fails** |
When total duration is too short:
```
"duration value '2' is invalid. Try using duration='5' instead, as duration support may vary by model and mode."
```
When total duration exceeds 15 seconds:
```
"Total shot duration (16s) exceeds maximum allowed (15s)."
```
When a single shot exceeds 15 seconds:
```
"Input should be '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' or '15'"
```
### elements
Array of element objects for character/object reference. Use `@Element1`, `@Element2`, etc. in prompts.
| Field | Type | Required | Description |
| ---------------------- | ------------- | -------- | ------------------------------------------------ |
| `frontal_image_url` | string (URI) | **yes** | Front view of the reference object or character. |
| `reference_image_urls` | array of URIs | no | Additional angles. Max 3 images per element. |
Maximum 4 total images across all elements and `input_image` references.
## Examples
### Minimal: text only
`input_image` is optional. Without it the model generates purely from the prompt.
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"prompt": "A puppy runs through a park",
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"prompt": "A puppy runs through a park"
}'
```
### Single prompt with reference image
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"prompt": "A dog runs across a sunny field",
"input_image": ["https://example.com/dog.jpg"],
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"prompt": "A dog runs across a sunny field",
"input_image": ["https://example.com/dog.jpg"]
}'
```
### Multi-shot with reference image
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "A woman walks toward the camera smiling, cinematic lighting", "duration": 5},
{"prompt": "She turns and looks out a window, soft focus background", "duration": 5},
],
"input_image": ["https://example.com/reference-face.jpg"],
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "A woman walks toward the camera smiling, cinematic lighting", "duration": 5},
{"prompt": "She turns and looks out a window, soft focus background", "duration": 5}
],
"input_image": ["https://example.com/reference-face.jpg"]
}'
```
### With start/end frames and elements
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "@Element1 picks up a coffee cup from the table", "duration": 5},
],
"start_image_url": "https://example.com/first-frame.jpg",
"tail_image_url": "https://example.com/last-frame.jpg",
"elements": [
{
"frontal_image_url": "https://example.com/character-front.jpg",
"reference_image_urls": ["https://example.com/character-side.jpg"],
}
],
"aspect_ratio": "16:9",
"generate_audio": True,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "@Element1 picks up a coffee cup from the table", "duration": 5}
],
"start_image_url": "https://example.com/first-frame.jpg",
"tail_image_url": "https://example.com/last-frame.jpg",
"elements": [
{
"frontal_image_url": "https://example.com/character-front.jpg",
"reference_image_urls": ["https://example.com/character-side.jpg"]
}
],
"aspect_ratio": "16:9",
"generate_audio": true
}'
```
### Response (`response_format: "url"`)
```json theme={null}
{
"created": 1775090723,
"model": "kling-video-o3-pro-reference-to-video",
"videos": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../video.mp4?..."
}
]
}
```
The URL is a temporary link that expires after a period of time.
### Response (`response_format: "b64_json"`)
```json theme={null}
{
"created": 1775090723,
"model": "kling-video-o3-pro-reference-to-video",
"videos": [
{
"b64_json": ""
}
]
}
```
## Using with /ai/queue
Recommended for video generation. Returns immediately, processes in the background.
### Enqueue
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [{"prompt": "A person speaking into a microphone", "duration": 5}],
"generate_audio": True,
"num_generations": 2,
},
)
generations = response.json()["generations"]
for g in generations:
print(f"ID: {g['generation_id']}, Status: {g['status']}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [{"prompt": "A person speaking into a microphone", "duration": 5}],
"generate_audio": true,
"num_generations": 2
}'
```
### Poll
```python Python theme={null}
import requests
import time
generation_id = "4ef840a4-..."
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
A generation is done when its `status` is `succeeded`, `failed`, or `cancelled`. On success, `result_url` points to the output file.
### Cancel
```python Python theme={null}
import requests
generation_id = "4ef840a4-..."
response = requests.delete(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())
```
```bash cURL theme={null}
curl -X DELETE -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
## Errors
| Error | Cause | Fix |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------ | ------------------------------------ |
| `Getting model response error: 422 - Value error, Cannot provide both 'prompt' and 'multi_prompt'.` | Sent both fields | Use one or the other |
| `Getting model response error: 422 - Value error, Either 'prompt' or 'multi_prompt' must be provided.` | Neither sent, or empty array | Provide at least one |
| `Field required` | `multi_prompt` item missing `prompt` | Every shot needs a `prompt` string |
| `duration value '2' is invalid` | Total duration \< 3 seconds | Ensure total across shots >= 3 |
| `Total shot duration (16s) exceeds maximum allowed (15s)` | Total duration > 15 seconds | Keep total at 15 seconds or less |
| `Input should be '1', '2', ... or '15'` | Single shot > 15 | Keep each shot at 15 seconds or less |
| `num_generations must be an integer between 1 and 4` | Invalid count (via `/ai/queue`) | Use 1-4 |
## Other Kling Models
| Model | Input | Use Case | Cost/sec |
| ---------------------------------------- | -------------------- | --------------------------------- | -------- |
| `kling-video-v2-6-pro-text-to-video` | Text only | Simple text-to-video | \$0.070 |
| `kling-video-v2-6-pro-image-to-video` | Image | Animate a single image | \$0.070 |
| `kling-video-o3-pro-image-to-video` | Image + text | Higher quality image animation | \$0.224 |
| `kling-video-o3-pro-reference-to-video` | Images + text | Reference-conditioned, multi-shot | \$0.224 |
| `kling-video-o3-pro-video-to-video-edit` | Video | Edit existing video | \$0.336 |
| `kling-video-v3-pro-motion-control` | Text + image + video | Camera/motion control | \$0.168 |
The O3 Pro models produce higher quality output than v2.x but cost roughly 3x more per second.
# Kling O3 Edit: Video to Video
Source: https://docs.oxen.ai/inference-api/reference/models/walkthroughs/kling_o3_pro_video_to_video_edit
Edit existing videos using text prompts with optional reference images and character elements
Edit existing videos using text instructions. Describe what to change β add objects, swap characters, alter scenery β and the model re-renders the video accordingly. Supports reference images (`@Image1`, `@Image2`, β¦) and structured element references (`@Element1`, `@Element2`, β¦) for character/object consistency across the edit.
**Model name:** `kling-video-o3-pro-video-to-video-edit`
## Endpoint
```
POST /api/ai/videos/generate
```
Video editing is synchronous β the request blocks until the edited video is ready (typically 1β5 minutes).
It is recommended to use [`/ai/queue`](/inference-api/reference/async_queue) instead for long-running jobs, so that you don't have long running http requests.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| ------------------ | ---------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | `"kling-video-o3-pro-video-to-video-edit"` |
| `prompt` | string | **yes** | -- | Text description of what to generate or how to edit the video. |
| `input_video` | string (URI) | **yes** | -- | URL of the source video to edit. |
| `input_image` | array of URIs | no | -- | Reference images for style/appearance. Use `@Image1`, `@Image2`, etc. in the prompt to refer to them. |
| `elements` | array of objects | no | -- | Structured element references for characters/objects. See [elements](#elements) below. |
| `keep_audio` | boolean | no | `false` | Whether to keep the original audio from the source video. |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded video bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
### elements
Array of element objects for character/object reference. Use `@Element1`, `@Element2`, etc. in prompts.
| Field | Type | Required | Description |
| ---------------------- | ------------- | -------- | ------------------------------------------------ |
| `frontal_image_url` | string (URI) | **yes** | Front view of the reference object or character. |
| `reference_image_urls` | array of URIs | no | Additional angles. Max 3 images per element. |
## Examples
### Basic video edit
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "A red bird flies in and lands in-between the two birds on the wire",
"input_video": "https://example.com/birds-on-wire.mp4",
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "A red bird flies in and lands in-between the two birds on the wire",
"input_video": "https://example.com/birds-on-wire.mp4"
}'
```
### Edit with reference images
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "Replace the person with @Image1 walking in the same direction",
"input_video": "https://example.com/street-scene.mp4",
"input_image": ["https://example.com/character-reference.jpg"],
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "Replace the person with @Image1 walking in the same direction",
"input_video": "https://example.com/street-scene.mp4",
"input_image": ["https://example.com/character-reference.jpg"]
}'
```
### Edit with elements and keep audio
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "@Element1 replaces the main character in the scene",
"input_video": "https://example.com/original-scene.mp4",
"elements": [
{
"frontal_image_url": "https://example.com/character-front.jpg",
"reference_image_urls": [
"https://example.com/character-side.jpg",
"https://example.com/character-back.jpg",
],
}
],
"keep_audio": True,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "@Element1 replaces the main character in the scene",
"input_video": "https://example.com/original-scene.mp4",
"elements": [
{
"frontal_image_url": "https://example.com/character-front.jpg",
"reference_image_urls": [
"https://example.com/character-side.jpg",
"https://example.com/character-back.jpg"
]
}
],
"keep_audio": true
}'
```
### Response (`response_format: "url"`)
```json theme={null}
{
"created": 1775090723,
"model": "kling-video-o3-pro-video-to-video-edit",
"videos": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../video.mp4?..."
}
]
}
```
The URL is a temporary link that expires after a period of time.
### Response (`response_format: "b64_json"`)
```json theme={null}
{
"created": 1775090723,
"model": "kling-video-o3-pro-video-to-video-edit",
"videos": [
{
"b64_json": ""
}
]
}
```
## Using with /ai/queue
Recommended for video editing. Returns immediately, processes in the background.
### Enqueue
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "Change the background to a sunset beach",
"input_video": "https://example.com/my-video.mp4",
"num_generations": 2,
},
)
generations = response.json()["generations"]
for g in generations:
print(f"ID: {g['generation_id']}, Status: {g['status']}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-video-to-video-edit",
"prompt": "Change the background to a sunset beach",
"input_video": "https://example.com/my-video.mp4",
"num_generations": 2
}'
```
### Poll
```python Python theme={null}
import requests
import time
generation_id = "4ef840a4-..."
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
A generation is done when its `status` is `succeeded`, `failed`, or `cancelled`. On success, `result_url` points to the output file.
### Cancel
```python Python theme={null}
import requests
generation_id = "4ef840a4-..."
response = requests.delete(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())
```
```bash cURL theme={null}
curl -X DELETE -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
## Errors
| Error | Cause | Fix |
| ---------------------------------------------------- | --------------------------------- | ------------------------- |
| `Field required` | Missing `prompt` or `input_video` | Both are required |
| `Invalid URL` | Malformed `input_video` URL | Provide a valid video URL |
| `num_generations must be an integer between 1 and 4` | Invalid count (via `/ai/queue`) | Use 1β4 |
## Other Kling Models
| Model | Input | Use Case | Cost/sec |
| ---------------------------------------- | -------------------- | --------------------------------- | -------- |
| `kling-video-v2-6-pro-text-to-video` | Text only | Simple text-to-video | \$0.070 |
| `kling-video-v2-6-pro-image-to-video` | Image | Animate a single image | \$0.070 |
| `kling-video-o3-pro-image-to-video` | Image + text | Higher quality image animation | \$0.224 |
| `kling-video-o3-pro-reference-to-video` | Images + text | Reference-conditioned, multi-shot | \$0.224 |
| `kling-video-o3-pro-video-to-video-edit` | Video + text | Edit existing video | \$0.336 |
| `kling-video-v3-pro-motion-control` | Text + image + video | Camera/motion control | \$0.168 |
The O3 Pro models produce higher quality output than v2.x but cost roughly 3x more per second.
# Model Walkthroughs
Source: https://docs.oxen.ai/inference-api/reference/models/walkthroughs/overview
Deeper guides for models with workflows or parameters that benefit from a full worked example.
Walkthroughs go further than the per-model reference pages: they wire together a complete example end-to-end, explain when to use the model, and call out parameters that are easy to miss.
Every model has a dedicated API reference with a request builder, sample cURL and Python snippets, and the full parameter table. That's usually what you want if you just need to make a call.
## Available walkthroughs
Multi-shot video generation with reference images, per-shot prompts and durations, and optional native audio.
Text-guided video editing with reference images and character/element consistency.
Video from prompt plus reference images, videos, or audio, up to 720p and 15 seconds with synchronized sound.
Restore and upscale video to 1080p, 2K, or 4K with detail-preserving temporal consistency.
# Seedance 2.0: Reference to Video
Source: https://docs.oxen.ai/inference-api/reference/models/walkthroughs/seedance_2_reference_to_video
Generate videos from text prompts guided by reference images, videos, and audio
ByteDance Seedance 2.0 reference-to-video generates video from a text prompt guided by reference images, videos, and/or audio. Reference media are addressed in the prompt as `@Image1`, `@Image2`, `@Video1`, `@Video2`, `@Audio1`, etc. Supports resolutions up to 720p, durations from 4β15 seconds, and synchronized audio generation including sound effects, ambient sounds, and lip-synced speech.
**Model name:** `bytedance-seedance-2-0-reference-to-video`
## Endpoint
```
POST /api/ai/videos/generate
```
Video generation is synchronous β the request blocks until the video is ready (typically 1β5 minutes).
It is recommended to use [`/ai/queue`](/inference-api/reference/async_queue) instead for long-running jobs, so that you don't have long running http requests.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| ------------------ | ------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | `"bytedance-seedance-2-0-reference-to-video"` |
| `prompt` | string | **yes** | -- | Text prompt. Use `@Image1`, `@Video1`, `@Audio1`, etc. to reference input media. |
| `input_images` | array of URIs | no | -- | Reference images (JPEG, PNG, WebP). Max 30 MB each. Up to 9. Use `@Image1`, `@Image2`, β¦ in the prompt. |
| `input_videos` | array of URIs | no | -- | Reference videos (MP4, MOV). Up to 3. Combined duration must be 2β15 s, total size \< 50 MB. Resolution between \~480p and \~720p. Use `@Video1`, `@Video2`, β¦ in the prompt. |
| `input_audios` | array of URIs | no | -- | Reference audio (MP3, WAV). Up to 3 files. Combined duration β€ 15 s. Max 15 MB each. Requires at least one reference image or video. Use `@Audio1`, `@Audio2`, β¦ in the prompt. |
| `resolution` | string | no | `"720p"` | `"480p"` for faster generation, `"720p"` for higher quality. |
| `duration` | string | no | `"auto"` | Duration in seconds: `"auto"`, or `"4"` through `"15"`. |
| `generate_audio` | boolean | no | `true` | Generate synchronized audio (sound effects, ambient sounds, lip-synced speech). Cost is the same either way. |
| `aspect_ratio` | string | no | `"auto"` | `"auto"`, `"21:9"`, `"16:9"`, `"4:3"`, `"1:1"`, `"3:4"`, or `"9:16"`. |
| `seed` | integer | no | -- | Random seed for reproducibility. Results may still vary slightly. |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded video bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
### Reference Media Limits
| Modality | Max Count | Size Limit | Other Constraints |
| -------- | --------- | ----------- | ---------------------------------------------------------------- |
| Images | 9 | 30 MB each | JPEG, PNG, WebP |
| Videos | 3 | 50 MB total | MP4, MOV. Combined duration 2β15 s. Resolution \~480p to \~720p. |
| Audio | 3 | 15 MB each | MP3, WAV. Combined duration β€ 15 s. Requires β₯ 1 image or video. |
Total files across all modalities must not exceed 12.
### Duration
| Value | Behavior |
| -------------- | -------------------------------------------- |
| `"auto"` | Model decides based on prompt and references |
| `"4"` β `"15"` | Fixed duration in seconds |
## Examples
### Text-only prompt
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "A serene mountain lake at sunrise with mist rolling across the water",
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "A serene mountain lake at sunrise with mist rolling across the water"
}'
```
### With reference images
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 walks through a crowded market, browsing the stalls",
"input_images": ["https://example.com/character.jpg"],
"duration": "8",
"aspect_ratio": "16:9",
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 walks through a crowded market, browsing the stalls",
"input_images": ["https://example.com/character.jpg"],
"duration": "8",
"aspect_ratio": "16:9"
}'
```
### With reference video and audio
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 dances to the rhythm of @Audio1 in the style of @Video1",
"input_images": ["https://example.com/dancer.jpg"],
"input_videos": ["https://example.com/dance-reference.mp4"],
"input_audios": ["https://example.com/music.mp3"],
"resolution": "720p",
"duration": "10",
"generate_audio": True,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 dances to the rhythm of @Audio1 in the style of @Video1",
"input_images": ["https://example.com/dancer.jpg"],
"input_videos": ["https://example.com/dance-reference.mp4"],
"input_audios": ["https://example.com/music.mp3"],
"resolution": "720p",
"duration": "10",
"generate_audio": true
}'
```
### Portrait video at 480p
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 speaks directly to camera, warm studio lighting",
"input_images": ["https://example.com/speaker.jpg"],
"resolution": "480p",
"aspect_ratio": "9:16",
"duration": "6",
"generate_audio": True,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 speaks directly to camera, warm studio lighting",
"input_images": ["https://example.com/speaker.jpg"],
"resolution": "480p",
"aspect_ratio": "9:16",
"duration": "6",
"generate_audio": true
}'
```
### Response (`response_format: "url"`)
```json theme={null}
{
"created": 1775090723,
"model": "bytedance-seedance-2-0-reference-to-video",
"videos": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../video.mp4?..."
}
]
}
```
The URL is a temporary link that expires after a period of time.
### Response (`response_format: "b64_json"`)
```json theme={null}
{
"created": 1775090723,
"model": "bytedance-seedance-2-0-reference-to-video",
"videos": [
{
"b64_json": ""
}
]
}
```
## Using with /ai/queue
Recommended for video generation. Returns immediately, processes in the background.
### Enqueue
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 waves at the camera and smiles",
"input_images": ["https://example.com/person.jpg"],
"duration": "5",
"num_generations": 2,
},
)
generations = response.json()["generations"]
for g in generations:
print(f"ID: {g['generation_id']}, Status: {g['status']}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance-seedance-2-0-reference-to-video",
"prompt": "@Image1 waves at the camera and smiles",
"input_images": ["https://example.com/person.jpg"],
"duration": "5",
"num_generations": 2
}'
```
### Poll
```python Python theme={null}
import requests
import time
generation_id = "4ef840a4-..."
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
A generation is done when its `status` is `succeeded`, `failed`, or `cancelled`. On success, `result_url` points to the output file.
### Cancel
```python Python theme={null}
import requests
generation_id = "4ef840a4-..."
response = requests.delete(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())
```
```bash cURL theme={null}
curl -X DELETE -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
## Errors
| Error | Cause | Fix |
| ---------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------- |
| `Field required` | Missing `prompt` | Provide a text prompt |
| `Too many input files` | Total files across images, videos, audio > 12 | Reduce the number of reference files |
| `Audio requires at least one image or video` | `input_audios` provided without `input_images` or `input_videos` | Add at least one reference image or video |
| `Invalid duration` | Duration not `"auto"` or `"4"`β`"15"` | Use a supported duration value |
| `Invalid resolution` | Resolution not `"480p"` or `"720p"` | Use `"480p"` or `"720p"` |
| `num_generations must be an integer between 1 and 4` | Invalid count (via `/ai/queue`) | Use 1β4 |
# Topaz Starlight Precise 2.5
Source: https://docs.oxen.ai/inference-api/reference/models/walkthroughs/topaz_starlight_precise_2_5
Upscale and restore video to 1080p or 4K with detail-preserving temporal consistency
Video restoration and upscaling model focused on preserving fine detail and temporal consistency while improving clarity. Upscales input video to 1080p, 2K, or 4K and lets you control the output frame rate (up to 60 fps).
**Model name:** `topazlabs-upscale-starlight-2-5-video`
## Endpoint
```
POST /api/ai/videos/generate
```
Video upscaling is synchronous β the request blocks until the video is ready. Processing time depends on the input video length and target resolution.
It is recommended to use [`/ai/queue`](/inference-api/reference/async_queue) instead for long-running jobs, so that you don't have long running http requests.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| ------------------ | ------------ | -------- | ------------ | ------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | `"topazlabs-upscale-starlight-2-5-video"` |
| `input_video` | string (URI) | **yes** | -- | URL of the video to upscale. |
| `resolution` | string | no | `"4k"` | Target output resolution: `"1080p"`, `"2k"`, or `"4k"`. |
| `target_fps` | integer | no | `24` | Target frame rate (1β60 fps). |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded video bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
### Resolution and Pricing
| Resolution | Cost per Second |
| ---------- | --------------- |
| 1080p | \$0.0374 |
| 2K | \$0.1494 |
| 4K | \$0.1494 |
Higher resolutions (2K and 4K) are billed at the high-res rate.
## Examples
### Upscale to 4K (default)
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "topazlabs-upscale-starlight-2-5-video",
"input_video": "https://example.com/my-video.mp4",
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "topazlabs-upscale-starlight-2-5-video",
"input_video": "https://example.com/my-video.mp4"
}'
```
### Upscale to 1080p at 60 fps
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "topazlabs-upscale-starlight-2-5-video",
"input_video": "https://example.com/my-video.mp4",
"resolution": "1080p",
"target_fps": 60,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "topazlabs-upscale-starlight-2-5-video",
"input_video": "https://example.com/my-video.mp4",
"resolution": "1080p",
"target_fps": 60
}'
```
### Response (`response_format: "url"`)
```json theme={null}
{
"created": 1775090723,
"model": "topazlabs-upscale-starlight-2-5-video",
"videos": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../video.mp4?..."
}
]
}
```
The URL is a temporary link that expires after a period of time.
### Response (`response_format: "b64_json"`)
```json theme={null}
{
"created": 1775090723,
"model": "topazlabs-upscale-starlight-2-5-video",
"videos": [
{
"b64_json": ""
}
]
}
```
## Using with /ai/queue
Recommended for longer videos. Returns immediately, processes in the background.
### Enqueue
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "topazlabs-upscale-starlight-2-5-video",
"input_video": "https://example.com/my-video.mp4",
"resolution": "4k",
"target_fps": 30,
"num_generations": 1,
},
)
generations = response.json()["generations"]
for g in generations:
print(f"ID: {g['generation_id']}, Status: {g['status']}")
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/queue \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "topazlabs-upscale-starlight-2-5-video",
"input_video": "https://example.com/my-video.mp4",
"resolution": "4k",
"target_fps": 30,
"num_generations": 1
}'
```
### Poll
```python Python theme={null}
import requests
import time
generation_id = "4ef840a4-..."
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(10)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
```
```bash cURL theme={null}
curl -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
A generation is done when its `status` is `succeeded`, `failed`, or `cancelled`. On success, `result_url` points to the output file.
### Cancel
```python Python theme={null}
import requests
generation_id = "4ef840a4-..."
response = requests.delete(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())
```
```bash cURL theme={null}
curl -X DELETE -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/4ef840a4-..."
```
## Errors
| Error | Cause | Fix |
| ---------------------------------------------------- | ------------------------------- | -------------------------------- |
| `Field required` | Missing `input_video` | Provide a video URL |
| `Invalid resolution` | Unsupported resolution value | Use `"1080p"`, `"2k"`, or `"4k"` |
| `target_fps must be between 1 and 60` | FPS out of range | Use a value between 1 and 60 |
| `num_generations must be an integer between 1 and 4` | Invalid count (via `/ai/queue`) | Use 1β4 |
# Video Generation
Source: https://docs.oxen.ai/inference-api/reference/video_generation
Generate videos from text prompts, images, or other videos
## Endpoint
```
POST /api/ai/videos/generate
```
Generates videos synchronously. The request blocks until the video is ready, which can take 1-10+ minutes depending on the model and duration.
For long-running or batch generation, consider using the [async queue](/inference-api/reference/async_queue) instead.
## Request Parameters
| Parameter | Type | Required | Default | Description |
| ------------------ | ------------ | ---------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | **yes** | -- | Video model name (e.g. `kling-video-v2-6-pro-text-to-video`, `kling-video-o3-pro-reference-to-video`) |
| `prompt` | string | **one of** | -- | Text prompt. Use this or `multi_prompt`, not both. |
| `multi_prompt` | array | **one of** | -- | Multi-shot prompts with per-shot duration. See [Kling O3 Pro reference](/inference-api/reference/models/kling-video-o3-pro-reference-to-video) for details. |
| `duration` | integer | no | 5 | Video duration in seconds (when using `prompt`). |
| `aspect_ratio` | string | no | `"16:9"` | Aspect ratio (e.g. `"16:9"`, `"9:16"`, `"1:1"`). |
| `input_image` | string/array | no | -- | Reference image(s) for image-to-video or reference-to-video models. |
| `input_video` | string (URL) | no | -- | Reference video for video-to-video models. |
| `generate_audio` | boolean | no | `false` | Generate audio track (model-dependent). |
| `response_format` | string | no | `"url"` | `"url"` returns a hosted URL. `"b64_json"` returns base64-encoded video bytes inline. |
| `target_namespace` | string | no | current user | Namespace to save results and bill to. Can be an organization name. |
Additional parameters vary by model. Use the [model detail endpoint](/inference-api/reference/models/overview#retrieve-model) (`GET /api/ai/models/:id`) to see the `request_schema` for model-specific parameters.
Either `prompt` or `multi_prompt` is required. Sending both returns an error:
```
"Getting model response error: 422 - Value error, Cannot provide both 'prompt' and 'multi_prompt'."
```
Sending neither returns:
```
"Getting model response error: 422 - Value error, Either 'prompt' or 'multi_prompt' must be provided."
```
## Examples
### Basic text-to-video
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A red balloon floating upward through blue sky",
"duration": 5,
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-v2-6-pro-text-to-video",
"prompt": "A red balloon floating upward through blue sky",
"duration": 5
}'
```
### Response (`response_format: "url"`)
```json theme={null}
{
"model": "kling-video-v2-6-pro-text-to-video",
"created": 1775090508,
"videos": [
{
"url": "https://hub.oxen.ai/api/repos/.../files/.../video.mp4?..."
}
]
}
```
The URL is a temporary link that expires after a period of time.
### Response (`response_format: "b64_json"`)
```json theme={null}
{
"model": "kling-video-v2-6-pro-text-to-video",
"created": 1775090508,
"videos": [
{
"b64_json": ""
}
]
}
```
### Multi-shot video
```python Python theme={null}
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "Wide shot: a bird takes off from a branch", "duration": 5},
{"prompt": "Tracking shot: the bird soars through clouds", "duration": 5},
],
"aspect_ratio": "16:9",
},
)
data = response.json()
print("Video URL:", data["videos"][0]["url"])
```
```bash cURL theme={null}
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Authorization: Bearer $OXEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-video-o3-pro-reference-to-video",
"multi_prompt": [
{"prompt": "Wide shot: a bird takes off from a branch", "duration": 5},
{"prompt": "Tracking shot: the bird soars through clouds", "duration": 5}
],
"aspect_ratio": "16:9"
}'
```
## Important Notes
* Video generation can take several minutes. Very long generations may time out on your client side.
* For long-running or batch generation, use [`/ai/queue`](/inference-api/reference/async_queue) which returns immediately and processes in the background.
## Errors
| Condition | Error |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Both `prompt` and `multi_prompt` | `"Getting model response error: 422 - Value error, Cannot provide both 'prompt' and 'multi_prompt'."` |
| Neither `prompt` nor `multi_prompt` | `"Getting model response error: 422 - Value error, Either 'prompt' or 'multi_prompt' must be provided."` |
| Model not found | `"Model not found: "` |
# Clone
Source: https://docs.oxen.ai/python-api/clone
# oxen.clone
## clone
```python theme={null}
def clone(repo_id: str,
path: Optional[str] = None,
host: str = "hub.oxen.ai",
branch: str = "main",
scheme: str = "https",
all=False)
```
Clone a repository
**Arguments**:
* `repo_id` - `str`
Name of the repository in the format 'namespace/repo\_name'.
For example 'ox/chatbot'
* `path` - `Optional[str]`
The path to clone the repo to. Defaults to the name of the repository.
* `host` - `str`
The host to connect to. Defaults to 'hub.oxen.ai'
* `branch` - `str`
The branch name id to clone. Defaults to 'main'
* `scheme` - `str`
The scheme to use. Defaults to 'https'
* `all` - `bool`
Whether to clone the full commit history or not. Default: False
**Returns**:
[Repo](/python-api/repo)
A Repo object that can be used to interact with the cloned repo.
# Data frame
Source: https://docs.oxen.ai/python-api/data_frame
# oxen.data\_frame
## DataFrame Objects
```python theme={null}
class DataFrame()
```
The DataFrame class allows you to perform CRUD operations on a remote data frame.
If you pass in a [Workspace](/examples/data/workspaces) or a [RemoteRepo](/concepts/remote-repos) the data is indexed into DuckDB on an oxen-server without downloading the data locally.
## Examples
### CRUD Operations
Index a data frame in a workspace.
```python theme={null}
from oxen import DataFrame
# Connect to and index the data frame
# Note: This must be an existing file committed to the repo
# indexing may take a while for large files
data_frame = DataFrame("datasets/SpamOrHam", "data.tsv")
# Add a row
row_id = data_frame.insert_row({"category": "spam", "message": "Hello, do I have an offer for you!"})
# Get a row by id
row = data_frame.get_row_by_id(row_id)
print(row)
# Update a row
row = data_frame.update_row(row_id, {"category": "ham"})
print(row)
# Delete a row
data_frame.delete_row(row_id)
# Get the current changes to the data frame
status = data_frame.diff()
print(status.added_files())
# Commit the changes
data_frame.commit("Updating data.csv")
```
## \_\_init\_\_
```python theme={null}
def __init__(remote: Union[str, RemoteRepo, Workspace],
path: str,
host: str = "hub.oxen.ai",
branch: Optional[str] = None,
scheme: str = "https",
workspace_name: Optional[str] = None)
```
Initialize the DataFrame class. Will index the data frame
into duckdb on init.
Will throw an error if the data frame does not exist.
**Arguments**:
* `remote` - `str`, `RemoteRepo`, or `Workspace`
The workspace or remote repo the data frame is in.
* `path` - `str`
The path of the data frame file in the repository.
* `host` - `str`
The host of the oxen-server. Defaults to "hub.oxen.ai".
* `branch` - `Optional[str]`
The branch of the remote repo. Defaults to None.
* `scheme` - `str`
The scheme of the remote repo. Defaults to "https".
## workspace\_url
```python theme={null}
def workspace_url(host: str = "oxen.ai", scheme: str = "https") -> str
```
Get the url of the data frame.
## size
```python theme={null}
def size() -> tuple[int, int]
```
Get the size of the data frame. Returns a tuple of (rows, columns)
## page\_size
```python theme={null}
def page_size() -> int
```
Get the page size of the data frame for pagination in list() command.
**Returns**:
The page size of the data frame.
## total\_pages
```python theme={null}
def total_pages() -> int
```
Get the total number of pages in the data frame for pagination in list() command.
**Returns**:
The total number of pages in the data frame.
## list\_page
```python theme={null}
def list_page(page_num: int = 1) -> List[dict]
```
List the rows within the data frame.
**Arguments**:
* `page_num` - `int`
The page number of the data frame to list. We default to page size of 100 for now.
**Returns**:
A list of rows from the data frame.
## insert\_row
```python theme={null}
def insert_row(data: dict)
```
Insert a single row of data into the data frame.
**Arguments**:
* `data` - `dict`
A dictionary representing a single row of data.
The keys must match a subset of the columns in the data frame.
If a column is not present in the dictionary,
it will be set to an empty value.
**Returns**:
The id of the row that was inserted.
## where\_sql\_from\_dict
```python theme={null}
def where_sql_from_dict(attributes: dict, operator: str = "AND") -> str
```
Generate the SQL from the attributes.
## select\_sql\_from\_dict
```python theme={null}
def select_sql_from_dict(attributes: dict,
columns: Optional[List[str]] = None) -> str
```
Generate the SQL from the attributes.
## get\_embeddings
```python theme={null}
def get_embeddings(attributes: dict, column: str = "embedding") -> List[float]
```
Get the embedding from the data frame.
## is\_nearest\_neighbors\_enabled
```python theme={null}
def is_nearest_neighbors_enabled(column="embedding")
```
Check if the embeddings column is indexed in the data frame.
## enable\_nearest\_neighbors
```python theme={null}
def enable_nearest_neighbors(column: str = "embedding")
```
Index the embeddings in the data frame.
## query
```python theme={null}
def query(sql: Optional[str] = None,
find_embedding_where: Optional[dict] = None,
embedding: Optional[list[float]] = None,
sort_by_similarity_to: Optional[str] = None,
page_num: int = 1,
page_size: int = 10)
```
Sort the data frame by the embedding.
## nearest\_neighbors\_search
```python theme={null}
def nearest_neighbors_search(find_embedding_where: dict,
sort_by_similarity_to: str = "embedding")
```
Get the nearest neighbors to the embedding.
## get\_by
```python theme={null}
def get_by(attributes: dict)
```
Get a single row of data by attributes.
## get\_row
```python theme={null}
def get_row(idx: int)
```
Get a single row of data by index.
**Arguments**:
* `idx` - `int`
The index of the row to get.
**Returns**:
A dictionary representing the row.
## get\_row\_by\_id
```python theme={null}
def get_row_by_id(id: str)
```
Get a single row of data by id.
**Arguments**:
* `id` - `str`
The id of the row to get.
**Returns**:
A dictionary representing the row.
## update\_row
```python theme={null}
def update_row(id: str, data: dict)
```
Update a single row of data by id.
**Arguments**:
* `id` - `str`
The id of the row to update.
* `data` - `dict`
A dictionary representing a single row of data.
The keys must match a subset of the columns in the data frame.
If a column is not present in the dictionary,
it will be set to an empty value.
**Returns**:
The updated row as a dictionary.
## delete\_row
```python theme={null}
def delete_row(id: str)
```
Delete a single row of data by id.
**Arguments**:
* `id` - `str`
The id of the row to delete.
## restore
```python theme={null}
def restore()
```
Unstage any changes to the schema or contents of a data frame
## commit
```python theme={null}
def commit(message: str, branch: Optional[str] = None)
```
Commit the current changes to the data frame.
**Arguments**:
* `message` - `str`
The message to commit the changes.
* `branch` - `str`
The branch to commit the changes to. Defaults to the current branch.
# Datasets
Source: https://docs.oxen.ai/python-api/datasets
# oxen.datasets
## load\_dataset
```python theme={null}
def load_dataset(repo_id: str,
path: str,
fmt: str = "hugging_face",
revision=None)
```
Load a dataset from an Oxen repository into memory using the HuggingFace datasets library.
**Arguments**:
* `repo_id` - `str`
The namespace/repo\_name of the oxen repository to load the dataset from
* `path` - `str` | Sequence\[str]
The path to the dataset we want to load
* `fmt` - `str`
The format of the data files. Currently only "hugging\_face" is supported.
* `revision` - `str` | None
The commit id or branch name of the version of the data to download
**Example**:
```python theme={null}
from oxen.datasets import load_dataset
dataset = load_dataset("datasets/gsm8k", "train.jsonl")
# use datasets functions as you normally would
dataset.shuffle()[:10]
```
## download
```python theme={null}
def download(repo_id: str,
path: str,
revision=None,
dst=None,
host="hub.oxen.ai",
scheme="https")
```
Download files or directories from a remote Oxen repository.
**Arguments**:
* `repo_id` - `str`
The namespace/repo\_name of the oxen repository to load the dataset from
* `path` - `str`
The path to the data files
* `revision` - `str | None`
The commit id or branch name of the version of the data to download
* `dst` - `str | None`
The path to download the data to.
* `host` - `str`
The host to download the data from.
* `scheme` - `str`
The scheme to download the data with. (default: "https")
## upload
```python theme={null}
def upload(repo_id: str,
path: str,
message: str,
branch: Optional[str] = None,
dst: str = "")
```
Upload files or directories to a remote Oxen repository.
**Arguments**:
* `repo_id` - `str`
The namespace/repo\_name of the oxen repository to upload the dataset to
* `path` - `str`
The path to the data files
* `message` - `str`
The commit message to use when uploading the data
* `branch` - `str | None`
The branch to upload the data to. If None, the `main` branch is used.
* `dst` - `str | None`
The directory to upload the data to.
# Df utils
Source: https://docs.oxen.ai/python-api/df_utils
# oxen.df\_utils
The `df_utils` module provides a consistent interface for loading data frames and saving them to disk.
Supported types: csv, parquet, json, jsonl, arrow
Example usage:
```python theme={null}
import os
from oxen import df_utils
# load a data frame
df = df_utils.load("path/to/data.csv")
# save a data frame
df_utils.save(df, "path/to/save.csv")
```
## load
```python theme={null}
def load(path: os.PathLike)
```
Reads a file into a data frame. The file format is inferred from the file extension.
Supported types: csv, parquet, json, jsonl, arrow
**Arguments**:
* `path` - `os.PathLike`
The path to the file to read.
## save
```python theme={null}
def save(data_frame: DataFrame, path: os.PathLike)
```
Saves a data frame to a file. The file format is inferred from the file extension.
**Arguments**:
* `data_frame` - `DataFrame`
The polars data frame to save.
* `path` - `os.PathLike`
The path to save the data frame to.
# Diff
Source: https://docs.oxen.ai/python-api/diff/diff
# oxen.diff/diff
Oxen can be used to compare data frames and return a tabular diff.
There is more information about the diff in the
[Diff Getting Started Documentation](/concepts/diffs).
For example comparing two data frames will give you an output data frame,
where the `.oxen.diff.status` column shows if the row was `added`, `removed`,
or `modified`.
```
shape: (6, 7)
+-------------+-----+-----+-------+--------+-------------+-------------------+
| file | x | y | width | height | label.right | .oxen.diff.status |
| --- | --- | --- | --- | --- | --- | --- |
| str | i64 | i64 | i64 | i64 | str | str |
+-------------+-----+-----+-------+--------+-------------+-------------------+
| image_0.jpg | 0 | 0 | 10 | 10 | cat | modified |
| image_1.jpg | 1 | 2 | 10 | 20 | null | removed |
| image_1.jpg | 200 | 100 | 10 | 20 | dog | added |
| image_2.jpg | 4 | 10 | 20 | 20 | null | removed |
| image_3.jpg | 4 | 10 | 20 | 20 | dog | added |
| image_4.jpg | 10 | 10 | 10 | 10 | dog | added |
+-------------+-----+-----+-------+--------+-------------+-------------------+
```
## Usage
```python theme={null}
import os
import oxen
result = oxen.diff("dataset_1.csv", "dataset_2.csv")
print(result.get())
```
## diff
```python theme={null}
def diff(path: os.PathLike,
to: Optional[os.PathLike] = None,
repo_dir: Optional[os.PathLike] = None,
revision_left: Optional[str] = None,
revision_right: Optional[str] = None,
output: Optional[os.PathLike] = None,
keys: list[str] = [],
compares: list[str] = [])
```
Compares data from two paths and returns a diff respecting the type of data.
**Arguments**:
* `path` - `os.PathLike`
The path to diff. If `to` is not provided,
this will compare the data frame to the previous commit.
* `to` - `os.PathLike`
An optional second path to compare to.
If provided this will be the right side of the diff.
* `repo_dir` - `os.PathLike`
The path to the oxen repository. Must be provided if `compare_to` is
not provided, or if `revision_left` or `revision_right` is provided.
If not provided, the repository will be searched for in the current
working directory.
* `revision_left` - `str`
The left revision to compare. Can be a commit hash or branch name.
* `revision_right` - `str`
The right revision to compare. Can be a commit hash or branch name.
* `output` - `os.PathLike`
The path to save the diff to. If not provided, the diff will not be saved.
* `keys` - `list[str]`
Only for tabular diffs. The keys to compare on.
This is used to join the two data frames.
Keys will be combined and hashed to create a identifier for each row.
* `compares` - `list[str]`
Only for tabular diffs. The compares to compare on.
This is used to compare the values of the two data frames.
## Diff Objects
```python theme={null}
class Diff()
```
Diff class wraps many types of diffs and provides a consistent interface.
For example the diff can be tabular or text. Eventually we will extend this
to support other types of diffs such as images, audio, etc.
## format
```python theme={null}
@property
def format() -> str
```
Returns the format of the diff. Ie. tabular, text, etc.
## tabular
```python theme={null}
@property
def tabular() -> Optional[TabularDiff]
```
Returns the tabular diff if the diff is tabular.
## text
```python theme={null}
@property
def text() -> Optional[TextDiff]
```
Returns the text diff if the diff is text.
## get
```python theme={null}
def get()
```
Resolves the diff type and returns the appropriate diff object.
# Line diff
Source: https://docs.oxen.ai/python-api/diff/line_diff
# oxen.diff/line\_diff
## LineDiff Objects
```python theme={null}
class LineDiff()
```
A class representing a change in a line of text.
## modification
```python theme={null}
@property
def modification() -> ChangeType
```
Returns the modification of the line diff.
## text
```python theme={null}
@property
def text() -> str
```
Returns the text of the line diff.
# Tabular diff
Source: https://docs.oxen.ai/python-api/diff/tabular_diff
# oxen.diff/tabular\_diff
## TabularDiff Objects
```python theme={null}
class TabularDiff()
```
This class returns a polars data frame that represents a tabular diff.
## data
```python theme={null}
@property
def data() -> DataFrame
```
Returns the data of the diff as a polars data frame.
# Text diff
Source: https://docs.oxen.ai/python-api/diff/text_diff
# oxen.diff/text\_diff
## TextDiff Objects
```python theme={null}
class TextDiff()
```
A class representing a text diff.
## num\_added
```python theme={null}
@property
def num_added() -> int
```
Returns the number of added lines in the diff.
## num\_removed
```python theme={null}
@property
def num_removed() -> int
```
Returns the number of removed lines in the diff.
## lines
```python theme={null}
@property
def lines() -> list[LineDiff]
```
Returns the contents of the diff as a polars data frame.
# Introduction
Source: https://docs.oxen.ai/python-api/index
Learn how to get started with the oxenai Python package.
## Install
```bash theme={null}
pip install oxenai
```
## Clone Repository
Clone a repository from the [Oxen Hub](https://oxen.ai) or your own [oxen-server](/getting-started/oxen-server). Detailed documentation for the [clone](/python-api/clone) method can be found in the [Module Reference](#module-reference) below.
```python theme={null}
import oxen
oxen.clone("ox/SpanishToEnglish")
```
This will create a directory called `SpanishToEnglish` in your current working directory and download the latest version of the repository.
If you have not setup your API Key locally, you will get an error cloning data. View our [Authentication & Authorization documentation](/getting-started/auth) to learn more.
## Initialize Local Repository
If you are creating a new repository from scratch, you can initialize it with the [init](/python-api/init) method.
We will be using a fictional repository called `CatsVsDogs` for this example.
```python theme={null}
import oxen
import os
# Create an empty directory named CatsVsDogs
directory = "CatsVsDogs"
os.makedirs(directory)
# Initialize the Oxen Repository
repo = oxen.init(directory)
```
This will create a `.oxen` directory to keep track of changes as you make them.
## Load Existing Repository
Use the [repo](/python-api/repo) class to interact with a repository that has already been initialized.
```python theme={null}
from oxen import Repo
# Load the repository from the CatsVsDogs directory
repo = Repo("CatsVsDogs")
# Check the status of the repository
print(repo.status())
```
## Add Files
Now let's create a README.md file and [add](/python-api/repo#add) it to the local staging area. This will not commit the changes to the repository, but it will prepare them to be committed.
```python theme={null}
# ... continue from previous example
# Create a README.md file
filename = os.path.join(repo.path, "README.md")
with open(filename, "w") as f:
f.write("# Cats vs. Dogs\n\nWhich is it? We will be using machine learning to find out!")
# Add the README.md file to the staging area
repo.add(filename)
# Confirm that the file has been staged
print(repo.status())
```
## Commit Changes
Now that we have added the README.md file to the staging area, we can commit the changes to the repository.
```python theme={null}
# ... continue from previous example
# Commit the changes to the repository
repo.commit("Adding README.md")
```
## Diff Changes
Oxen.ai has powerful diff tools built in that allow you to see the changes to files between commits, branches, and more.
```python theme={null}
result = oxen.diff("README.md")
print(result.get())
```
To learn more about diffs checkout the [diff](/concepts/diffs) documentation or the [Python API Documentation](/python-api/diff/diff).
## Push To Remote
It's one thing to version your data locally, but where the real power comes in is when you can share your data with others. Oxen repositories can be pushed to a remote repository hosted on [Oxen Hub](https://oxen.ai) or your own [oxen-server](/getting-started/oxen-server).
There are a few steps when pushing to a remote for the first time.
1. [Create Remote](/python-api/remote_repo#create_repo)
2. [Point Local to Remote](/python-api/repo#set_remote)
3. [Push Changes](/python-api/repo#push)
### Create Remote
Before you can push to a remote repository, you must create it. This can be done with the [create\_repo](/python-api/remote_repo#create_repo) method.
```python theme={null}
from oxen.remote_repo import create_repo
# Create a remote repository
remote_name = "MyNamespace/CatsVsDogs"
remote_repo = create_repo(remote_name)
```
### Point Local to Remote
Now that we have created the remote repository, we need to point our local repository to sync to it. This can be done with the [set\_remote](/python-api/repo#set_remote) method.
```python theme={null}
from oxen import Repo
# Load the local repository
repo = Repo("CatsVsDogs")
# Point the local repository to the remote
repo.set_remote("origin", remote_repo.url())
```
### Push Changes
Now that we have created the remote repository and pointed our local repository to it, we can [push](/python-api/repo#push) our changes to the remote repository.
```python theme={null}
# Push the changes to the remote repository
repo.push()
```
### Full Push Example
The end to end workflow from scratch looks like this:
```python theme={null}
from oxen import Repo
from oxen.remote_repo import create_repo
from oxen.auth import config_auth
# 0. Load the local repository
repo = Repo("CatsVsDogs")
# 1. Configure Authentication
config_auth("YOUR_AUTH_TOKEN")
# 2. Create a remote repository
remote_name = "MyNamespace/CatsVsDogs"
repo = create_repo(remote_name)
# 3. Point the local repository to the remote
repo.set_remote("origin", repo.url)
# 4. Push the changes to the remote repository
repo.push()
```
## Pull Data
Now that we have pushed our changes to the remote repository, we can [pull](/python-api/repo#pull) them down to another machine.
```python theme={null}
import oxen
import os
repo_path = "CatsVsDogs"
if os.path.exists(repo_path):
# if you already have a local copy of the repository, you can load it
repo = oxen.Repo(repo_path)
else:
# if you don't have a local copy of the repository, you can clone it
repo = oxen.clone("ox/CatsVsDogs")
# Pull the latest changes from the remote repository
repo.pull()
```
## OxenFS (fsspec Integration)
OxenFS allows you to conveniently read and write files through a Pythonic file interface.
```python theme={null}
import oxen
fs = oxen.OxenFS("openai", "gsm8k")
with fs.open("gsm8k_test.parquet") as f:
content = f.read()
```
It also integrates directly with third-party libraries like Pandas like this:
```python theme={null}
df = pd.read_parquet("oxen://openai:gsm8k@main/gsm8k_test.parquet")
```
See the full documentation for [OxenFS](/python-api/oxen_fs).
## Branching
Branching is a powerful feature of Oxen that allows you to create a named version of your data without affecting the original version. This is useful when you want to experiment with your changes affecting the original version.
### Create Branch
To create a new branch, use the [Repo.checkout](/python-api/repo#checkout) method.
```python theme={null}
from oxen import Repo
repo = Repo("CatsVsDogs")
repo.checkout("add-dogs", create=True)
```
This both creates the branch and checks it out (the command line equivalent of `oxen checkout -b add-dogs`).
### List Branches
To list all of the branches in a repository, use the [Repo.branches](/python-api/repo#branches) method.
```python theme={null}
from oxen import Repo
repo = Repo("CatsVsDogs")
print(repo.branches())
```
Output:
```
[Branch(name=add-dogs, commit_id=3168391af834ac18), Branch(name=main, commit_id=3168391af834ac18)]
```
As you can see there should be a `main` branch and a `add-dogs` branch, each tied to a commit id. The commit ids will be the same at this point, because the branches have not diverged in content.
## Module Reference
Detailed documentation for each Python module.
### Clone
[clone](/python-api/clone) is used to download a repository to your local machine.
### Initialize Repository
[init](/python-api/init) is used to initialize a new local repository.
### Configure User
[user](/python-api/user) is used to configure the user for a local repository.
### Setup Auth
[auth](/python-api/auth) is used to configure authentication for remote repositories.
### Repositories
The [Repositories](/python-api/repositories) page has an overview of the two repository classes, and detailed documentation for each class can be found on their respective pages below.
* [Repo](/python-api/repo) is used to interact with data locally.
* [RemoteRepo](/python-api/remote_repo) is used to interact with a remote data without downloading all of it locally.
### OxenFS
[OxenFS](/python-api/oxen_fs) is an [fsspec](https://filesystem-spec.readthedocs.io/en/latest/) backend that allows you to read and write files in your Oxen repo through a Pythonic file interface. It also provides a convenient integration point with third-party libraries.
# Init
Source: https://docs.oxen.ai/python-api/init
# oxen.init
## init
```python theme={null}
def init(path: str = "./")
```
Initialize a [Repo](/python-api/repo) at the given path.
**Arguments**:
* `path` - `str`
The path to initialize the repo at.
**Returns**:
[Repo](/python-api/repo)
A Repo object that can be used to interact with the repo.
# Oxen fs
Source: https://docs.oxen.ai/python-api/oxen_fs
# oxen.oxen\_fs
## OxenFS Objects
```python theme={null}
class OxenFS(fsspec.AbstractFileSystem)
```
OxenFS is a filesystem interface for Oxen repositories that implements the
[fsspec](https://filesystem-spec.readthedocs.io/en/latest/) protocol. This
allows you to interact with Oxen repositories using familiar filesystem
operations and integrate with other compatible libraries like Pandas.
## Basic Usage
### Creating a Filesystem Instance
```python theme={null}
import oxen
# For Oxen Hub repositories
fs = oxen.OxenFS("ox", "Flowers")
# For local oxen-server
fs = oxen.OxenFS("ox", "test-repo", host="localhost:3000", scheme="http")
```
### Reading Files
```python theme={null}
with fs.open("data/train.csv") as f:
content = f.read()
```
### Writing Files
You must have write access to the repository to write files. See:
[https://docs.oxen.ai/python-api#private-repositories](https://docs.oxen.ai/python-api#private-repositories)
OxenFS will automatically commit the file to the repository when the
context is exited (or the file is closed some other way). New
directories are automatically created as needed.
```python theme={null}
# Write with custom commit message
with fs.open("data/test.txt", mode="wb", commit_message="Added test.txt") as f:
f.write("Hello, world!")
# You can also set/update the commit message inside the context
with fs.open("data/test.txt", mode="wb") as f:
f.commit_message = "Updated test.txt"
f.write("Hello, world again!")
```
## Writing file objects
If you're integrating Oxen in a situation where you already have a file object,
you can save it to your repo by using `shutil.copyfileobj` like this:
```python theme={null}
import shutil
file_object_from_somewhere = open("data.csv")
with fs.open("train/data.csv", mode="wb") as output_file:
output_file.commit_message = "Copy from a file object"
shutil.copyfileobj(file_object_from_somewhere, output_file)
```
## Integration with Third Party Libraries (Pandas, etc.)
OxenFS works seamlessly with Pandas and other fsspec-compatible libraries using
the URL format: `oxen://namespace:repo@revision/path/to/file`
### Reading Data
These will work with Pandas `{to,from}_{csv,parquet,json,etc.}` functions.
```python theme={null}
import pandas as pd
# Read parquet directly from Oxen repository
df = pd.read_parquet("oxen://openai:gsm8k@main/gsm8k_test.parquet")
```
### Writing Data
```python theme={null}
# Write DataFrame directly to Oxen repository
df.to_csv("oxen://ox:my-repo@main/data/test.csv", index=False)
```
## Notes
* Only binary read ("rb") and write ("wb") modes are currently supported
* But writing will automatically encode strings to bytes
* Does not yet support streaming files. All operations use temporary local files.
## \_\_init\_\_
```python theme={null}
def __init__(namespace: str,
repo: str,
host: str = "hub.oxen.ai",
revision: str = "main",
scheme: str = "https",
**kwargs)
```
Initialize the OxenFS instance.
**Arguments**:
* `namespace` - `str`
The namespace of the repository.
* `repo` - `str`
The name of the repository.
* `host` - `str`
The host to connect to. Defaults to 'hub.oxen.ai'
* `revision` - `str`
The branch name or commit id to checkout. Defaults to 'main'
* `scheme` - `str`
The scheme to use for the remote url. Default: 'https'
## ls
```python theme={null}
def ls(path: str = "", detail: bool = False)
```
List the contents of a directory.
**Arguments**:
* `path` - `str`
The path to list the contents of.
* `detail` - `bool`
If True, return a list of dictionaries with detailed metadata.
Otherwise, return a list of strings with the filenames.
## OxenFSFileWriter Objects
```python theme={null}
class OxenFSFileWriter()
```
A file writer for the OxenFS backend.
This is normally called through `OxenFS.open()` or `fsspec.open()`.
## write
```python theme={null}
def write(data: str | bytes)
```
Write string or binary data to the file.
## flush
```python theme={null}
def flush()
```
Flush the file to disk.
## tell
```python theme={null}
def tell()
```
Return the current position of the file.
## seek
```python theme={null}
def seek(offset: int, whence: int = os.SEEK_SET)
```
Seek to a specific position in the file.
## commit
```python theme={null}
def commit(commit_message: Optional[str] = None)
```
Commit the file to the remote repo.
## close
```python theme={null}
def close()
```
Close the file writer. This will commit the file to the remote repo.
# Remote repo
Source: https://docs.oxen.ai/python-api/remote_repo
# oxen.remote\_repo
## get\_repo
```python theme={null}
def get_repo(name: str, host: str = "hub.oxen.ai", scheme: str = "https")
```
Get a RemoteRepo object for the specified name. For example 'ox/CatDogBBox'.
**Arguments**:
* `name` - `str`
Name of the repository in the format 'namespace/repo\_name'.
* `host` - `str`
The host to connect to. Defaults to 'hub.oxen.ai'
**Returns**:
[RemoteRepo](/python-api/remote_repo)
## create\_repo
```python theme={null}
def create_repo(name: str,
description="",
is_public: bool = True,
host: str = "hub.oxen.ai",
scheme: str = "https",
files: List[Tuple[str, str]] = [])
```
Create a new repository on the remote server.
**Arguments**:
* `name` - `str`
Name of the repository in the format 'namespace/repo\_name'.
* `description` - `str`
Description of the repository.
Only applicable to [OxenHub](https://oxen.ai).
* `is_public` - `bool`
Whether the repository is public or private.
Only applicable to [OxenHub](https://oxen.ai).
* `host` - `str`
The host to connect to. Defaults to 'hub.oxen.ai'
* `scheme` - `str`
The scheme to use for the remote url. Default: 'https'
* `files` - `List[Tuple[str, str]]`
A list of tuples containing the path to the file and the contents
of the file that you would like to seed the repository with.
**Returns**:
[RemoteRepo](/python-api/remote_repo)
## RemoteRepo Objects
```python theme={null}
class RemoteRepo()
```
The RemoteRepo class allows you to interact with an Oxen repository
without downloading the data locally.
## Examples
### Add & Commit Files
Adding and committing a file to a remote workspace.
```python theme={null}
from oxen import RemoteRepo
repo = RemoteRepo("ox/CatDogBBox")
repo.add("/path/to/image.png")
status = repo.status()
print(status.added_files())
repo.commit("Adding my image to the remote workspace.")
```
### Downloading Specific Files
Grab a specific file revision and load it into pandas.
```python theme={null}
from oxen import RemoteRepo
import pandas as pd
# Connect to the remote repo
repo = RemoteRepo("ox/CatDogBBox")
# Specify the version of the file you want to download
branch = repo.get_branch("my-pets")
# Download takes a file or directory a commit id
repo.download("annotations", revision=branch.commit_id)
# Once you have the data locally, use whatever library you want to explore the data
df = pd.read_csv("annotations/train.csv")
print(df.head())
```
## \_\_init\_\_
```python theme={null}
def __init__(repo_id: str,
host: str = "hub.oxen.ai",
revision: str = "main",
scheme: str = "https")
```
Create a new RemoteRepo object to interact with.
**Arguments**:
* `repo_id` - `str`
Name of the repository in the format 'namespace/repo\_name'.
For example 'ox/chatbot'
* `host` - `str`
The host to connect to. Defaults to 'hub.oxen.ai'
* `revision` - `str`
The branch name or commit id to checkout. Defaults to 'main'
* `scheme` - `str`
The scheme to use for the remote url. Default: 'https'
## create
```python theme={null}
def create(empty: bool = False, is_public: bool = False)
```
Will create the repo on the remote server.
**Arguments**:
* `empty` - `bool`
Whether to create an empty repo or not. Default: False
* `is_public` - `bool`
Whether the repository is public or private. Default: False
## exists
```python theme={null}
def exists() -> bool
```
Checks if this remote repo exists on the server.
## delete
```python theme={null}
def delete()
```
Delete this remote repo from the server.
## checkout
```python theme={null}
def checkout(revision: str, create=False)
```
Switches the remote repo to the specified revision.
**Arguments**:
* `revision` - `str`
The name of the branch or commit id to checkout.
* `create` - `bool`
Whether to create a new branch if it doesn't exist. Default: False
## ls
```python theme={null}
def ls(directory: Optional[str] = None,
page_num: int = 1,
page_size: int = 100)
```
Lists the contents of a directory in the remote repo.
**Arguments**:
* `directory` - `str`
The directory to list. If None, will list the root directory.
* `page_num` - `int`
The page number to return. Default: 1
* `page_size` - `int`
The number of items to return per page. Default: 100
## scan
```python theme={null}
def scan(directory: Optional[str] = None, page_size: int = 100)
```
Generator over the contents of a directory in the remote repo
**Arguments**:
* `directory` - `str`
The directory to list. If None, will list the root directory
* `page_size` - `int`
The number of items to return per page. Default: 100
## download
```python theme={null}
def download(src: str,
dst: Optional[str] = None,
revision: Optional[str] = None)
```
Download a file or directory from the remote repo.
**Arguments**:
* `src` - `str`
The path to the remote file
* `dst` - `str | None`
The path to the local file. If None, will download to
the same path as `src`
* `revision` - `str | None`
The branch or commit id to download. Defaults to `self.revision`
## add
```python theme={null}
def add(src: str,
dst: Optional[str] = "",
branch: Optional[str] = None,
workspace_name: Optional[str] = None)
```
Stage a file to a workspace in the remote repo.
**Arguments**:
* `src` - `str`
The path to the local file to upload
* `dst` - `str | None`
The directory to upload the file to. If None, will upload to the root directory.
* `branch` - `str | None`
The branch to upload the file to. Defaults to `self.revision`
**Returns**:
[Workspace](/python-api/workspace)
## status
```python theme={null}
def status()
```
Get the status of the workspace.
## commit
```python theme={null}
def commit(message: str)
```
Commit the workspace to the remote repo.
## upload
```python theme={null}
def upload(src: str,
commit_message: str,
file_name: Optional[str] = None,
dst_dir: Optional[str] = "",
branch: Optional[str] = None)
```
Upload a file to the remote repo.
**Arguments**:
* `src` - `str`
The path to the local file to upload
* `file_name` - `str | None`
The name of the file to upload. If None, will use the name of the file in `src`
* `dst_dir` - `str | None`
The directory to upload the file to. If None, will upload to the root directory.
* `branch` - `str | None`
The branch to upload the file to. Defaults to `self.revision`
## metadata
```python theme={null}
def metadata(path: str)
```
Get the metadata for a file in the remote repo.
## file\_exists
```python theme={null}
def file_exists(path: str, revision: str = None)
```
Check if a file exists in the remote repo.
**Arguments**:
* `path` - `str`
The path to the file to check
* `revision` - `str`
The revision to check against, defaults to `self.revision`
## file\_has\_changes
```python theme={null}
def file_has_changes(local_path: str,
remote_path: str = None,
revision: str = None)
```
Check if a local file has changed compared to a remote revision
**Arguments**:
* `local_path` - `str`
The local path to the file to check
* `remote_path` - `str`
The remote path to the file to check, will default to `local_path` if not provided
* `revision` - `str`
The revision to check against, defaults to `self.revision`
## log
```python theme={null}
def log()
```
Get the commit history for a remote repo
## branch\_exists
```python theme={null}
def branch_exists(name: str) -> bool
```
Check if a branch exists in the remote repo.
**Arguments**:
* `name` - `str`
The name of the branch to check
## branch
```python theme={null}
def branch()
```
Get the current branch for a remote repo
## branches
```python theme={null}
def branches()
```
List all branches for a remote repo
## list\_workspaces
```python theme={null}
def list_workspaces()
```
List all workspaces for a remote repo
## get\_branch
```python theme={null}
def get_branch(branch: str)
```
Return a branch by name on this repo, if exists
**Arguments**:
* `branch` - `str`
The name of the branch to return
## create\_branch
```python theme={null}
def create_branch(branch: str)
```
Return a branch by name on this repo,
creating it from the currently checked out branch if it doesn't exist
**Arguments**:
* `branch` - `str`
The name to assign to the created branch
## create\_checkout\_branch
```python theme={null}
def create_checkout_branch(branch: str)
```
Create a new branch from the currently checked out branch,
and switch to it
**Arguments**:
* `branch` - `str`
The name to assign to the created branch
## merge
```python theme={null}
def merge(base_branch: str, head_branch: str)
```
Merge the head branch into the base branch on the remote repo.
**Arguments**:
* `base_branch` - `str`
The base branch to merge into
* `head_branch` - `str`
The head branch to merge
## namespace
```python theme={null}
@property
def namespace() -> str
```
The namespace for the repo.
## name
```python theme={null}
@property
def name() -> str
```
The name of the repo.
## identifier
```python theme={null}
@property
def identifier()
```
The namespace/name of the repo.
## url
```python theme={null}
@property
def url() -> str
```
The remote url for the repo.
## revision
```python theme={null}
@property
def revision() -> str
```
The branch or commit id for the repo
# Repo
Source: https://docs.oxen.ai/python-api/repo
# oxen.repo
## Repo Objects
```python theme={null}
class Repo()
```
The Repo class that allows you to interact with your local oxen repo.
## Examples
### Init, Add, Commit and Push
Adding and committing a file to a remote workspace.
```python theme={null}
import os
from oxen import Repo
# Initialize the Oxen Repository in a CatsAndDogs directory
directory = "CatsAndDogs"
repo = Repo(directory)
repo.init()
repo.add("images")
repo.commit("Adding all the images")
# Replace and with your values
repo.set_remote("origin", "https://hub.oxen.ai//")
repo.push()
```
## \_\_init\_\_
```python theme={null}
def __init__(path: str = "", mkdir=False)
```
Create a new Repo object. Use .init() to initialize a new oxen repository,
or pass the path to an existing one.
**Arguments**:
* `path` - `str`
Path to the main working directory of your oxen repo.
* `mkdir` - `bool`
Whether to create the directory if one doesn't exist. Default: False
## init
```python theme={null}
def init()
```
Initializes a new oxen repository at the path specified in the constructor.
Will create a .oxen folder to store all the versions and metadata.
## clone
```python theme={null}
def clone(url: str, branch: str = "main", all=False)
```
Clone repository from a remote url.
**Arguments**:
* `url` - `str`
The url of the remote repository. ex) [https://hub.oxen.ai/ox/chatbot](https://hub.oxen.ai/ox/chatbot)
* `branch` - `str`
The name of the branch to clone. Default: main
* `all` - `bool`
Whether to clone the full commit history or not. Default: False
## branches
```python theme={null}
def branches()
```
List all branches for a repo
## branch
```python theme={null}
def branch(name: str, delete=False)
```
## checkout
```python theme={null}
def checkout(revision: str, create=False)
```
Checkout a branch or commit id.
**Arguments**:
* `revision` - `str`
The name of the branch or commit id to checkout.
* `create` - `bool`
Whether to create a new branch if it doesn't exist. Default: False
## add
```python theme={null}
def add(path: str)
```
Stage a file or directory to be committed.
## add\_schema\_metadata
```python theme={null}
def add_schema_metadata(path: str, column_name: str, metadata: str)
```
Add schema to the local repository
## rm
```python theme={null}
def rm(path: str, recursive=False, staged=False)
```
Remove a file or directory from being tracked.
This will not delete the file or directory.
**Arguments**:
* `path` - `str`
The path to the file or directory to remove.
* `recursive` - `bool`
Whether to remove the file or directory recursively. Default: False
* `staged` - `bool`
Whether to remove the file or directory from the staging area.
* `Default` - False
* `remote` - `bool`
Whether to remove the file or directory from a remote workspace.
* `Default` - False
## status
```python theme={null}
def status()
```
Check the status of the repo. Returns a StagedData object.
## commit
```python theme={null}
def commit(message: str)
```
Commit the staged data in a repo with a message.
**Arguments**:
* `message` - `str`
The commit message.
## log
```python theme={null}
def log()
```
Get the commit history for a repo.
## set\_remote
```python theme={null}
def set_remote(name: str, url: str)
```
Map a name to a remote url.
**Arguments**:
* `name` - `str`
The name of the remote. Ex) origin
* `url` - `str`
The url you want to map the name to. Ex) [https://hub.oxen.ai/ox/chatbot](https://hub.oxen.ai/ox/chatbot)
## push
```python theme={null}
def push(remote_name: str = "origin",
branch: str = "main",
delete: bool = False)
```
Push data to a remote repo from a local repo.
**Arguments**:
* `remote_name` - `str`
The name of the remote to push to.
* `branch` - `str`
The name of the branch to push to.
## pull
```python theme={null}
def pull(remote_name: str = "origin", branch: str = "main", all=False)
```
Pull data from a remote repo to a local repo.
**Arguments**:
* `remote_name` - `str`
The name of the remote to pull from.
* `branch` - `str`
The name of the branch to pull from.
* `all` - `bool`
Whether to pull all data from branch history or not. Default: False
## path
```python theme={null}
@property
def path()
```
Returns the path to the repo.
## current\_branch
```python theme={null}
@property
def current_branch()
```
Returns the current branch.
## merge
```python theme={null}
def merge(branch: str)
```
Merge a branch into the current branch.
# Repositories
Source: https://docs.oxen.ai/python-api/repositories
Learn how to use the Repository classes.
## Repository Classes
There are a few basic python classes you can use to interact with Oxen repositories. The full list of Python APIs can be found in the [API Documentation](/python-api).
## Remote vs Local
Oxen has the concept of [Remote Repositories](#remote-repositories) and [Local Repositories](#local-repositories). One of the core tenets of Oxen is that data should feel like it is local, even if it is not. Hence the APIs for Local vs Remote are very similar, the only difference is where you are performing the operation.
## Remote Repositories
Remote Repositories only download pointers and metadata, so that you can interact with the data as if it was local.
Here is the full documentation for the [RemoteRepo](/python-api/remote_repo).
### Integrate with Pandas
The fastest way to integrate Oxen into your existing workflow is to use the fact that Oxen gives you direct access to files and directories given a specific revision.
For example, let's load a data file given a specific commit into [Pandas](https://pandas.pydata.org/)
```python Python theme={null}
from oxen import RemoteRepo
import pandas as pd
# Connect to the remote repo
repo = RemoteRepo("ox/CatDogBBox")
# Specify the version of the file you want to download
branch = repo.get_branch("my-pets")
# Download takes a file or directory a commit id
repo.download("annotations", revision=branch.commit_id)
# Once you have the data locally, use whatever library you want to explore the data
df = pd.read_csv("annotations/train.csv")
print(df.head())
```
All the files are also accessible directly over http, which removes some of the boilerplate as long as the files are of a reasonable size.
The url structure is `https://hub.oxen.ai/api/repos/:namespace/:repo_name/file/:revision/:file_path`
```python Python theme={null}
import pandas as pd
df = pd.read_csv("https://hub.oxen.ai/api/repos/ox/CatDogBBox/file/main/annotations/test.csv")
print(df.head())
```
### Add Files
Oxen has the concept of [Remote Workspaces](/examples/data/workspaces) that make it easy to add data to a remote repository without ever downloading it locally.
```python Python theme={null}
from oxen import RemoteRepo
# Connect to the Remote
repo = RemoteRepo("ox/CatDogBBox")
# Create a branch on the remote and check it out
# similar to oxen checkout -b add-images
repo.create_checkout_branch("add-images")
```
## Local Repositories
(Local) [Repos](/python-api/repo) have all the files versioned and accessible on your local machine. They duplicate the data between your working directory and a hidden .oxen directory so that you can quickly swap between versions and run experiments.
If you are creating a new repository from scratch, this is a great place to start. The workflow is very similar to [git](https://git-scm.com/) in terms of initializing a repository, adding data, committing, and pushing to a remote.
Let's walk through some basic operations.
### Init
Assuming you are creating a brand new repository, first you will have to create an empty directory, point your `LocalRepo` to it and run `init()`.
```python theme={null}
import os
from oxen import Repo
# Create an empty directory named CatsAndDogs
directory = "CatsAndDogs"
os.makedirs(directory)
# Initialize the Oxen Repository
repo = Repo(directory)
repo.init()
```
### Add Files
Now let's create a README.md file and add it to the local staging area.
```python theme={null}
import os
from oxen import Repo
# write a file called README.md to disk
directory = "CatsAndDogs"
file_name = "README.md"
file_path = os.path.join(directory, file_name)
# Open the file in write mode
with open(file_path, "w") as file:
# Write the title to the file
file.write("# " + directory + "\n")
# Assuming the Repo is already initialized
repo = Repo(directory)
# add the path relative to the dir
repo.add(file_name)
# list added files
status = repo.status()
print(status.added_files())
```
You should see that we have one file added `[README.md]`
### Commit Staged Files
With your README.md staged you can now commit with a message
```python theme={null}
from oxen import Repo
# Assuming you have already added the data
repo = Repo(directory)
repo.commit("Adding README.md")
```
π Congratulations you have just versioned your first file! Now to sync it with the rest of your team.
### Configure Remote
The easiest way to create a remote is in the [Oxen Hub web interface](https://oxen.ai).
Then once you have a remote created, set the remote on the repo object.
```python theme={null}
from oxen import Repo
# Once you have data committed that you want to sync
repo = Repo(directory)
# You can have multiple named remotes
username = "YOUR_USERNAME"
repo_name = "REMOTE_REPO_NAME"
remote_name = "origin"
remote_url = f"https://hub.oxen.ai/{username}/{repo_name}"
repo.set_remote(remote_name, remote_url)
```
### Push to Remote
With your remote set and auth key configured, you are ready to push the data!
```python theme={null}
from oxen import Repo
# Once you have committed data and set the remote, it's time to push your branch
repo = Repo(directory)
remote_name = "origin"
remote_branch = "main"
repo.push(remote_name, remote_branch)
```
# Workspace
Source: https://docs.oxen.ai/python-api/workspace
# oxen.workspace
## Workspace Objects
```python theme={null}
class Workspace()
```
The Workspace class allows you to interact with an Oxen workspace
without downloading the data locally.
Workspaces can be created off a branch and is tied to the commit id of the branch
at the time of creation.
You can commit a Workspace back to the same branch if the branch has not
advanced, otherwise you will have to commit to a new branch and merge.
## Examples
### Adding Files to a Workspace
Create a workspace from a branch.
```python theme={null}
from oxen import RemoteRepo
from oxen import Workspace
# Connect to the remote repo
repo = RemoteRepo("ox/CatDogBBox")
# Create the workspace
workspace = Workspace(repo, "my-branch")
# Add a file to the workspace
workspace.add("my-image.png")
# Print the status of the workspace
status = workspace.status()
print(status.added_files())
# Commit the workspace
workspace.commit("Adding my image to the workspace.")
```
## \_\_init\_\_
```python theme={null}
def __init__(repo: "RemoteRepo",
branch: str,
workspace_id: Optional[str] = None,
workspace_name: Optional[str] = None,
path: Optional[str] = None)
```
Create a new Workspace.
**Arguments**:
* `repo` - `PyRemoteRepo`
The remote repo to create the workspace from.
* `branch` - `str`
The branch name to create the workspace from. The workspace
will be tied to the commit id of the branch at the time of creation.
* `workspace_id` - `Optional[str]`
The workspace id to create the workspace from.
If left empty, will create a unique workspace id.
* `workspace_name` - `Optional[str]`
The name of the workspace. If left empty, the workspace will have no name.
* `path` - `Optional[str]`
The path to the workspace. If left empty, the workspace will be created in the root of the remote repo.
## id
```python theme={null}
@property
def id()
```
Get the id of the workspace.
## name
```python theme={null}
@property
def name()
```
Get the name of the workspace.
## branch
```python theme={null}
@property
def branch()
```
Get the branch that the workspace is tied to.
## commit\_id
```python theme={null}
@property
def commit_id()
```
Get the commit id of the workspace.
## repo
```python theme={null}
@property
def repo() -> "RemoteRepo"
```
Get the remote repo that the workspace is tied to.
## status
```python theme={null}
def status(path: str = "")
```
Get the status of the workspace.
**Arguments**:
* `path` - `str`
The path to check the status of.
## add
```python theme={null}
def add(src: str, dst: str = "")
```
Add a file to the workspace
**Arguments**:
* `src` - `str`
The path to the local file to be staged
* `dst` - `str`
The path in the remote repo where the file will be added
## rm
```python theme={null}
def rm(path: str)
```
Remove a file from the workspace
**Arguments**:
* `path` - `str`
The path to the file on workspace to be removed
## commit
```python theme={null}
def commit(message: str, branch_name: Optional[str] = None) -> PyCommit
```
Commit the workspace to a branch
**Arguments**:
* `message` - `str`
The message to commit with
* `branch_name` - `Optional[str]`
The name of the branch to commit to. If left empty, will commit to the branch
the workspace was created from.
## delete
```python theme={null}
def delete()
```
Delete the workspace