Skip to main content

Quickstart

Five steps from an empty project to a fine-tuned model answering over an OpenAI-compatible endpoint. Each one is the request that actually performs it, so this page doubles as the shortest possible integration.

Every path below is scoped to a project: {owner_slug} is your account slug and {project_slug} is the project's. Both appear in the URL of any project screen.

01 · Get a key​

Create and revoke keys from the profile menu, under API keys. A key is shown once, in the response to creating it — it is stored as a digest, so no listing can return it and nobody can recover it for you. If you lose it, revoke it and make another.

Send it as a bearer token, and keep it out of version control.

export REOPENLY_API_KEY="fnf_your_api_key"

02 · Point the SDK at your project​

Every project exposes an OpenAI-compatible surface. The only change to existing code is the base URL.

pip install openai
from openai import OpenAI

client = OpenAI(
base_url="https://reopenly.com/api/projects/{owner_slug}/{project_slug}/openai/v1",
api_key="fnf_your_api_key",
)

You can stop here. A project with no dataset and no fine-tune still serves the catalog's base models through that client.

03 · Add examples​

Chats are the training data. Add them from your own pipeline with the same API the editor uses, so what you curate by hand and what you import end up in one place.

POST /api/v1/projects/{owner_slug}/{project_slug}/chats
Authorization: Bearer fnf_your_api_key
Content-Type: application/json

{
"name": "returns-faq-01",
"tags": ["support"],
"messages": [
{"role": "user", "text": "How do returns work?"},
{"role": "assistant", "text": "Returns are free within 30 days."}
]
}

Tags are how a fine-tune selects its data, so they are worth choosing deliberately rather than after the fact.

Listing them back gives you message_count, a durable updated_at, and a nullable source per chat. message_count is computed by the server, and updated_at moves when the chat, its tags or its messages are edited. source is one of csv, api, editor, or playground; older chats created before provenance tracking can return null. The server decides this value, so an API-key caller cannot claim a different ingestion path.

GET /api/v1/projects/{owner_slug}/{project_slug}/chats

04 · Fine-tune​

A job is created from dataset tags and a base model, and it is measured in steps rather than epochs — the trainer runs a fixed number of training steps, and the bounds the API enforces are published by GET /api/v1/base-models.

POST /api/v1/projects/{owner_slug}/{project_slug}/training-jobs
Authorization: Bearer fnf_your_api_key
Content-Type: application/json

{
"dataset_tags": ["support"],
"model_name": "<catalog-model-id>",
"max_seq_length": 2048,
"num_train_epochs": 3
}

Watch it, and stop it if you want to:

GET /api/v1/projects/{owner_slug}/{project_slug}/training-jobs/{job_id}
POST /api/v1/projects/{owner_slug}/{project_slug}/training-jobs/{job_id}/cancel

First, ReOpenly freezes the selected chats, so later edits cannot change the run. Loading the model and reading and tokenizing your data happen before training starts, and publishing the model after it ends: only the training loop is billed. A run that never reaches its first training step is charged nothing.

Cancelling and stopping are two operations. POST .../cancel applies while the job is queued or preparing, where nothing has been learned and nothing is charged. Once training has begun, POST .../pause is the control: the run keeps training to its next recovery checkpoint and settles paused, where it can be resumed or finished. Neither applies once the job is post-processing or finalizing.

note

There is no separate public evaluation or deployment job state. Eligible runs evaluate during the training loop, upload strict loss improvements as adapter candidates, and promote the lowest-loss verified candidate during finalization. See Training jobs for the state table, evaluation policy, and billing boundaries.

05 · Serve it​

A job that produced an artifact can be published under a stable name you choose. Create the alias first:

POST /api/v1/projects/{owner_slug}/{project_slug}/models
Authorization: Bearer fnf_your_api_key
Content-Type: application/json

{
"name": "Support bot",
"slug": "support-bot",
"training_job_id": 87
}

From then on the slug is another model value for the same client from step 02:

response = client.chat.completions.create(
model="support-bot",
messages=[{"role": "user", "content": "How do returns work?"}],
temperature=0.7,
max_tokens=1024,
stream=True,
)

for chunk in response:
print(chunk.choices[0].delta.content or "", end="")

The weights are yours. Download a promoted LoRA adapter with GET /api/v1/projects/{owner_slug}/{project_slug}/training-jobs/{job_id}/download?format=lora and run it anywhere.

Where to go next​