API Documentation — v1

0-sbatti AI API

Self-hosted AI inference gateway with chat, embeddings and RAG support. All models run locally via Ollama.

Base URL: https://ai.0-sbatti.it
Format: JSON
Auth: X-API-Key
Rate limit: 100 req/h

Authentication

Every authenticated request requires an API key in the request header. Contact the administrator to obtain a key.

Pass your API key via the X-API-Key header on every request that requires authentication. Keys have the format sk-xxxxxxxxxxxxxxxx. The admin key is used exclusively for key management endpoints.
bash
curl https://ai.0-sbatti.it/v1/chat \
  -H "X-API-Key: sk-your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{"model": "mistral:latest", "message": "Hello!"}'

Rate Limiting

Authenticated endpoints enforce a per-key rate limit. Exceeding the limit returns HTTP 429.

ScopeLimitReset
Per API key100 requestsEvery hour
Daily quotaconfigurable per keyMidnight UTC

Error Codes

StatusMeaning
200OK — request succeeded
201Created — resource was created
400Bad Request — missing or invalid field
401Unauthorized — missing or invalid API key
403Forbidden — key is disabled or not admin
429Too Many Requests — rate limit exceeded
503Service Unavailable — model or DB unreachable

Endpoints

All responses are JSON. Requests with a body must set Content-Type: application/json.

GET /health no auth
Description
Response
cURL

Returns the current status of the gateway, including Ollama connectivity and database health. Use this endpoint for uptime monitoring.

No authentication required. Safe to use from public monitoring tools.

json
{
  "status": "ok",
  "ollama": "up",
  "database": "up",
  "models_available": [
    "mistral:latest",
    "qwen-0sbatti:latest",
    "qwen2.5-coder:3b",
    "nomic-embed-text:latest"
  ],
  "timestamp": "2026-05-24T16:00:00.000000Z"
}
bash
curl https://ai.0-sbatti.it/health
GET /v1/models no auth
Description
Response
cURL

Returns the list of AI models currently loaded in Ollama and available for inference. Pass the model value directly to /v1/chat.

json
{
  "models": [
    "mistral:latest",
    "qwen-0sbatti:latest",
    "qwen2.5-coder:3b",
    "nomic-embed-text:latest"
  ]
}
bash
curl https://ai.0-sbatti.it/v1/models
POST /v1/chat X-API-Key required
Description
Request
Response
cURL

Send a message to an AI model and receive a text response. All inference runs locally — no data leaves the server. Rate limit: 100 requests/hour per key.

Responses from local models can take 5–90 seconds depending on message length and server load. Set your HTTP client timeout accordingly.
FieldTypeRequiredDescription
messagestringrequiredThe user message to send to the model
modelstringoptionalModel name. Default: mistral:latest
json
{
  "model": "mistral:latest",
  "message": "Explain the difference between REST and GraphQL"
}
json
{
  "response": "REST is an architectural style that uses standard HTTP methods...",
  "model": "mistral:latest",
  "elapsed_s": 12.47
}
bash
curl https://ai.0-sbatti.it/v1/chat \
  -H "X-API-Key: sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral:latest",
    "message": "Explain the difference between REST and GraphQL"
  }'
POST /v1/embed X-API-Key required
Description
Request
Response
cURL

Converts a text string into a dense vector embedding using nomic-embed-text. Returns a 768-dimensional float array. Use these vectors for semantic search, clustering, or as input to your own RAG pipeline.

FieldTypeRequiredDescription
textstringrequiredThe text to convert to an embedding vector
json
{
  "text": "Vector databases store high-dimensional embeddings"
}
json
{
  "embedding": [0.0412, -0.1893, 0.0751, ...],
  "dimensions": 768,
  "elapsed_s": 0.31
}
bash
curl https://ai.0-sbatti.it/v1/embed \
  -H "X-API-Key: sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"text": "Vector databases store high-dimensional embeddings"}'
POST /v1/rag/query X-API-Key required
Description
Request
Response
cURL

Semantic search over indexed documents combined with AI-generated answers. The pipeline: embed question → search Qdrant → build context → generate answer with Mistral.

Documents are indexed from the Nextcloud instance. The RAG index uses nomic-embed-text for embedding and Qdrant as vector store.
⏱ Latency: This endpoint can take up to 60s. Set --max-time 90 in curl or equivalent timeout in your client.
FieldTypeRequiredDescription
questionstringrequiredNatural language question to answer from documents
top_kintegeroptionalNumber of documents to retrieve. Default: 5. Max: 20
json
{
  "question": "What services are described in the documentation?",
  "top_k": 5
}
json
{
  "answer": "Based on the documents, the following services are described...",
  "sources": [
    {
      "filename": "info_0sbatti.txt",
      "relevance": 0.9012,
      "excerpt": "0-sbatti.it provides cloud services including..."
    }
  ],
  "model_used": "mistral:latest",
  "docs_used": 3,
  "elapsed_s": 32.04,
  "warning": "nessun documento con contenuto leggibile trovato"
}

The warning field is only present when no usable document content was found.

bash
curl https://ai.0-sbatti.it/v1/rag/query \
  -H "X-API-Key: sk-your-api-key" \
  -H "Content-Type: application/json" \
  --max-time 90 \
  -d '{
    "question": "What services are described in the documentation?",
    "top_k": 5
  }'
POST /v1/keys/generate Admin key required
Description
Request
Response
cURL

Creates a new API key with a configurable daily request limit. Requires the admin key (sk-admin-*). The generated key is shown only once — store it securely.

⚠ Security: The generated key value is returned only in this response. It cannot be retrieved later — only its hash is stored in the database.
FieldTypeRequiredDescription
user_idstringoptionalIdentifier for the user/app owning this key. Default: "unknown"
namestringoptionalHuman-readable label for this key. Default: auto-generated
daily_limitintegeroptionalMaximum requests per day. Default: 1000
json
{
  "user_id": "mario.rossi",
  "name": "production-app",
  "daily_limit": 500
}
json
{
  "key": "sk-a3f1b2c4d5e6f7890abc1234567890ab",
  "id": 7,
  "user_id": "mario.rossi",
  "name": "production-app",
  "daily_limit": 500
}
bash
curl https://ai.0-sbatti.it/v1/keys/generate \
  -X POST \
  -H "X-API-Key: sk-admin-sbatti-2026" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "mario.rossi",
    "name": "production-app",
    "daily_limit": 500
  }'

Available Models

All models run locally via Ollama. No data is sent to external services.

ModelTypeContextBest for
mistral:latest chat 32k tokens General purpose Q&A, reasoning, Italian language
qwen-0sbatti:latest chat 32k tokens Business management, ERP-style queries, custom fine-tuned
qwen2.5-coder:3b code 32k tokens Code generation, completion, debugging
nomic-embed-text:latest embed 768 dim Semantic search, RAG pipelines, document similarity

Code Examples

Complete examples for /v1/chat in common languages.

Python
JavaScript
PHP
python
import requests

API_KEY  = "sk-your-api-key"
BASE_URL = "https://ai.0-sbatti.it"

def chat(message: str, model: str = "mistral:latest") -> str:
    response = requests.post(
        f"{BASE_URL}/v1/chat",
        headers={
            "X-API-Key": API_KEY,
            "Content-Type": "application/json",
        },
        json={"model": model, "message": message},
        timeout=90,
    )
    response.raise_for_status()
    return response.json()["response"]


# Example usage
answer = chat("What is machine learning?")
print(answer)

# With a different model
code = chat(
    "Write a Python function to sort a list",
    model="qwen2.5-coder:3b"
)
print(code)


# Embeddings example
def embed(text: str) -> list[float]:
    response = requests.post(
        f"{BASE_URL}/v1/embed",
        headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
        json={"text": text},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["embedding"]


vector = embed("Hello world")
print(f"Embedding dimensions: {len(vector)}")
javascript
const API_KEY  = "sk-your-api-key";
const BASE_URL = "https://ai.0-sbatti.it";

async function chat(message, model = "mistral:latest") {
  const response = await fetch(`${BASE_URL}/v1/chat`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model, message }),
    signal: AbortSignal.timeout(90_000),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error || `HTTP ${response.status}`);
  }

  const data = await response.json();
  return data.response;
}

// Example usage (Node.js / browser)
chat("What is the capital of Italy?")
  .then(answer => console.log(answer))
  .catch(console.error);


// Embeddings example
async function embed(text) {
  const response = await fetch(`${BASE_URL}/v1/embed`, {
    method: "POST",
    headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ text }),
  });
  const data = await response.json();
  return data.embedding; // float[]
}

embed("Hello world").then(v => console.log(`${v.length} dimensions`));
php
<?php

define('API_KEY',  'sk-your-api-key');
define('BASE_URL', 'https://ai.0-sbatti.it');

function aiChat(string $message, string $model = 'mistral:latest'): string
{
    $ch = curl_init(BASE_URL . '/v1/chat');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_TIMEOUT        => 90,
        CURLOPT_HTTPHEADER     => [
            'X-API-Key: ' . API_KEY,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'model'   => $model,
            'message' => $message,
        ]),
    ]);

    $body = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code !== 200) {
        $err = json_decode($body, true);
        throw new \RuntimeException($err['error'] ?? "HTTP {$code}");
    }

    return json_decode($body, true)['response'];
}

// Example usage
try {
    $answer = aiChat('Explain cloud computing in one sentence');
    echo $answer . PHP_EOL;

    $code = aiChat(
        'Write a PHP function to validate an email',
        'qwen2.5-coder:3b'
    );
    echo $code . PHP_EOL;
} catch (\RuntimeException $e) {
    echo "Error: " . $e->getMessage() . PHP_EOL;
}

Interactive Playground

Test /v1/chat directly from your browser. Your API key is never stored.

POST /v1/chat
API Key
Model
Message
Equivalent cURL
bash
curl https://ai.0-sbatti.it/v1/chat \
  -H "X-API-Key: sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"model": "mistral:latest", "message": "What is the meaning of life?"}'
200 OK