furybot

OpenAI-compatible /v1

Base URL ends in /v1. Use a chat-scoped key. Works with Continue, Cline, Aider, OpenClaw (openai-completions), LibreChat, and the official OpenAI SDKs pointed at a custom base URL.

Implemented

  • GET /v1/models, GET /v1/models/{id}
  • POST /v1/chat/completions — blocking and SSE stream: true
  • Tools / tool_calls / role: tool (API adapts upstream Ollama or OpenAI-shaped backends)
  • Vision via multipart content arrays with image_url parts — data:image/…;base64,… or https:// (SSRF-safe: public IPs only, redirect re-checked, size-capped). Images are resized, scanned for child-safety, then sent to a vision-capable model
  • PDF file parts — text extracted into the prompt when a text layer exists; embedded JPEG/PNG streams attached for vision when present. No Tesseract OCR in this binary — scanned pages without embeds need page screenshots + a vision model (practical path; see below)
  • Context / history / learning cache (when Valkey + column encryption are armed): plan byte floors + addon GiB; over quota continuously evicts least-recently-used entries; personal cache is seat-private; shared learning (o/…) is opt-in per org (default on); seat and org purge endpoints bump generation counters (see docs/ADDONS.md)
  • Opt-in web search (SearXNG) — model-only by default
  • max_completion_tokens as an alias of max_tokens
  • GET /v1/chat/queue/{id} — Galtops extension when a completion is deferred (X-Galtops-Chat-Queued)

Web search (opt-in)

Default is model-only — no internet egress, predictable cost and latency. Same posture as the studio chat toggle. Check GET /api/metasearch_enabled for whether this deployment has SearXNG configured.

  • Body: "search": true (studio parity) or "web_search": true
  • Header: X-Galtops-Web-Search: true (when the SDK body is fixed). Body wins over header when both are present
  • Response includes galtops.web_search (used, results, degraded, available). Strict OpenAI clients ignore unknown keys
  • Asking for search when it is not configured → 400. SearXNG unreachable → answer continues model-only with degraded: true
# Model-only (default)
curl -sS https://YOUR-HOST/v1/chat/completions \
  -H "Authorization: Bearer YOUR_CHAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "general",
    "messages": [{"role":"user","content":"Summarise quantum entanglement in two sentences."}]
  }'

# Opt-in web search
curl -sS https://YOUR-HOST/v1/chat/completions \
  -H "Authorization: Bearer YOUR_CHAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "general",
    "search": true,
    "messages": [{"role":"user","content":"What happened in AI regulation this week?"}]
  }'

Vision + PDF

Pick a model that reads images (catalogue label / vision / vl name). Child-safety scanning still runs on every image — that floor is never liftable.

OCR: this API does not run Tesseract. Text-layer PDFs are extracted; embedded rasters go to vision; pure scans should be sent as images (or rendered page screenshots) to a vision model.

# Image (data URI) — Python sketch
import base64, json, urllib.request

img_b64 = base64.b64encode(open("photo.jpg","rb").read()).decode()
body = {
  "model": "vision",  # or your deployment's vision id
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "Describe this and list any text you see."},
      {"type": "image_url", "image_url": {
        "url": f"data:image/jpeg;base64,{img_b64}"
      }},
    ],
  }],
}
req = urllib.request.Request(
  "https://YOUR-HOST/v1/chat/completions",
  data=json.dumps(body).encode(),
  headers={
    "Authorization": "Bearer YOUR_CHAT_KEY",
    "Content-Type": "application/json",
  },
)
print(urllib.request.urlopen(req).read().decode())

# PDF text extract (file part)
# content item:
# {"type":"file","file":{"filename":"brief.pdf",
#   "file_data":"data:application/pdf;base64,..."}}

Context cache & shared learning

When the organisation's context (or history / learning) byte cap is exceeded on write, the platform continuously evicts the least-recently-used sealed entries until the new entry fits — newest stays, oldest goes. TTL is only a backstop.

Personal cache is always private-to-seat (u/… partitions, seat-bound seal). Completions for a user only use that seat's context, history and personal learning — never another seat's. Clear your own seat cache anytime from the profile page (end of day is fine); that does not touch org shared learning.

Shared learning (o/…) is the only org-wide pool. It defaults on and can be turned off by an org admin in the organisation console (/api/org/shared-learning). When off, assistants neither read nor write org-shared learning; seat-private memory continues. Org admins can still purge shared learning or force-purge every seat (type-to-confirm). Never cross-org.

Stubbed and refused

  • POST /v1/embeddings501. Not implemented — an error beats fabricated vectors
  • POST /v1/completions, /v1/responses, /v1/images/* → OpenAI-shaped 404 with a message pointing at the right surface
  • Remote http:// image_url and private / metadata hosts → 400. Public https:// is fetched SSRF-safely

Differences from stock OpenAI

  • Unknown model on completions → 404, not a silent upstream 502
  • 429 includes Retry-After
  • Floors run on every user/tool message and on search/PDF text before inference
  • Web search and galtops.* are Galtops extensions — optional fields clients may ignore
  • No Tesseract OCR — text-layer PDFs and embedded rasters only; pure scan pages need screenshots + vision

Also see

Editor setup: repo docs/integrations.md. Image graphs: /docs/comfy. Studio cards: /docs/api.

OpenAI-compatible /v1 API · Furybot