Skip to main content
Orux AIOruxAI
Back to articles

Orux AI / Model notes

Claude Sonnet 5.5 API: Python, JSON Output and Cost per Call

Call the Claude Sonnet 5.5 API from Python with the OpenAI SDK, validate JSON output, and see real per-call cost: 190/950 Credits per 1M tokens on Orux AI.

Orux AIPublished 9 min read
Claude Sonnet 5.5 API: Python, JSON Output and Cost per Call — Call the Claude Sonnet 5.5 API from Python with the OpenAI SD…
On this page

The Claude Sonnet 5.5 API costs $2 per 1M input tokens and $10 per 1M output tokens at Anthropic's list price. On Orux AI the same model, claude-sonnet-5-5, is 190 Credits ($0.95) per 1M input tokens and 950 Credits ($4.75) per 1M output tokens, and you call it with the standard OpenAI Python SDK by changing only base_url. Below: the price table, a working Python and cURL call, a JSON extraction case we actually ran twice, a validator that caught the first run's mismatch, and what those two calls cost.

Claude Sonnet 5.5 API at a glance

Claude Sonnet 5.5 is Anthropic's mid-tier model, released on September 28, 2026. It is the one most teams reach for when Haiku is too shallow and Opus is too expensive. Here is what it costs per 1M tokens, as of Oct 29, 2026.

Rate (per 1M tokens)Anthropic list priceOrux AIOrux as % of list
Input$2.00190 Credits ($0.95)47.5%
Output$10.00950 Credits ($4.75)47.5%
Cache read$0.1012 Credits ($0.06)60%
5-minute cache write$2.50237.5 Credits ($1.19)47.5%
1-hour cache write$4.00380 Credits ($1.90)47.5%

Swipe horizontally to view the full table.

Sources: Anthropic pricing and the Claude Sonnet 5.5 model page. Orux AI bills in Credits at a fixed 1 Credit = $0.005 (200 Credits = $1); dollar figures in brackets are only there so you can compare.

Three things the table does not show:

  • Every chat request on Orux AI has a minimum charge of 0.001 Credits.
  • Anthropic's Batch API gives 50% off. Orux AI does not offer batch processing for this model, so the comparison above is against standard, non-batch pricing.
  • Failed requests are not charged. You pay only for completed calls, from a prepaid balance with no monthly fee.

For a full breakdown of all three Claude tiers, caching maths and the Haiku 100K threshold, see Claude API pricing.

Call Claude Sonnet 5.5 with the OpenAI SDK

Orux AI exposes an OpenAI-compatible Chat Completions endpoint at https://orux.top/api/v1. Create a key at /keys; the full key is shown once, at creation, so store it in an environment variable straight away.

Python
# pip install openai
import os
from openai import OpenAI

client = OpenAI(base_url="https://orux.top/api/v1", api_key=os.environ["ORUX_API_KEY"])

resp = client.chat.completions.create(
    model="claude-sonnet-5-5",
    messages=[{"role": "user", "content": "Summarize this in 3 bullet points: ..."}],
    max_tokens=500,
)
print(resp.choices[0].message.content)

# Estimate the charge in Credits from the published rates (Credits per 1M tokens)
IN_RATE, OUT_RATE = 190, 950  # claude-sonnet-5-5, checked 2026-10-29
u = resp.usage
print(f"~ {(u.prompt_tokens * IN_RATE + u.completion_tokens * OUT_RATE) / 1e6:.6f} Credits")

The same request with cURL:

Shell
curl https://orux.top/api/v1/chat/completions \
  -H "Authorization: Bearer $ORUX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-5-5", "max_tokens": 500,
       "messages": [{"role": "user", "content": "Summarize this in 3 bullet points: ..."}]}'

Streaming works with stream=True, as with any OpenAI-compatible endpoint. Error handling is worth getting right on day one: a 401 means the key is wrong, a 402 means your balance is empty (top up, do not rotate keys), and a 429 means slow down with exponential backoff and jitter.

A note on the alternatives. Anthropic also ships its own OpenAI SDK compatibility layer, but its documentation describes that layer as intended mainly to test and compare model capabilities, not as a long-term production path (Anthropic docs). If you want the native feature set, use Anthropic's own SDK directly. Orux AI is a different thing: an independent third-party gateway where one key and one OpenAI-style client reach Claude alongside GPT, Gemini, DeepSeek and others, billed from one balance. Pick whichever matches how many models you need to run.

Getting reliable JSON from Claude

On Orux AI, the Claude Sonnet 5.5 endpoint documents messages and max_tokens. We did not use a JSON mode or a schema parameter for the test below: it is prompt-based generation followed by validation in code. That is the pattern we recommend regardless of model, because "the model returned parseable JSON" and "the data is correct" are two different claims.

The extraction contract

We ran this on October 29, 2026 with four fictional museum loan records and max_tokens=1000. The records are designed to trip up a careless extractor: a conflicting unapproved valuation, an explicitly confirmed zero, a missing gallery, and an impossible date (February 30). This is the complete first request:

Text
Extract this fictional museum loan inventory. Return JSON only, without Markdown. The root has exactly one key, items. Each item has exactly id, title, gallery, insurance_usd, arrival_date. Preserve the stated item order. insurance_usd is a nonnegative JSON number or null; an explicitly confirmed zero is 0, not null. arrival_date is an actual calendar date in YYYY-MM-DD or null. Missing, unapproved, conflicting or impossible values must be null. Never choose between conflicting unapproved estimates. Do not infer facts.

A1: Brass compass. Gallery A. Approved insurance USD 12000. Arrival 2026-11-14.
B2: Silk route map. Gallery B. Draft insurance estimates: USD 8000 in one note and USD 9000 in another; neither approved. Arrival 2026-11-16.
C3: Ceramic sherd. Insurance is explicitly confirmed as USD 0. Gallery and arrival date are unconfirmed.
D4: Glass lens. Gallery D. Approved insurance USD 500. Arrival is written as 2026-02-30; the actual date has not been confirmed.

First output vs clarified rerun

The first response, unchanged:

JSON
{"items":[{"id":"A1","title":"Brass compass","gallery":"A","insurance_usd":12000,"arrival_date":"2026-11-14"},{"id":"B2","title":"Silk route map","gallery":"B","insurance_usd":null,"arrival_date":"2026-11-16"},{"id":"C3","title":"Ceramic sherd","gallery":null,"insurance_usd":0,"arrival_date":null},{"id":"D4","title":"Glass lens","gallery":"D","insurance_usd":500,"arrival_date":null}]}

It got the hard parts right. The confirmed zero for C3 survived as 0, B2 did not pick 8000 or 9000, and February 30 became null. But it shortened "Gallery A" to "A". Our prompt never said to keep the full label, so this is a gap in the contract, not a misunderstanding of the facts. An import table keyed on "Gallery A" would still break.

We appended one sentence and reran with the same records and parameters:

Text
For gallery, preserve the full source label exactly (for example, "Gallery A", not "A"). Never shorten or translate these labels.

The second response, also unedited. Save it as response.json:

JSON
{
  "items": [
    {"id": "A1", "title": "Brass compass", "gallery": "Gallery A", "insurance_usd": 12000, "arrival_date": "2026-11-14"},
    {"id": "B2", "title": "Silk route map", "gallery": "Gallery B", "insurance_usd": null, "arrival_date": "2026-11-16"},
    {"id": "C3", "title": "Ceramic sherd", "gallery": null, "insurance_usd": 0, "arrival_date": null},
    {"id": "D4", "title": "Glass lens", "gallery": "Gallery D", "insurance_usd": 500, "arrival_date": null}
  ]
}

Both calls finished with finish_reason=stop. Note that a single null collapses several reasons (unapproved, unconfirmed, impossible) into one value. A real import should keep the source text and a review queue alongside the JSON.

A runnable validator

The validator checks the root key, the exact field set, non-empty unique IDs, value types, non-negative amounts and real calendar dates. Given a second file, it also compares every value and the item order against a reference answer. For this example an editor transcribed the expected values from the source by hand; they match the second response, so expected.json has the same content as response.json.

The Node version we used (Node.js 22), saved as validate.mjs:

JavaScript
import fs from 'node:fs';
import {pathToFileURL} from 'node:url';
import {isDeepStrictEqual} from 'node:util';
export function validate(raw, expected) {
  const x = JSON.parse(raw), keys = ['id','title','gallery','insurance_usd','arrival_date'];
  const object = v => v !== null && typeof v === 'object' && !Array.isArray(v);
  const sameKeys = (v, k) => object(v) && Object.keys(v).sort().join('|') === [...k].sort().join('|');
  if (!sameKeys(x, ['items']) || !Array.isArray(x.items) || !x.items.length) throw Error('items');
  const seen = new Set();
  for (const item of x.items) {
    if (!sameKeys(item, keys)) throw Error('keys');
    if (typeof item.id !== 'string' || !item.id.trim() || seen.has(item.id)) throw Error('id');
    seen.add(item.id);
    for (const k of ['title','gallery']) {
      if (item[k] !== null && (typeof item[k] !== 'string' || !item[k].trim())) throw Error(k);
    }
    const n = item.insurance_usd;
    if (n !== null && (typeof n !== 'number' || !Number.isFinite(n) || n < 0)) throw Error('insurance_usd');
    const d = item.arrival_date;
    if (d !== null) {
      if (typeof d !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(d)) throw Error('arrival_date');
      const parsed = new Date(d + 'T00:00:00Z');
      if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0,10) !== d) throw Error('arrival_date');
    }
  }
  if (expected !== undefined && !isDeepStrictEqual(x, expected)) throw Error('source_mismatch');
  return x;
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  try {
    const expected = process.argv[3] ? JSON.parse(fs.readFileSync(process.argv[3],'utf8')) : undefined;
    const data = validate(fs.readFileSync(process.argv[2],'utf8'), expected);
    console.log(`PASS ${data.items.length} items; source fixture ${expected ? 'checked' : 'NOT checked'}`);
  } catch (error) { console.error('REJECT', error.message); process.exitCode = 1; }
}

The Python equivalent, validate.py, with the same checks and the same error labels:

Python
import json, math, re, sys
from datetime import date

KEYS = {"id", "title", "gallery", "insurance_usd", "arrival_date"}


def validate(raw, expected=None):
    x = json.loads(raw)  # Markdown fences or prose around the JSON fail here
    if not isinstance(x, dict) or set(x) != {"items"} or not isinstance(x["items"], list) or not x["items"]:
        raise ValueError("items")
    seen = set()
    for item in x["items"]:
        if not isinstance(item, dict) or set(item) != KEYS:
            raise ValueError("keys")
        item_id = item["id"]
        if not isinstance(item_id, str) or not item_id.strip() or item_id in seen:
            raise ValueError("id")
        seen.add(item_id)
        for k in ("title", "gallery"):
            v = item[k]
            if v is not None and (not isinstance(v, str) or not v.strip()):
                raise ValueError(k)
        n = item["insurance_usd"]
        if n is not None and (isinstance(n, bool) or not isinstance(n, (int, float))
                              or not math.isfinite(n) or n < 0):
            raise ValueError("insurance_usd")
        d = item["arrival_date"]
        if d is not None:
            if not isinstance(d, str) or not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", d):
                raise ValueError("arrival_date")
            try:
                date.fromisoformat(d)  # rejects 2026-02-30
            except ValueError:
                raise ValueError("arrival_date") from None
    if expected is not None and x != expected:
        raise ValueError("source_mismatch")
    return x


if __name__ == "__main__":
    try:
        expected = json.load(open(sys.argv[2])) if len(sys.argv) > 2 else None
        data = validate(open(sys.argv[1]).read(), expected)
        checked = "checked" if expected is not None else "NOT checked"
        print(f"PASS {len(data['items'])} items; source fixture {checked}")
    except json.JSONDecodeError:
        print("REJECT json", file=sys.stderr)
        sys.exit(1)
    except ValueError as e:
        print("REJECT", e, file=sys.stderr)
        sys.exit(1)

Run either one against the response and the reference:

Shell
node validate.mjs response.json expected.json
python validate.py response.json expected.json
# PASS 4 items; source fixture checked

Run it with only response.json and you get a structural check, which would happily accept a wrong amount with a valid numeric type. The reference comparison is what catches content errors. Against that reference, the first response failed with source_mismatch (the shortened gallery labels) and the second passed.

To call the model and validate in one step, reuse the client from the quickstart:

Python
import json
from validate import validate

PROMPT = open("prompt.txt").read()        # the contract and records above, plus the gallery rule
expected = json.load(open("expected.json"))

resp = client.chat.completions.create(
    model="claude-sonnet-5-5",
    messages=[{"role": "user", "content": PROMPT}],
    max_tokens=1000,
)
data = validate(resp.choices[0].message.content, expected)  # raises on any mismatch

Nine corrupted fixtures, nine rejections

We then broke the reference file nine different ways and ran the validator against each. All nine were rejected; the Python port returns the same labels as the Node original, except that the Markdown case reports json instead of a JSON.parse error. These are tests of the validator, not nine model failures.

  • Duplicate ID: rejected as id
  • Extra key on an item: rejected as keys
  • One item removed: rejected as source_mismatch
  • 12000 changed to the string "12000": rejected as insurance_usd
  • Unknown valuation changed from null to 0: rejected as source_mismatch
  • Confirmed zero changed from 0 to null: rejected as source_mismatch
  • Arrival date set to 2026-02-30: rejected as arrival_date
  • 12000 changed to 12500: rejected as source_mismatch
  • JSON wrapped in Markdown code fences: rejected at parse time

Two model calls and nine fixtures show what happened here. They are not a reliability rate. When validation fails, stop the import and send the record to a person.

What those two calls actually cost

The two calls reported 402 input / 195 output tokens and 446 input / 207 output tokens. At the rates above:

CallTokens (in / out)Orux AIAnthropic list price
First run402 / 1950.26163 Credits ($0.001308)$0.002754
Clarified rerun446 / 2070.28139 Credits ($0.001407)$0.002962
Both848 / 4020.54302 Credits ($0.002715)$0.005716

Swipe horizontally to view the full table.

The arithmetic is just tokens times rate: (402 × 190 + 195 × 950) ÷ 1,000,000 = 0.26163 Credits. Output tokens dominate the bill at a 5:1 output-to-input price ratio, so the cheapest lever is usually a tighter output format, not a shorter prompt. At the average of these two calls (0.27151 Credits), 10,000 similar extractions a month would cost about 2,715 Credits ($13.58) on Orux AI.

When to use Haiku 5.5 or Opus 5.5 instead

Same workload (2,000 input + 500 output tokens per request), priced per 1,000 requests, as of Oct 29, 2026:

ModelOrux AI per 1M (in / out)Orux AI per 1,000 requestsAnthropic list per 1,000 requestsReach for it when
Claude Haiku 5.5 (prompt ≤100K)10.8 / 54 Credits48.6 Credits ($0.243)$0.45High-volume classification, routing, short extraction
Claude Sonnet 5.5190 / 950 Credits855 Credits ($4.275)$9.00Most extraction, drafting, debugging
Claude Opus 5.5380 / 1,900 Credits1,710 Credits ($8.55)$18.00Hard reasoning where a wrong answer is expensive

Swipe horizontally to view the full table.

Haiku 5.5 has a price step: prompts over 100,000 input tokens are billed at 56 / 280 Credits per 1M (official $0.50 / $2.50). A sensible pattern is to run the validator above on Haiku first; if it passes your reference fixtures, you just cut the bill by roughly 17×. If you are weighing Claude against other vendors, Claude Sonnet 5.5 vs GPT-6 Luna vs Gemini 4 Argon compares cost per task across all three.

FAQ

Does Claude Sonnet 5.5 support JSON mode?

On Orux AI, the Claude Sonnet 5.5 endpoint documents messages and max_tokens, and our test used neither a JSON mode nor a schema parameter. We got clean JSON by stating the exact keys, types and null rules in the prompt, then validating the result in code. Treat parseable JSON as the start of checking, not the end.

Can I use the OpenAI Python SDK with Claude?

Yes. Point the OpenAI SDK at https://orux.top/api/v1, pass your Orux AI key, and set model="claude-sonnet-5-5". Nothing else in a standard Chat Completions call changes. Anthropic's own OpenAI compatibility layer exists too, but Anthropic describes it as mainly for testing and comparison rather than long-term production use.

How much does one Claude Sonnet 5.5 call cost?

Multiply tokens by rate: on Orux AI, 190 Credits per 1M input and 950 per 1M output tokens. Our real 402-in / 195-out call cost 0.26163 Credits ($0.0013), versus about $0.0028 at Anthropic's list price. Every chat request has a minimum charge of 0.001 Credits.

Does Orux AI support prompt caching for Claude Sonnet 5.5?

Cache pricing is published on the model page: cache reads are 12 Credits per 1M tokens, 5-minute cache writes 237.5 Credits and 1-hour cache writes 380 Credits. Caching pays off when a long, identical prefix (system prompt, documents) is reused several times within the cache window.

What happens if a request fails?

Failed requests are not charged; you only pay for completed calls. Handle 402 as "balance empty, top up", 401 as a bad key, and 429 as a rate limit to retry with exponential backoff and jitter. A response that parses but fails your validator is a completed call and is billed, which is another reason to keep outputs short.

Sources and next step

Try the saved "Museum inventory · validated extraction" example on the Claude Sonnet 5.5 model page; loading it fills the form and does not start a run. Prices checked on 2026-10-29. Sources: Anthropic pricing, Anthropic OpenAI SDK compatibility, Orux AI pricing.

Try Claude Sonnet 5.5 model page on Orux AIRun it online, then check pricing and API examples on the model page.Open modelTry Claude Haiku 5.5 on Orux AIRun it online, then check pricing and API examples on the model page.Open modelTry Claude Opus 5.5 on Orux AIRun it online, then check pricing and API examples on the model page.Open model

Build with these models through one API

One OpenAI-compatible API for chat, image, video and audio models, billed in Credits.

Related articles

Browse all articles →