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="roy_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="roy_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/projects/{owner_slug}/{project_slug}/chats
Authorization: Bearer roy_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 and a durable updated_at per chat. message_count is computed by the control plane, and updated_at moves when the chat, its tags or its messages are edited. There is no source field: upload, API and editor are ingestion paths, not persisted provenance, and a field that reported one would be guessing.

GET /api/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 optimizer steps, and the bounds the API enforces are published by GET /api/base-models.

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

{
"dataset_tags": ["support"],
"model_name": "unsloth/Qwen3.5-9B",
"max_seq_length": 2048,
"num_train_epochs": 3
}

Watch it, and stop it if you want to:

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

A job moves through queued → preparing → training → finalizing → terminal. Cancelling is honoured at the next safe point rather than immediately, and you are billed for the GPU minutes consumed up to that point and no further.

note

There is no evaluation or deployment stage. The job reports what it actually did — the examples it trained on, the base revision it resolved, and the hardware it ran on — and a stage that does not exist would be a worse answer than none. See Training jobs for the full state table.

05 · Serve it

A job that produced an artifact is hosted under a stable name you choose, and from then on it is just another model value to 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: a finished fine-tune can be downloaded and run anywhere, and nothing here depends on your staying.

Where to go next