CookbookCount cost from usage

Count cost from usage

Every response reports the tokens it used in usage, and the x-syntic-billing-source header says how it was paid: subscription (inside your plan’s allowance, no per-token charge) or api_credit (charged to your usage-credit wallet at the model’s rate). With those two pieces you can log the cost of each request in your own system.

The rates

Dollars per million tokens, from Pricing:

ModelInputCached inputOutput
Amara S1.22.500.4012.00
Amara Quick1.500.258.00
Amara Deep2.500.2512.50
Syntic Code2.500.2512.50
Syntic Code Fast4.500.7020.00

Cached input means prompt tokens served from the prompt cache. They are reported as usage.cached_tokens (some responses nest it as usage.prompt_tokens_details.cached_tokens) and are part of prompt_tokens, not in addition to it.

import os
 
import requests
 
RATES = {  # $ per 1M tokens: input, cached input, output
    "Amara S1.2": (2.50, 0.40, 12.00),
    "Amara Quick": (1.50, 0.25, 8.00),
    "Amara Deep": (2.50, 0.25, 12.50),
    "Syntic Code": (2.50, 0.25, 12.50),
    "Syntic Code Fast": (4.50, 0.70, 20.00),
}
 
 
def cost_usd(model: str, usage: dict, billing_source: str) -> float:
    if billing_source != "api_credit":
        return 0.0                                   # inside the plan allowance
    rin, rcache, rout = RATES[model]
    details = usage.get("prompt_tokens_details") or {}
    cached = min(usage.get("cached_tokens") or details.get("cached_tokens") or 0, usage["prompt_tokens"])
    fresh = usage["prompt_tokens"] - cached
    return (fresh * rin + cached * rcache + usage["completion_tokens"] * rout) / 1_000_000
 
 
requested = "Amara Quick"
r = requests.post(
    "https://app.syntic.ai/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['SYNTIC_API_KEY']}"},
    json={"model": requested, "stream": False, "messages": [{"role": "user", "content": "Two facts about otters."}]},
    timeout=(10, 300),
)
r.raise_for_status()
answered_by = r.headers.get("x-syntic-model-downgraded") or requested   # a fallback changes the rate
source = r.headers.get("x-syntic-billing-source", "subscription")
usage = r.json()["usage"]
print(f"{answered_by}: {usage['total_tokens']} tokens, ${cost_usd(answered_by, usage, source):.6f} ({source})")

Streamed requests

With streaming, usage arrives on a chunk near the end of the stream. Keep the last usage you see and compute the cost after [DONE]. Read x-syntic-billing-source from the response headers before you start reading events.

Things that make the real bill differ

  • Fallbacks. If the model you asked for is not in your plan, the default model answers and x-syntic-model-downgraded names it. Use that name for the rate, as the code above does.
  • Cancelled streams. If you disconnect before the final usage chunk, Syntic bills an estimate. Your log will have no usage for that request; record it as “unknown”.
  • Rounding. The Console is the authority for what was charged. Your own figure is for monitoring and per-customer attribution, and should match within a fraction of a cent per request.

Keep the rate table in configuration, not code, and check it against Pricing when prices change.

Was this page helpful?