# XUSS AI API (OpenAI-compatible)

Use XUSS models from any OpenAI-compatible client — SDKs, editors, chat apps.
One key, billed from your XUSS balance.

- **Base URL:** `https://xuss.us/v1`
- **Auth:** `Authorization: Bearer <your-key>` (create a key in the panel → **API**)
- **Endpoints:** `GET /v1/models`, `POST /v1/chat/completions`, `GET /v1/tools`, `GET /v1/skills`

## Quick start

### cURL

```bash
curl https://xuss.us/v1/chat/completions \
  -H "Authorization: Bearer $XUSS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "xuss/kitsune",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

### Python (openai)

```python
from openai import OpenAI

client = OpenAI(base_url="https://xuss.us/v1", api_key="XUSS_API_KEY")
r = client.chat.completions.create(
    model="xuss/kitsune",
    messages=[{"role": "user", "content": "Hello"}],
)
print(r.choices[0].message.content)
```

### Node.js (openai)

```js
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://xuss.us/v1", apiKey: "XUSS_API_KEY" });
const r = await client.chat.completions.create({
  model: "xuss/kitsune",
  messages: [{ role: "user", content: "Hello" }],
});
console.log(r.choices[0].message.content);
```

### Go (net/http)

```go
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
)

func main() {
    body := []byte(`{"model":"xuss/kitsune","messages":[{"role":"user","content":"Hello"}]}`)
    req, _ := http.NewRequest("POST", "https://xuss.us/v1/chat/completions", bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer "+XUSS_API_KEY)
    req.Header.Set("Content-Type", "application/json")
    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}
```

### PHP (curl)

```php
<?php
$ch = curl_init('https://xuss.us/v1/chat/completions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . XUSS_API_KEY, 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'model' => 'xuss/kitsune',
        'messages' => [['role' => 'user', 'content' => 'Hello']],
    ]),
]);
echo json_decode(curl_exec($ch), true)['choices'][0]['message']['content'], PHP_EOL;
```

## Models

`GET /v1/models` returns the models available to you with their prices (USD per
1M tokens):

```json
{
  "object": "list",
  "data": [
    { "id": "xuss/kitsune", "object": "model", "owned_by": "xuss",
      "context_window": 1000000, "vision": true,
      "pricing": { "prompt": 0.00000029, "completion": 0.00000129, "input_cache_read": 0.00000002 } }
  ]
}
```

Model ids are namespaced `xuss/<name>`. Use the exact `id` from this endpoint.

## Chat completions

`POST /v1/chat/completions` — OpenAI schema. Supported request fields:

| Field | Notes |
|---|---|
| `model` | required — an id from `/v1/models` |
| `messages` | required — `system` / `user` / `assistant` / `tool` roles |
| `stream` | `true` for Server-Sent Events |
| `temperature`, `top_p`, `stop`, `seed`, `presence_penalty`, `frequency_penalty` | passed through |
| `max_tokens` (or `max_completion_tokens`) | output cap |
| `tools`, `tool_choice` | function calling (returns `tool_calls`) |
| `response_format` | `{"type":"json_object"}` for raw JSON output |

### System prompt

Your `system` messages are honoured. XUSS adds one small system line so the
model knows its own name — it will answer with the model's configured name when
asked "what model are you?".

### Raw JSON output

Force a JSON answer with the standard OpenAI flag:

```json
{ "model": "xuss/kitsune",
  "messages": [{"role":"user","content":"Give {\"ok\":true}"}],
  "response_format": { "type": "json_object" } }
```

### Images (vision)

For vision-capable models (see `vision: true` in `/v1/models`), send content as
an array with `image_url` parts (a public URL or a `data:image/...;base64,...`
data URL):

```json
{ "model": "xuss/kitsune",
  "messages": [{ "role": "user", "content": [
    { "type": "text", "text": "What is in this image?" },
    { "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }
  ]}]}
```

### Streaming

Set `"stream": true`. The response is SSE `data:` chunks ending with
`data: [DONE]`; the final chunk carries `usage`.

## Function calling

Send OpenAI-style `tools`; the assistant message will contain `tool_calls`, and
you continue with a `tool` role message carrying `tool_call_id`.

## Server tools (skills + web)

Opt in per request with `"xuss_tools": true` (all) or an array (subset). XUSS
then runs these tools **on the server** — the model calls them, the server
executes them and continues, and you get the final answer in the same response
(no client-side loop).

| Tool | What it does |
|---|---|
| `web_search` | Web search (titles, URLs, snippets) |
| `fetch_url` | Fetch an HTTP(S) URL and return its text (HTML → text) |
| `browser_render` | Open a page in a real headless browser (status, title, text, console errors, layout metrics, optional JS `eval`) |
| `read_skill` | Load one of the built-in **skills** (full how-to doc) into context |

These are **read-only** — no file, database, hosting or account access.

```python
r = client.chat.completions.create(
    model="xuss/kitsune",
    xuss_tools=True,                       # or ["web_search", "read_skill"]
    messages=[{"role": "user", "content": "Search the web for the latest Python release."}],
)
```

Available skills (load with `read_skill`, e.g. *"use the product-design skill"*):
`telegram-bots`, `product-design`, `cybersecurity`, `telegram-miniapps`,
`shop-bot`, `payments`, `php-web`, `ai-integration`, `python-backend`,
`node-backend`, `databases`, `rest-api`, `deployment-ops`, `git-github`,
`scraping-automation`, `seo`, `media-pipeline`, `i18n-localization`,
`testing-quality`, `analytics-monitoring`, `legal-templates`,
`react-best-practices`, `mobile-design`, `senior-frontend`, `senior-backend`,
`senior-security`, `ui-design-system`, `tgbot-clone`, `product-layers`,
`find-skills`.

Discover them live: `GET /v1/tools` and `GET /v1/skills`.

## Errors

Errors use the **OpenAI error format**:

```json
{ "error": { "message": "Model 'xuss/foo' not found",
             "type": "invalid_request_error", "param": "model",
             "code": "model_not_found" } }
```

| Status | type / code | Meaning |
|---|---|---|
| 400 | invalid_request_error | bad request |
| 401 | authentication_error · invalid_api_key / key_expired | missing, invalid or expired key |
| 402 | insufficient_quota · key_spend_limit_reached | balance empty or key hit its spend limit |
| 403 | permission_error | not enabled for your account |
| 404 | invalid_request_error · model_not_found | unknown model |
| 413 | invalid_request_error | body too large (> 25 MB) |
| 422 | invalid_request_error | invalid payload |
| 429 | rate_limit_error · rate_limit_exceeded | rate limited |
| 500/502 | api_error · upstream_error | temporary upstream problem |
| 503 | api_error · service_unavailable | API disabled |

## Billing

Every request is billed to your **main XUSS balance** at the model's API price
(shown in `/v1/models` and on the panel **API** page). Cached input tokens are
billed at the cache-read price. The API price can be **cheaper** than the panel
agent. When you use server tools (`xuss_tools`), every tool round is billed the
same way, so the final `usage` covers the whole call.

## Notes

- Rate limits apply per user and per key.
- Keys are shown once at creation; revoke and recreate if leaked (max 10 active).
- Keys can have an optional **expiry date** and **spend limit** (USD) — enforced on every request.
- Key creation in the panel is protected by Cloudflare Turnstile.
- Never expose your key in client-side code.
