spiderssense.com Home

Documentation

One OpenAI- and Anthropic-compatible gateway in front of every frontier model. Keep your existing SDK — point it at our base URL and authenticate with your key. Billing is prepaid from your balance; create a key in the dashboard to get started.

Base URLhttps://spiderssense.com/v1
API keysk-uno-…Create one in your dashboard, then use it as the API key / bearer token.

Quickstart

One OpenAI-compatible base URL. Send your key as a Bearer token and call any model by its id.

bash
curl https://spiderssense.com/v1/chat/completions \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5",
    "messages": [{ "role": "user", "content": "Hello!" }]
  }'

OpenCode

Add a provider to your opencode.json (project root or ~/.config/opencode/), then pick a model with /models. The attachment + modalities flags tell OpenCode the model accepts images.

opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "spiderssense": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "spiderssense.com",
      "options": {
        "baseURL": "https://spiderssense.com/v1",
        "apiKey": "sk-uno-YOUR_KEY"
      },
      "models": {
        "gpt-5": {
          "attachment": true,
          "tool_call": true,
          "reasoning": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] }
        }
      }
    }
  }
}

Claude Code

Point Claude Code at our endpoint with two environment variables, then run it as usual.

bash
export ANTHROPIC_BASE_URL="https://spiderssense.com"
export ANTHROPIC_AUTH_TOKEN="sk-uno-YOUR_KEY"
export ANTHROPIC_MODEL="claude-sonnet-4"

claude

Claude Desktop

Wire the Claude Desktop app to our gateway through Configure Third-Party Inference. Five clicks, no rebuild, no extra binaries — your existing Claude Desktop talks to every model we host.

01Step 1 / 5

Open Troubleshooting → Enable Developer Mode

Top-left menu → Help → Troubleshooting → Enable Developer Mode. This unlocks the Developer menu used by the next steps.

Claude Desktop setup screenshot — Step 1
02Step 2 / 5

Developer → Configure Third-Party Inference

A new Developer entry appears in the same menu. Open it and pick Configure Third-Party Inference… — this is where Claude Desktop's model routing lives.

Claude Desktop setup screenshot — Step 2
03Step 3 / 5

Set generous Usage limits (optional)

Bump Max tokens per window to something large (e.g. 10 000 000 000) and Token cap window to 1 hour. This is just a client-side soft cap — real billing happens on our side.

Claude Desktop setup screenshot — Step 3
04Step 4 / 5

Tighten the sandbox (recommended)

Turn ON Disable Claude.ai sign-in so users only see the gateway provider on the login screen. Leave Allow Auto mode and deep-link handling OFF for a clean managed experience.

Claude Desktop setup screenshot — Step 4
05Step 5 / 5

Plug in the gateway credentials

Credential kind: Static API key · Base URL: https://spiderssense.com/ · API key: your sk-uno-… · Auth scheme: bearer. Hit Test connection and Test model discovery — both should turn green and you'll see the full model list auto-populate.

Claude Desktop setup screenshot — Step 5

Cursor

Plug our gateway into Cursor as a custom OpenAI-compatible provider. Open Settings → Models → Add custom model, fill in the fields below, then call any frontier model from Chat / Cmd-K.

Connection modeOpenAI-compatible
Base URLhttps://spiderssense.com/v1
API keysk-uno-…Create one in your dashboard and paste it as the OpenAI API key.
Notes
  • Custom API endpoints in Cursor require a Pro plan or higher.
  • The 7-day Pro trial does not unlock custom endpoints — paid plans only.
Short model aliasesUse these short names anywhere a model id is expected.
AliasModel
cursor48claude-opus-4.8
cursor47claude-opus-4.7
cursor46claude-opus-4.6
cursor45claude-opus-4.5
cursor46sclaude-sonnet-4.6
cursor45sclaude-sonnet-4.5
cursor45hclaude-haiku-4.5
cursorfableclaude-fable-5
cursorgpt54gpt-5.4
cursorgpt55gpt-5.5
cursorgpt56gpt-5.6-sol
cursorgpt56terragpt-5.6-terra
cursorgpt56lunagpt-5.6-luna
cursorgeminigemini-3.1-pro-preview

OpenAI & Anthropic SDK

Drop-in — change only the base URL and the key.

python
# OpenAI SDK
from openai import OpenAI

client = OpenAI(
    base_url="https://spiderssense.com/v1",
    api_key="sk-uno-YOUR_KEY",
)
resp = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)

Key Info API

Programmatic endpoint for resellers and monitoring dashboards. Send your sk-uno-… as a Bearer token — get back the key's balance, spent amount, token counters, request count and status. Public (no session cookie), rate-limited to 60 requests per minute per IP.

Request
bash
curl https://spiderssense.com/v1/key \
  -H "Authorization: Bearer sk-uno-YOUR_KEY"
Example response
json
{
  "data": {
    "id": 1050907,
    "name": "new",
    "key_prefix": "sk-uno-a1da6",
    "is_active": true,
    "is_expired": false,
    "balance": "$31.22",
    "balance_micro": 31221741,
    "spent": "$18.78",
    "spent_micro": 18778259,
    "usage": {
      "input_tokens": 89167860,
      "output_tokens": 637428,
      "cached_tokens": 63619449,
      "requests": 719
    },
    "last_used_at": "2026-07-06T15:51:55.000Z",
    "created_at": "2026-06-30T18:31:17.000Z",
    "expires_at": null
  }
}
Response fields
idNumeric key id (stable across the key's lifetime).
nameHuman-readable label you set when creating the key.
key_prefixFirst 12 chars of the key — safe to display in dashboards.
is_activefalse if the key was deactivated. Requests will return 401.
is_expiredtrue if expires_at is set and in the past.
balance / balance_microRemaining prepaid USD on the key. Micro = 1/1,000,000 USD.
spent / spent_microLifetime spend on this key.
usage.input_tokensCumulative input tokens across all requests.
usage.output_tokensCumulative output tokens.
usage.cached_tokensCumulative cached-input tokens (charged at cache pricing).
usage.requestsTotal requests billed to this key.
last_used_atISO timestamp of the most recent billed request. null if unused.
created_atWhen the key was created.
expires_atOptional expiry. null means no expiry.

Count Tokens

Estimate how many input tokens your request will spend BEFORE sending it. Anthropic-compatible endpoint — free, doesn't call the LLM, doesn't decrement your balance. Perfect for cost preview in UIs and budget planning.

cURL
bash
curl https://spiderssense.com/v1/messages/count_tokens \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4.8",
    "system": "You are a scientist",
    "messages": [{ "role": "user", "content": "Hello, Claude" }]
  }'
Anthropic Python SDK
python
from anthropic import Anthropic

client = Anthropic(
    base_url="https://spiderssense.com",
    api_key="sk-uno-YOUR_KEY",
)
response = client.messages.count_tokens(
    model="claude-opus-4.8",
    system="You are a scientist",
    messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(response.json())
Response
json
{ "input_tokens": 15 }
Notes
  • Exact BPE count for text and code — the same tokenizer the model itself uses.
  • Free but rate-limited: 60 calls per minute per key, 120 per minute per IP.
  • Requires a valid sk-uno-… key, but balance is NOT checked — you can call it with a $0 balance.
  • Payload cap: 32 KB per request. Larger prompts must be estimated client-side.

Image Generation

Generate images from a text prompt. Chat-native models use /v1/chat/completions with modalities: ["image", "text"]. Multiple fixed-price models from the live catalog use the native OpenAI-compatible /v1/images/generations endpoint, not just Midjourney. Both return base64 image data and are billed from your prepaid balance.

cURL
bash
curl https://spiderssense.com/v1/chat/completions \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-image-2",
    "messages": [
      { "role": "user", "content": "A red fox in a snowy forest, cinematic" }
    ],
    "modalities": ["image", "text"]
  }' \
  --max-time 180 \
  -o image.json
Response
json
{
  "choices": [
    {
      "message": {
        "content": "",
        "images": [
          {
            "image_url": {
              "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA…"
            }
          }
        ]
      }
    }
  ],
  "usage": {
    "prompt_tokens": 14,
    "completion_tokens": 229,
    "completion_tokens_details": { "image_tokens": 1 },
    "cost": 0.04
  }
}
Save the PNG
bash
# pull the data URL, strip the prefix, decode to a PNG
jq -r '.choices[0].message.images[0].image_url.url' image.json \
  | sed 's/^data:image\/png;base64,//' \
  | base64 -d > image.png
Image-to-image edit
bash
# encode the source image as a data URL
IMG_B64=$(base64 -w 0 source.png)

curl https://spiderssense.com/v1/chat/completions \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"openai/gpt-image-2\",
    \"messages\": [{
      \"role\": \"user\",
      \"content\": [
        { \"type\": \"text\", \"text\": \"Replace the red car with a blue car\" },
        { \"type\": \"image_url\", \"image_url\": { \"url\": \"data:image/png;base64,$IMG_B64\" } }
      ]
    }],
    \"modalities\": [\"image\", \"text\"]
  }" \
  --max-time 180 \
  -o image.json
Image-to-image (Python)
python
import base64
from openai import OpenAI

client = OpenAI(
    base_url="https://spiderssense.com/v1",
    api_key="sk-uno-YOUR_KEY",
    timeout=180.0,
)

source = base64.b64encode(open("source.png", "rb").read()).decode()
data_url = f"data:image/png;base64,{source}"

resp = client.chat.completions.create(
    model="openai/gpt-image-2",
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": "Replace the red car with a blue car; keep the background",
            },
            {
                "type": "image_url",
                "image_url": {"url": data_url},
            },
        ],
    }],
    extra_body={"modalities": ["image", "text"]},
)

result_url = resp.choices[0].message.images[0]["image_url"]["url"]
result = base64.b64decode(result_url.split(",", 1)[1])
open("edited.png", "wb").write(result)
print(f"saved edited.png ({len(result)} bytes)")
How image-to-image works
  1. Read the source PNG, JPEG or WEBP and encode its bytes as base64.
  2. Put the text edit instruction and the image_url block in the same user message. The URL must include the data:image/...;base64, prefix.
  3. Send modalities: ["image", "text"] to /v1/chat/completions and allow up to 180 seconds.
  4. Decode choices[0].message.images[0].image_url.url exactly like a text-to-image response.
Python (openai SDK)
python
import base64
from openai import OpenAI

client = OpenAI(
    base_url="https://spiderssense.com/v1",
    api_key="sk-uno-YOUR_KEY",
    timeout=180.0,
)
resp = client.chat.completions.create(
    model="openai/gpt-image-2",
    messages=[{"role": "user", "content": "A red fox in a snowy forest, cinematic"}],
    extra_body={"modalities": ["image", "text"]},
)
url = resp.choices[0].message.images[0]["image_url"]["url"]
b64 = url.split(",", 1)[1]
open("image.png", "wb").write(base64.b64decode(b64))
print("saved image.png")

Native fixed-price image generation

POST /v1/images/generations supports every fixed-price image model exposed by the live catalog. Use the model id shown below; the set of models and their per-image prices can change without a docs deployment, so do not assume Midjourney is the only native generator.

Generic cURL and download
bash
curl https://spiderssense.com/v1/images/generations \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "IMAGE_MODEL_FROM_CATALOG",
    "prompt": "A glass observatory above a bioluminescent forest",
    "n": 1,
    "response_format": "b64_json"
  }' \
  --max-time 300 \
  -o generated-image.json

jq -r '.data[0].b64_json' generated-image.json | base64 -d > generated-image.png
Generic Python (openai SDK)
python
import base64
from openai import OpenAI

client = OpenAI(
    base_url="https://spiderssense.com/v1",
    api_key="sk-uno-YOUR_KEY",
    timeout=300.0,
)
response = client.images.generate(
    model="IMAGE_MODEL_FROM_CATALOG",
    prompt="A glass observatory above a bioluminescent forest",
    n=1,
    response_format="b64_json",
)
image = base64.b64decode(response.data[0].b64_json)
open("generated-image.png", "wb").write(image)
print(f"saved generated-image.png ({len(image)} bytes)")
Native Images API response
json
{
  "created": 1783593159,
  "model": "IMAGE_MODEL_FROM_CATALOG",
  "data": [
    {
      "b64_json": "iVBORw0KGgoAAAANSUhEUgAA…",
      "revised_prompt": "A glass observatory above a bioluminescent forest"
    }
  ]
}
Provider-specific size behavior
  • All native fixed-price generators currently accept n: 1 only.
  • Grok image generation is fixed at 1K output; a requested size cannot increase it.
  • Seedream forces 2K output regardless of the requested size.
  • Wan and Qwen use their provider defaults when size is omitted. Pass size only when the selected model documents support for the requested value.
  • Prices are loaded dynamically from the catalog. Check the current catalog instead of relying on a hardcoded amount.

Midjourney Task details

midjourney-fast-imagine is a Task-backed text-to-image model. It accepts one prompt, n: 1 and optional provider parameters, waits for the task to finish, then returns the image in data[0].b64_json.

Model catalogFetched live. Fixed-price rows use /v1/images/generations; prices remain catalog-driven.
Model idEndpoint
Constraints & tips
  • For /v1/chat/completions, modalities must include "image" — without it the model replies with text only.
  • Image-to-image is supported by openai/gpt-image-2 and google/gemini-3.1-flash-image: add an image_url data URL next to the text instruction in the user message.
  • Use /v1/images/generations for every fixed-price image row in the live catalog; native generation is not limited to Midjourney.
  • The image comes back at choices[0].message.images[0].image_url.url as a data:image/png;base64,… string. Strip the prefix and base64-decode to get the raw PNG bytes.
  • message.content is usually an empty string — the picture lives in images, not in content.
  • Non-streaming only: image models ignore stream:true. Set your HTTP client timeout to at least 180 s (generation can take 1–3 min).
  • Response size: ~0.5–2 MB base64 per image inside one JSON.
  • Billing: charged per image from your key balance the moment the response returns (usage.cost / completion_tokens_details.image_tokens).

Video Generation

POST /v1/videos/generations supports two response modes. Kling, Wan 2.6 and Vidu create asynchronous jobs that you poll; existing Veo models remain synchronous and return the mp4 inline as base64. Video is billed per generated second from your prepaid balance.

Async video jobs

For the models below, POST /v1/videos/generations returns HTTP 202 with {id, status}. Kling v3 and Vidu Q3 Pro accept image + last_frame; Wan 2.6 accepts image only. Poll GET /v1/videos/generations/{id} using the same bearer key and download the temporary signed URL immediately.

kling-v3-t2vWan-AI/Wan2.6-T2Vviduq3-pro
Async cURL: submit, poll, download
bash
# 1) Submit a job. This returns HTTP 202 immediately.
JOB_ID=$(curl -sS https://spiderssense.com/v1/videos/generations \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-v3-t2v",
    "prompt": "waves crashing on a rocky shore at sunset",
    "duration_seconds": 8,
    "aspect_ratio": "16:9"
  }' | jq -r '.id')

# 2) Poll with the same bearer key until completed or failed.
while true; do
  RESULT=$(curl -sS "https://spiderssense.com/v1/videos/generations/$JOB_ID" \
    -H "Authorization: Bearer sk-uno-YOUR_KEY")
  STATUS=$(printf '%s' "$RESULT" | jq -r '.status')
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && { printf '%s\n' "$RESULT"; exit 1; }
  sleep 5
done

# 3) The signed URL is temporary. Download the video immediately.
URL=$(printf '%s' "$RESULT" | jq -r '.url')
curl -L "$URL" -o video.mp4
HTTP 202 response
json
HTTP/1.1 202 Accepted
{
  "id": "video_q1w2e3r4t5y6u7i8o9p0a1s2d3f4g5h6",
  "status": "queued"
}
Completed job
json
{
  "id": "video_q1w2e3r4t5y6u7i8o9p0a1s2d3f4g5h6",
  "status": "completed",
  "url": "https://storage.example.com/video.mp4?X-Amz-Signature=…",
  "duration_seconds": 8,
  "expires_at": "2026-07-17T15:04:05.000Z"
}
Async Python (httpx)
python
import time
import httpx

base_url = "https://spiderssense.com"
headers = {"Authorization": "Bearer sk-uno-YOUR_KEY"}

with httpx.Client(timeout=60.0) as client:
    response = client.post(
        f"{base_url}/v1/videos/generations",
        headers=headers,
        json={
            "model": "Wan-AI/Wan2.6-T2V",
            "prompt": "a red apple on a wooden table, cinematic close-up",
            "duration_seconds": 8,
            "aspect_ratio": "16:9",
        },
    )
    response.raise_for_status()
    job = response.json()  # HTTP 202: {"id": ..., "status": ...}

    while job["status"] in {"queued", "running"}:
        time.sleep(5)
        response = client.get(
            f"{base_url}/v1/videos/generations/{job['id']}",
            headers=headers,
        )
        response.raise_for_status()
        job = response.json()

    if job["status"] == "failed":
        raise RuntimeError(f"Video generation failed: {job}")

    # The completed URL is signed and temporary, so download it now.
    video = client.get(job["url"])
    video.raise_for_status()
    open("video.mp4", "wb").write(video.content)
    print(f"saved video.mp4 ({job['duration_seconds']} s); URL expires {job['expires_at']}")

Synchronous Veo models

Existing Google Veo models keep the synchronous contract: the POST request waits for generation and returns a base64-encoded mp4 in data[0].b64_json. The text-to-video and image-to-video examples below remain valid for those models.

Text-to-video request
bash
curl https://spiderssense.com/v1/videos/generations \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/veo-3.1-1080p-audio",
    "prompt": "waves crashing on a rocky shore at sunset, cinematic slow motion",
    "duration_seconds": 8,
    "aspect_ratio": "16:9"
  }' \
  --max-time 300 \
  -o video.json
Response
json
{
  "created": 1783593159,
  "model": "google/veo-3.1-1080p-audio",
  "data": [
    {
      "b64_json": "AAAAIGZ0eXBpc29tAAACAG…",  // full mp4 in base64
      "mime_type": "video/mp4",
      "resolution": "1080p",
      "duration_seconds": 8
    }
  ],
  "usage": { "video_seconds": 8 }
}
Save the mp4
bash
# decode the base64 payload to a playable mp4
jq -r '.data[0].b64_json' video.json | base64 -d > video.mp4

# or in one shot, streaming with python
python -c "
import json, base64, sys
d = json.load(open('video.json'))
open('video.mp4','wb').write(base64.b64decode(d['data'][0]['b64_json']))
"
First / last frame request
bash
# Encode the first and optional last frames.
FIRST_B64=$(base64 -w 0 first.png)
LAST_B64=$(base64 -w 0 last.png)

# Omit last_frame for first-frame-only generation.
curl https://spiderssense.com/v1/videos/generations \
  -H "Authorization: Bearer sk-uno-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"google/veo-3.1-1080p-audio\",
    \"prompt\": \"the person smiles and turns to look at the camera\",
    \"duration_seconds\": 8,
    \"image\": {
      \"b64_json\": \"$FIRST_B64\",
      \"mime_type\": \"image/png\"
    },
    \"last_frame\": {
      \"b64_json\": \"$LAST_B64\",
      \"mime_type\": \"image/png\"
    }
  }" \
  --max-time 300 \
  -o video.json
Python (openai SDK)
python
import base64, httpx

client = httpx.Client(timeout=300.0)
resp = client.post(
    "https://spiderssense.com/v1/videos/generations",
    headers={"Authorization": "Bearer sk-uno-YOUR_KEY"},
    json={
        "model": "google/veo-3.1-1080p-audio",
        "prompt": "a red apple on a wooden table, cinematic close-up",
        "duration_seconds": 4,
    },
)
data = resp.json()
mp4 = base64.b64decode(data["data"][0]["b64_json"])
open("video.mp4", "wb").write(mp4)
print(f"saved {len(mp4)} bytes, billed {data['usage']['video_seconds']} s")
Model catalogFetched live from the model catalog. Prices are per generated second.
Model idResolutionAudioOur priceMarket
Constraints & tips
  • Async models: POST returns 202; poll GET /v1/videos/generations/{id} with the same bearer key while status is queued or running.
  • Async terminal statuses are completed and failed. On completed, immediately download url before expires_at; the signed URL is temporary.
  • For synchronous Veo models, duration_seconds must be one of 4, 6 or 8. Anything else returns 400.
  • Frame controls use image for the first frame and last_frame for the final frame. Kling v3, Vidu Q3 Pro and Veo 2/3.1 support both; Wan 2.6 supports image only.
  • aspect_ratio: "16:9" (default) or "9:16" for vertical shorts.
  • Synchronous Veo response: set your HTTP client timeout to at least 300 s. Typical latency is 40–180 s.
  • Synchronous Veo response size: 800 KB – 25 MB base64 mp4 embedded in one JSON. Decode data[0].b64_json and write bytes to disk.
  • Synchronous Veo image input (optional): PNG / JPEG / WEBP encoded as base64 without the data:...;base64, prefix. It becomes the first frame.
  • Billing: video_seconds × per-second price of the chosen model. Debited from your key balance the moment the response comes back.
  • Playground rejects video models — use POST /v1/videos/generations only.