Attribute API usage and DA spend to individual end-users so you can bill them accurately and detect abuse.
If you are building an application on top of OpenDunes — a SaaS product, an internal tool with multiple users, or a multi-tenant platform — you need to know which of your users is responsible for each API call. Two tools do it: the user field on each request, and one key per app or segment.
user field#Send your own identifier for the end-user in user:
import os
from openai import OpenAI
client = OpenAI(base_url="https://opendunes.com/v1", api_key=os.environ["OPENDUNES_API_KEY"])
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Summarize this for me."}],
user="customer-4521",
extra_body={"session_id": "chat-2026-09-24-001"},
)| Where it goes | |
|---|---|
| the model's provider | user travels with the request, as you sent it |
| your Broadcast destinations | every trace carries it as the user id, and session_id as the session; a destination's filter rules can select on user_id |
| your usage history | not there — Activity and /v1/activity count per account and model, not per end-user |
So per-user spend is read in your own monitoring tool, from the traces: each carries its cost in DA. Use an opaque id, never a name or an email.
For per-segment totals without a monitoring tool, use one key per segment.
Create a dedicated API key for each app, tenant, or user segment you want to track. Every request made with that key is attributed to it in analytics, so per-key usage is your per-segment usage.
Create keys in Dashboard → Keys, or programmatically via the Management API — authenticated with a management key (mk-…, created in Dashboard → Settings → Management Keys):
curl -X POST https://opendunes.com/v1/keys \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "tenant-acme", "limit": 1000, "limit_reset": "monthly" }'The 201 response carries the key under data — its hash names it from then on — and the raw sk- key in key, shown once only, so store it immediately. limit caps what the tenant's key spends, in DA. Then route each tenant's traffic through its own key:
import os
from openai import OpenAI
acme = OpenAI(
base_url="https://opendunes.com/v1",
api_key=os.environ["OPENDUNES_KEY_TENANT_ACME"], # this tenant's own sk- key
)
resp = acme.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Summarize this for me."}],
)Name keys after the tenant or segment (tenant-acme, mobile-app, free-tier) — the name is how you will recognize them in analytics and exports.
Per-key usage lives in Dashboard → Analytics (session-authenticated — it is a dashboard surface, not a Bearer API):
key_id (plus model, from, to) — download one export per tenant, or one full export and group by key on your side.Download the export CSV from the dashboard, then aggregate. The cost column is total_cost, an integer in micro-DA (1 DA = 1,000,000 micro-DA) — divide by 1,000,000 for DA:
import csv
from collections import defaultdict
def spend_da(csv_path: str) -> float:
with open(csv_path, newline="") as f:
rows = list(csv.DictReader(f))
micro = sum(int(row["total_cost"]) for row in rows) # micro-DA, exact integers
return micro / 1_000_000
# One export per tenant key: the file's total IS that tenant's spend
tenants = ["tenant-acme", "tenant-globex", "mobile-app"]
spend = {t: spend_da(f"{t}-june.csv") for t in tenants}
for tenant, da in sorted(spend.items(), key=lambda x: -x[1]):
print(f"{tenant:20s} {da:.2f} DA")Because total_cost is integer micro-DA, sums reconcile exactly against your balance history — no float drift.
Export a recent window per key and flag segments whose spend exceeds a threshold:
def flag_spikes(spend: dict[str, float], threshold_da: float = 50.0) -> list[str]:
return [key for key, da in spend.items() if da > threshold_da]For an automated check without exports, read each key's own usage with that key at GET /v1/key — usage_daily, usage_weekly and usage_monthly, in DA.
A key name is yours to choose, and a user value travels to the model's provider and to your own destinations. The mapping between a key or a user id and your actual users lives entirely on your side — if your application handles Algerian personal data, keep both opaque, and make sure that mapping table complies with your own Law 18-07 obligations.
If you want to enforce a per-user request cap on your side, track request counts in Redis or a similar store keyed by user ID, and short-circuit before calling the OpenDunes API when the user exceeds your limit:
import time
import redis
r = redis.Redis()
def check_user_rate_limit(user_id: str, max_rpm: int = 10) -> bool:
key = f"ratelimit:{user_id}:{int(time.time() // 60)}"
count = r.incr(key)
r.expire(key, 120) # 2-minute TTL
return count <= max_rpmOpenDunes puts no cap on the number of requests, so this limit is yours alone — it runs entirely on your infrastructure.