[{"data":1,"prerenderedAt":779},["ShallowReactive",2],{"blog-llm-endpoint-abuse-denial-of-wallet-de":3},{"slug":4,"published":5,"minutes":6,"category":7,"tags":8,"keywords":14,"about":27,"sources":35,"cover":69,"og":70,"expertise":71,"locales":72,"lang":74,"title":76,"description":77,"coverAlt":78,"metaTitle":79,"takeaways":80,"faq":86,"toc":102,"blocks":138,"others":592},"llm-endpoint-abuse-denial-of-wallet","2026-10-16",14,"web",[9,10,11,12,13],"Nuxt","LLM security","Rate limiting","OWASP","Cost control",[15,16,17,18,19,20,21,22,23,24,25,26],"denial of wallet llm","owasp llm10 unbounded consumption","rate limit llm api nuxt","nuxt security rate limiter","protect openai api key frontend","llm token budget per user","limit llm cost per user","nitro api rate limiting","cloudflare turnstile nuxt","anthropic spend limit","openai hard spend limit","llm prompt size limit",[28,30,33],{"name":11,"url":29},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",{"name":31,"url":32},"Denial-of-service attack","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDenial-of-service_attack",{"name":9,"url":34},"https:\u002F\u002Fnuxt.com\u002F",[36,39,42,45,48,51,54,57,60,63,66],{"title":37,"url":38},"OWASP GenAI Security Project: LLM10:2025 Unbounded Consumption","https:\u002F\u002Fgenai.owasp.org\u002Fllmrisk\u002Fllm102025-unbounded-consumption\u002F",{"title":40,"url":41},"Nuxt Security: Rate Limiter middleware","https:\u002F\u002Fnuxt-security.vercel.app\u002Fmiddleware\u002Frate-limiter",{"title":43,"url":44},"Nuxt Security: Request Size Limiter middleware","https:\u002F\u002Fnuxt-security.vercel.app\u002Fmiddleware\u002Frequest-size-limiter",{"title":46,"url":47},"nuxt-auth-utils: README (GitHub)","https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils",{"title":49,"url":50},"h3: request utilities (getRequestIP, readValidatedBody)","https:\u002F\u002Fh3.dev\u002Futils\u002Frequest",{"title":52,"url":53},"Upstash: Ratelimiting algorithms","https:\u002F\u002Fupstash.com\u002Fdocs\u002Fredis\u002Fsdks\u002Fratelimit-ts\u002Falgorithms",{"title":55,"url":56},"Cloudflare Turnstile: Validate the token","https:\u002F\u002Fdevelopers.cloudflare.com\u002Fturnstile\u002Fget-started\u002Fserver-side-validation\u002F",{"title":58,"url":59},"Claude Platform Docs: Rate limits and spend limits","https:\u002F\u002Fplatform.claude.com\u002Fdocs\u002Fen\u002Fapi\u002Frate-limits",{"title":61,"url":62},"Claude Platform Docs: Token counting","https:\u002F\u002Fplatform.claude.com\u002Fdocs\u002Fen\u002Fbuild-with-claude\u002Ftoken-counting",{"title":64,"url":65},"OpenAI API Docs: Spend limits","https:\u002F\u002Fdevelopers.openai.com\u002Fapi\u002Fdocs\u002Fguides\u002Fspend-limits",{"title":67,"url":68},"OpenAI API Docs: Rate limits","https:\u002F\u002Fplatform.openai.com\u002Fdocs\u002Fguides\u002Frate-limits","\u002Fimages\u002Fblog\u002Fllm-endpoint-abuse-denial-of-wallet\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fllm-endpoint-abuse-denial-of-wallet\u002Fog.jpg","vue-nuxt-developer",[73,74,75],"en","de","hu","Denial of Wallet: öffentliche LLM-Endpunkte in einer Nuxt-App absichern","Missbrauch und ausufernde Kosten auf einer öffentlichen LLM-Route in Nuxt stoppen: Auth, Rate Limits, Prompt-Limits, Token-Budgets, Turnstile, Spend Limits.","Anfragen an einen Nuxt-Chat-Endpunkt durchlaufen Bot-Prüfung, Session, Rate Limit, Prompt-Limit und Token-Budget, bevor sie die Modell-API erreichen.","Denial of Wallet: Nuxt-LLM-Routen absichern · Balázs Csorba",[81,82,83,84,85],"Eine öffentliche LLM-Route ist eine nutzungsbasiert abgerechnete API mit Ihrer Kreditkarte dahinter; OWASP führt das Risiko als LLM10:2025 Unbounded Consumption.","Vor den Modellaufruf gehört eine Session, und nie ein generischer Proxy: Der Server wählt Modell, System-Prompt und max_tokens.","Anfragen pro Nutzer und pro IP in einem gemeinsamen Speicher begrenzen und Tokens getrennt davon, denn eine Anfrage kann tausendmal mehr kosten als eine andere.","Vor dem Aufruf ein Token-Budget reservieren und danach mit dem tatsächlichen Verbrauch abrechnen, über einen atomaren Zähler.","Spend Limits beim Anbieter sind die letzte Sicherung, nicht die Kontrolle: Sie schalten die Funktion für alle Nutzer gleichzeitig ab und greifen nicht sofort.",[87,90,93,96,99],{"q":88,"a":89},"Was ist Denial of Wallet?","Denial of Wallet ist ein Angriff auf einen nutzungsbasiert abgerechneten Dienst, bei dem es um Kosten statt um Ausfall geht. OWASP nennt ihn als Beispiel für LLM10:2025 Unbounded Consumption: Ein Angreifer erzeugt so viele Operationen, dass die Rechnung für den Betreiber untragbar wird.",{"q":91,"a":92},"Reicht der Rate Limiter von nuxt-security für einen LLM-Endpunkt?","Er ist eine gute erste Schicht, aber laut eigener Dokumentation ist der eingebaute Rate Limiter nicht für komplexe Produktionsanwendungen geeignet. Er zählt Anfragen, keine Tokens, und sein Standard-LRU-Cache gilt pro Serverinstanz. Kombinieren Sie ihn mit einem Limit an der Edge und einem Token-Budget pro Nutzer in einem gemeinsamen Speicher wie Redis.",{"q":94,"a":95},"Soll ich nach IP oder nach Nutzer limitieren?","Beides. IP-Limits schützen die Routen, die vor dem Login funktionieren, und bremsen einfache Fluten. Für die Kosten zählen die Limits pro Nutzer, denn viele legitime Nutzer können sich eine IP teilen, und ein Angreifer kann viele IPs durchwechseln.",{"q":97,"a":98},"Wie begrenze ich die Länge von Prompts?","Die Größe des Request-Bodys begrenzen, die Nachrichtenlänge mit einem Schema prüfen, den Gesprächsverlauf auf dem Server halten statt ihn vom Client anzunehmen, und vor dem Modellaufruf die Token-Zahl prüfen. max_tokens auf dem Server setzen, um die Ausgabe zu deckeln.",{"q":100,"a":101},"Schützen mich Spend Limits des Anbieters vor Missbrauch?","Nur als letzte Instanz. Hard Limits bei OpenAI und Spend Limits bei Anthropic stoppen beim Erreichen den gesamten Verkehr der Organisation, des Projekts oder Workspaces, ein Angreifer legt die Funktion damit für alle lahm. OpenAI weist außerdem darauf hin, dass die Durchsetzung nicht sofort erfolgt und die Ausgaben das Limit leicht überschreiten können.",[103,105,108,111,114,117,120,123,126,129,132,135],{"id":104,"title":88},"what-is-denial-of-wallet",{"id":106,"title":107},"abuse-patterns","Welche Missbrauchsmuster treffen öffentliche LLM-Routen?",{"id":109,"title":110},"layered-defence","Wie sieht eine mehrschichtige Abwehr aus?",{"id":112,"title":113},"authentication-first","Warum kommt die Authentifizierung zuerst?",{"id":115,"title":116},"rate-limits","Wie limitiert man pro Nutzer und pro IP?",{"id":118,"title":119},"input-limits","Wie begrenzt man Prompt-Größe und Ausgabe?",{"id":121,"title":122},"token-budgets","Wie setzt man Token-Budgets pro Nutzer durch?",{"id":124,"title":125},"bot-protection","Wo passen CAPTCHAs und Turnstile hin?",{"id":127,"title":128},"provider-spend-limits","Was fangen Spend Limits beim Anbieter ab?",{"id":130,"title":131},"monitoring","Was sollte man überwachen und alarmieren?",{"id":133,"title":134},"checklist","Checkliste für eine öffentliche LLM-Route",{"id":136,"title":137},"sources","Quellen",[139,143,146,149,157,160,161,164,209,212,213,216,225,226,238,251,252,268,287,289,300,330,333,334,345,352,361,363,364,370,376,391,393,409,410,421,423,430,431,434,486,489,490,493,517,524,525,549,556,557],{"type":140,"content":141},"paragraph",[142],"Eine LLM-Funktion auf einer öffentlichen Website ist eine nutzungsbasiert abgerechnete API mit Ihren Zahlungsdaten dahinter. Ein Kontaktassistent, ein Produktfinder oder ein Button „Seite zusammenfassen“ enden alle in einer Server-Route, die Text an ein Modell weiterreicht und pro Token bezahlt. Wenn diese Route jedem beliebig oft mit beliebiger Eingabe antwortet, ist nicht die Frage, ob jemand sie als kostenlose Modell-API nutzt, sondern nur wann.",{"type":140,"content":144},[145],"Dieser Artikel ist die Kosten- und Missbrauchsseite von LLM-Funktionen in Nuxt. Er übersetzt das OWASP-Risiko LLM10:2025 Unbounded Consumption in konkrete Schichten einer Nuxt- und Nitro-App: Sessions, Anfrage- und Token-Limits, Prompt-Größenlimits, Budgets pro Nutzer, Cloudflare Turnstile und die Spend Limits von OpenAI und Anthropic, mit Code zum Anpassen.",{"type":147,"level":148,"id":104,"text":88},"heading",2,{"type":140,"content":150},[151,152,156],"OWASP beschreibt ",{"tag":153,"href":38,"children":154},"a",[155],"Unbounded Consumption"," als LLM-Anwendung, die Nutzern „übermäßige und unkontrollierte Inferenzen“ erlaubt, was zu Denial of Service, wirtschaftlichen Verlusten, Modelldiebstahl und verschlechtertem Dienst führt. Denial of Wallet ist die kostenorientierte Variante: Ein Angreifer erzeugt übermäßig viele Operationen, um das nutzungsbasierte Abrechnungsmodell von Cloud-KI-Diensten auszunutzen, und verursacht untragbare Kosten. Dafür muss nichts ausfallen; der Schaden ist die Rechnung.",{"type":140,"content":158},[159],"LLM-Routen sind aus zwei Gründen exponierter als gewöhnliche API-Routen. Die Kosten pro Anfrage sind variabel und hoch: Eine Anfrage mit langem Gesprächsverlauf und langer Antwort kann um Größenordnungen mehr kosten als eine einzeilige Frage. Und die Ausgabe ist für den Angreifer direkt nützlich, ein offener Chat-Endpunkt lohnt sich also zum Skripten, auch wenn er mit Ihrem Produkt nichts zu tun hat.",{"type":147,"level":148,"id":106,"text":107},{"type":140,"content":162},[163],"Der OWASP-Eintrag nennt sieben Beispiele. Auf eine typische Nuxt-Funktion übertragen, bleiben wenige Muster mit jeweils einer Hauptmaßnahme:",{"type":165,"head":166,"rows":173},"table",[167,169,171],[168],"Muster",[170],"Wie es aussieht",[172],"Hauptmaßnahme",[174,181,188,195,202],[175,177,179],[176],"Trittbrett-Proxy (Denial of Wallet)",[178],"Skripte nutzen Ihre Route als kostenlose Modell-API; Prompts ohne Bezug zum Produkt",[180],"Session, fester System-Prompt, Token-Budget pro Nutzer",[182,184,186],[183],"Wiederholte Anfragen",[185],"Hohe Anfragerate von wenigen Konten oder IPs",[187],"Rate Limits an der Edge und pro Nutzer",[189,191,193],[190],"Variable-Length Input Flood, Continuous Input Overflow",[192],"Riesige Bodies, eingefügte Dokumente, wachsende Verläufe",[194],"Body-Limit, Schema-Validierung, Token-Obergrenze",[196,198,200],[197],"Ressourcenintensive Anfragen",[199],"Sehr lange Ausgaben, Tool-Schleifen, aufwendiges Reasoning",[201],"max_tokens auf dem Server, Schrittlimits, Timeouts",[203,205,207],[204],"Modellextraktion und funktionale Replikation",[206],"Systematische Anfragen, die Ausgaben als Trainingsdaten sammeln",[208],"Volumenlimits, keine Logprobs, Anomalie-Monitoring",{"type":140,"content":210},[211],"Ein sechstes Muster benennt OWASP nicht, aber jedes Team erlebt es: selbst verursachter Verbrauch. Eine Retry-Schleife ohne Obergrenze, ein Agent, der immer weiter Tools aufruft, oder ein Client-Bug, der bei jedem Tastendruck den ganzen Verlauf schickt, verbrennen Budget genau wie ein Angreifer. Dieselben Limits fangen das ab.",{"type":147,"level":148,"id":109,"text":110},{"type":140,"content":214},[215],"Keine einzelne Maßnahme reicht, weil jede etwas anderes misst. Ein Rate Limiter zählt Anfragen, ein Größenlimit Bytes, ein Budget Tokens und ein Spend Limit Geld. Ich ordne sie von der billigsten zur teuersten Prüfung, damit abgewiesener Verkehr so wenig wie möglich kostet:",{"type":217,"attrs":218,"inner":222,"caption":223},"diagram",{"viewBox":219,"role":220,"aria-labelledby":221},"0 0 720 170","img","dow1-t dow1-d","\u003Ctitle id=\"dow1-t\">Schichten vor einem LLM-Aufruf\u003C\u002Ftitle>\u003Cdesc id=\"dow1-d\">Eine Anfrage passiert Edge und Bot-Prüfung, dann die Session-Prüfung, dann das Anfragelimit, dann Eingabelimits für Größe und Tokens, dann das Token-Budget pro Nutzer, und erst dann die Modell-API, wo das Spend Limit des Anbieters die letzte Sicherung ist.\u003C\u002Fdesc>\u003Cdefs>\u003Cmarker id=\"dow1-ah\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\u003Cpath d=\"M0 0L10 5L0 10z\" class=\"d-head\" \u002F>\u003C\u002Fmarker>\u003C\u002Fdefs>\u003Crect x=\"20\" y=\"47\" width=\"95\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"68\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Edge, Bots\u003C\u002Ftext>\u003Ctext x=\"68\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">WAF, Turnstile\u003C\u002Ftext>\u003Cpath d=\"M115 85H135\" class=\"d-line\" marker-end=\"url(#dow1-ah)\" \u002F>\u003Crect x=\"137\" y=\"47\" width=\"95\" height=\"76\" rx=\"10\" class=\"d-sky\" \u002F>\u003Ctext x=\"184\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Session\u003C\u002Ftext>\u003Ctext x=\"184\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">401 ohne\u003C\u002Ftext>\u003Cpath d=\"M232 85H252\" class=\"d-line\" marker-end=\"url(#dow1-ah)\" \u002F>\u003Crect x=\"254\" y=\"47\" width=\"95\" height=\"76\" rx=\"10\" class=\"d-accent\" \u002F>\u003Ctext x=\"302\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Rate Limit\u003C\u002Ftext>\u003Ctext x=\"302\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">Anfragen\u003C\u002Ftext>\u003Cpath d=\"M349 85H369\" class=\"d-line\" marker-end=\"url(#dow1-ah)\" \u002F>\u003Crect x=\"371\" y=\"47\" width=\"95\" height=\"76\" rx=\"10\" class=\"d-mint\" \u002F>\u003Ctext x=\"418\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Eingabelimits\u003C\u002Ftext>\u003Ctext x=\"418\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">Bytes, Tokens\u003C\u002Ftext>\u003Cpath d=\"M466 85H486\" class=\"d-line\" marker-end=\"url(#dow1-ah)\" \u002F>\u003Crect x=\"488\" y=\"47\" width=\"95\" height=\"76\" rx=\"10\" class=\"d-gold\" \u002F>\u003Ctext x=\"536\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Budget\u003C\u002Ftext>\u003Ctext x=\"536\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">Tokens pro Tag\u003C\u002Ftext>\u003Cpath d=\"M583 85H603\" class=\"d-line\" marker-end=\"url(#dow1-ah)\" \u002F>\u003Crect x=\"605\" y=\"47\" width=\"95\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"652\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Modell-API\u003C\u002Ftext>\u003Ctext x=\"652\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">Spend Limit\u003C\u002Ftext>",[224],"Billige Prüfungen zuerst: Eine an der Edge abgewiesene Anfrage kostet nichts, eine vom Spend Limit gestoppte hat die Funktion schon lahmgelegt.",{"type":147,"level":148,"id":112,"text":113},{"type":140,"content":227},[228,229,232,233,237],"Jedes andere Limit braucht eine Identität, an der es hängt. Ohne Session ist der einzige Schlüssel die IP-Adresse, und die ist schwach. Mit ",{"tag":153,"href":47,"children":230},[231],"nuxt-auth-utils"," liefert ",{"tag":234,"children":235},"code",[236],"requireUserSession(event)"," die Session oder antwortet mit 401, die erste Zeile des Handlers entscheidet also, ob der Modellaufruf überhaupt stattfinden darf. Für eine Funktion, die vor dem Login funktionieren muss, etwa einen Produktfinder, nach einer Bot-Prüfung eine leichte anonyme Session ausgeben und ihr ein deutlich kleineres Budget geben als einem eingeloggten Kunden.",{"type":140,"content":239},[240,241,244,245,250],"Die zweite Regel: Nie einen generischen Proxy ausliefern. Wenn der Client Modell, System-Prompt, ",{"tag":234,"children":242},[243],"max_tokens"," oder Tools wählen kann, ist Ihr Endpunkt eine offene Modell-API mit Umweg. Der Client schickt eine Nachricht und eventuell eine Gesprächs-ID; alles andere entscheidet der Server. So bleibt auch der API-Schlüssel auf dem Server, wie in ",{"tag":246,"to":247,"children":248},"link","\u002Fblog\u002Fnuxt-llm-features-ai-sdk-streaming",[249],"LLM-Funktionen in Nuxt ausliefern"," beschrieben.",{"type":147,"level":148,"id":115,"text":116},{"type":140,"content":253},[254,255,258,259,262,263,267],"Das Modul ",{"tag":153,"href":41,"children":256},[257],"nuxt-security"," bringt einen Rate Limiter mit, der standardmäßig global aktiv ist: 150 Anfragen pro 300.000 ms pro IP, gespeichert in einem LRU-Cache, mit 429 Too Many Requests bei Überschreitung. Er lässt sich pro Route über ",{"tag":234,"children":260},[261],"routeRules"," einstellen, und als Speicher dient jeder ",{"tag":153,"href":264,"children":265},"https:\u002F\u002Funstorage.unjs.io\u002F",[266],"unstorage","-Treiber. Die eigene Dokumentation ist ehrlich zu den Grenzen: Der eingebaute Rate Limiter sei „nicht für komplexe Produktionsanwendungen geeignet“, empfohlen werden Lösungen auf Infrastrukturebene.",{"type":140,"content":269},[270,271,274,275,278,279,282,283,286],"Für eine LLM-Route heißt das drei Anpassungen. Einen gemeinsamen Speicher verwenden, denn ein LRU-Cache im Speicher gilt pro Instanz und wird bei jedem Deploy zurückgesetzt. Auf der Modellroute ein viel engeres Limit setzen als auf dem Rest der Site. Und die Client-IP korrekt ermitteln: ",{"tag":234,"children":272},[273],"getRequestIP"," aus h3 liest ",{"tag":234,"children":276},[277],"X-Forwarded-For"," nur nach ausdrücklicher Aktivierung, weil der erste Eintrag Client-Eingabe ist; hinter Cloudflare kann die Option ",{"tag":234,"children":280},[281],"ipHeader"," von nuxt-security stattdessen auf ",{"tag":234,"children":284},[285],"cf-connecting-ip"," zeigen.",{"type":234,"code":288},"\u002F\u002F nuxt.config.ts (illustrative)\nexport default defineNuxtConfig({\n  modules: ['nuxt-security', 'nuxt-auth-utils'],\n  security: {\n    rateLimiter: {\n      driver: { name: 'redis', options: { url: process.env.REDIS_URL } },\n      ipHeader: 'cf-connecting-ip', \u002F\u002F only if Cloudflare sits in front\n    },\n  },\n  routeRules: {\n    '\u002Fapi\u002Fchat': {\n      security: {\n        rateLimiter: { tokensPerInterval: 20, interval: 60_000 },\n        requestSizeLimiter: { maxRequestSizeInBytes: 32_000 },\n      },\n    },\n  },\n})",{"type":140,"content":290},[291,292,295,296,299],"Dieser Limiter arbeitet pro IP. Das Limit pro Nutzer gehört in den Handler, nach ",{"tag":234,"children":293},[294],"requireUserSession",", mit der Nutzer-ID als Schlüssel. Welcher Algorithmus dort läuft, ist weniger wichtig, als überhaupt konsequent einen zu nutzen; die Abwägungen, wie ",{"tag":153,"href":53,"children":297},[298],"Upstash sie dokumentiert",":",{"type":165,"head":301,"rows":308},[302,304,306],[303],"Algorithmus",[305],"Stärke",[307],"Schwäche",[309,316,323],[310,312,314],[311],"Festes Fenster",[313],"Sehr günstig bei Speicher und Rechenaufwand",[315],"Bursts an Fenstergrenzen rutschen durch; Ansturm bei jedem neuen Fenster",[317,319,321],[318],"Gleitendes Fenster",[320],"Behebt das Grenzproblem",[322],"Mehr Speicher und Rechenaufwand; eine Näherung",[324,326,328],[325],"Token Bucket",[327],"Glättet Bursts; erlaubt einen höheren Anfangs-Burst",[329],"Mehr Zustand, über den man nachdenken muss",{"type":140,"content":331},[332],"Anthropics eigene API nutzt einen Token Bucket, der kontinuierlich bis zum Limit aufgefüllt statt in festen Abständen zurückgesetzt wird, ein vernünftiges Vorbild für eine Chat-Funktion, in der Nutzer schubweise tippen.",{"type":147,"level":148,"id":118,"text":119},{"type":140,"content":335},[336,337,340,341,344],"Ein Anfragelimit sagt nichts darüber, was jede Anfrage kostet. Die Eingabe in drei Schritten deckeln. Zuerst Bytes: Der Request Size Limiter von nuxt-security antwortet über ",{"tag":234,"children":338},[339],"maxRequestSizeInBytes"," mit 413 Payload Too Large, Standard ist 2.000.000; eine Chatnachricht braucht wenige Kilobyte. Dann die Form: den Body mit ",{"tag":234,"children":342},[343],"readValidatedBody"," und einem Schema prüfen, das die Nachrichtenlänge begrenzt. Dann Tokens: vor dem Aufruf zählen.",{"type":140,"content":346},[347,348,351],"Das größte Loch ist meist der Gesprächsverlauf. Schickt der Client bei jedem Zug das ganze Gespräch, kann ein Angreifer einen erfundenen Verlauf beliebiger Länge senden. Den Verlauf auf dem Server halten, mit der Gesprächs-ID als Schlüssel, und auf eine feste Zahl von Zügen oder Tokens kürzen. Der ",{"tag":153,"href":62,"children":349},[350],"Token-Counting-Endpunkt"," von Anthropic ist kostenlos, hat ein eigenes, von der Nachrichtenerstellung getrenntes Rate Limit und liefert eine Schätzung der Eingabe-Tokens, das genügt für eine Obergrenze.",{"type":140,"content":353},[354,355,357,358,360],"Die Ausgabe deckelt ",{"tag":234,"children":356},[243],", gesetzt auf dem Server. Wichtig ist, was es tut und was nicht: Für Anthropics Rate Limits werden Ausgabe-Tokens gezählt, während sie entstehen, und ",{"tag":234,"children":359},[243]," fließt nicht in die Berechnung der Ausgabe-Tokens pro Minute ein. Es ist eine Kostenobergrenze pro Anfrage, keine Ratensteuerung, das Budget aus dem nächsten Abschnitt braucht es trotzdem.",{"type":234,"code":362},"\u002F\u002F server\u002Fapi\u002Fchat.post.ts (illustrative)\nimport { z } from 'zod'\n\nconst Body = z.object({\n  conversationId: z.string().uuid(),\n  message: z.string().min(1).max(4000),\n})\nconst MAX_INPUT = 12_000   \u002F\u002F tokens, after history trimming\nconst MAX_OUTPUT = 800     \u002F\u002F the server decides, never the client\n\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event)          \u002F\u002F 401 without a session\n  const body = await readValidatedBody(event, Body.parse)    \u002F\u002F 400 on bad or long input\n\n  const history = await loadHistory(user.id, body.conversationId, { maxTurns: 10 })\n  const messages = [...history, { role: 'user', content: body.message }]\n\n  const { input_tokens } = await anthropic.messages.countTokens({ model: MODEL, system: SYSTEM, messages })\n  if (input_tokens > MAX_INPUT) throw createError({ statusCode: 413, statusMessage: 'Conversation too long' })\n\n  const reserved = input_tokens + MAX_OUTPUT\n  const key = await reserveTokens(user.id, reserved)          \u002F\u002F null when the daily budget is spent\n  if (!key) throw createError({ statusCode: 429, statusMessage: 'Daily limit reached' })\n\n  let used = 0\n  try {\n    const res = await anthropic.messages.create({ model: MODEL, system: SYSTEM, messages, max_tokens: MAX_OUTPUT })\n    const u = res.usage\n    used = u.input_tokens + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + u.output_tokens\n    await saveTurn(user.id, body.conversationId, body.message, res.content)\n    return res.content\n  } finally {\n    await settleTokens(key, reserved, used)                   \u002F\u002F refund the unused part\n  }\n})",{"type":147,"level":148,"id":121,"text":122},{"type":140,"content":365},[366,367,369],"Ein Budget ist die Maßnahme, die Kosten tatsächlich begrenzt: Tokens pro Nutzer und Tag oder pro anonymer Session und Stunde. Mein Muster ist Reservieren, dann Abrechnen. Vor dem Aufruf den schlimmsten Fall reservieren, also gezählte Eingabe plus ",{"tag":234,"children":368},[243],". Würde die Reservierung das Budget überschreiten, mit 429 ablehnen. Nach dem Aufruf mit dem tatsächlichen Verbrauch aus der Antwort abrechnen und die Differenz gutschreiben. Parallele Anfragen desselben Nutzers können dann nicht überziehen, weil jede ihren Anteil schon beansprucht hat.",{"type":217,"attrs":371,"inner":373,"caption":374},{"viewBox":219,"role":220,"aria-labelledby":372},"dow2-t dow2-d","\u003Ctitle id=\"dow2-t\">Token-Budget reservieren und abrechnen\u003C\u002Ftitle>\u003Cdesc id=\"dow2-d\">Der Handler zählt die Eingabe-Tokens, reserviert Eingabe plus max_tokens per atomarem Inkrement gegen das Tagesbudget, lehnt bei Überschreitung ab, ruft das Modell auf und schreibt danach die Differenz zwischen Reservierung und tatsächlichem Verbrauch gut.\u003C\u002Fdesc>\u003Cdefs>\u003Cmarker id=\"dow2-ah\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\u003Cpath d=\"M0 0L10 5L0 10z\" class=\"d-head\" \u002F>\u003C\u002Fmarker>\u003C\u002Fdefs>\u003Crect x=\"20\" y=\"47\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"79\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Zählen\u003C\u002Ftext>\u003Ctext x=\"79\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">Eingabe-Tokens\u003C\u002Ftext>\u003Cpath d=\"M138 85H158\" class=\"d-line\" marker-end=\"url(#dow2-ah)\" \u002F>\u003Crect x=\"160\" y=\"47\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-accent\" \u002F>\u003Ctext x=\"220\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Reservieren\u003C\u002Ftext>\u003Ctext x=\"220\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">+ max_tokens\u003C\u002Ftext>\u003Cpath d=\"M279 85H299\" class=\"d-line\" marker-end=\"url(#dow2-ah)\" \u002F>\u003Crect x=\"301\" y=\"47\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-gold\" \u002F>\u003Ctext x=\"360\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Über Budget?\u003C\u002Ftext>\u003Ctext x=\"360\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">429, kein Aufruf\u003C\u002Ftext>\u003Cpath d=\"M419 85H439\" class=\"d-line\" marker-end=\"url(#dow2-ah)\" \u002F>\u003Crect x=\"441\" y=\"47\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-sky\" \u002F>\u003Ctext x=\"500\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Modell aufrufen\u003C\u002Ftext>\u003Ctext x=\"500\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">usage in Antwort\u003C\u002Ftext>\u003Cpath d=\"M560 85H580\" class=\"d-line\" marker-end=\"url(#dow2-ah)\" \u002F>\u003Crect x=\"582\" y=\"47\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-mint\" \u002F>\u003Ctext x=\"641\" y=\"81\" text-anchor=\"middle\" class=\"d-text\">Abrechnen\u003C\u002Ftext>\u003Ctext x=\"641\" y=\"103\" text-anchor=\"middle\" class=\"d-small\">Rest gutschreiben\u003C\u002Ftext>",[375],"Die Reservierung macht das Budget bei Parallelität sicher; das Abrechnen hält es genau.",{"type":140,"content":377},[378,379,382,383,386,387,390],"Der Zähler muss atomar sein. Ein ",{"tag":234,"children":380},[381],"getItem"," gefolgt von ",{"tag":234,"children":384},[385],"setItem"," über den Nitro-Speicher ist ein Read-Modify-Write, zwei parallele Anfragen können beide den alten Wert lesen. Redis ",{"tag":234,"children":388},[389],"INCRBY"," liefert die neue Summe in einem Schritt, Prüfung und Reservierung können sich also nicht überholen:",{"type":234,"code":392},"\u002F\u002F server\u002Futils\u002Fbudget.ts (illustrative, ioredis)\nconst DAILY_TOKENS = 200_000\n\nexport async function reserveTokens(userId: string, n: number) {\n  const key = `llm:budget:${userId}:${new Date().toISOString().slice(0, 10)}`\n  const total = await redis.incrby(key, n)\n  if (total === n) await redis.expire(key, 60 * 60 * 48)\n  if (total > DAILY_TOKENS) { await redis.decrby(key, n); return null }\n  return key\n}\n\nexport async function settleTokens(key: string, reserved: number, used: number) {\n  if (reserved > used) await redis.decrby(key, reserved - used)\n}",{"type":140,"content":394},[395,396,399,400,403,404,408],"Beim Abrechnen alle Eingabe-Tokens zählen. Mit Prompt Caching umfasst Anthropics ",{"tag":234,"children":397},[398],"input_tokens"," nur die Tokens nach dem letzten Cache-Breakpoint; die gesamte Eingabe ist ",{"tag":234,"children":401},[402],"cache_read_input_tokens + cache_creation_input_tokens + input_tokens",". Ob man gecachte Tokens zum niedrigeren Preis gewichtet, ist eine Produktentscheidung; ein schlichtes Token-Budget ist Nutzern leichter zu erklären. Zur Kostenseite von Caching und Routing siehe ",{"tag":246,"to":405,"children":406},"\u002Fblog\u002Fllm-cost-latency-prompt-caching-routing",[407],"Prompt Caching und Model Routing",".",{"type":147,"level":148,"id":124,"text":125},{"type":140,"content":411},[412,413,416,417,420],"Eine Bot-Prüfung verteuert Automatisierung; sie deckelt keine Kosten. Ich setze sie dort ein, wo eine Identität entsteht, etwa bevor eine anonyme Chat-Session ausgegeben wird, nicht bei jeder Nachricht. Bei ",{"tag":153,"href":56,"children":414},[415],"Cloudflare Turnstile"," ist die serverseitige Validierung über die Siteverify-API Pflicht, denn das Client-Widget allein schützt nichts: Tokens lassen sich fälschen, jedes Token ist 300 Sekunden gültig und kann nur einmal validiert werden, eine Wiederverwendung wird als ",{"tag":234,"children":418},[419],"timeout-or-duplicate"," abgelehnt.",{"type":234,"code":422},"\u002F\u002F server\u002Fapi\u002Fsession\u002Fanonymous.post.ts (illustrative)\nexport default defineEventHandler(async (event) => {\n  const { token } = await readBody\u003C{ token: string }>(event)\n  const r = await $fetch\u003C{ success: boolean }>('https:\u002F\u002Fchallenges.cloudflare.com\u002Fturnstile\u002Fv0\u002Fsiteverify', {\n    method: 'POST',\n    body: { secret: useRuntimeConfig().turnstileSecret, response: token, remoteip: getRequestIP(event) },\n  })\n  if (!r.success) throw createError({ statusCode: 403 })\n  await setUserSession(event, { user: { id: crypto.randomUUID(), tier: 'anonymous' } })\n  return { ok: true }\n})",{"type":424,"variant":425,"title":426,"body":427},"callout","warn","Eine Bot-Prüfung ist kein Budget",[428],[429],"Menschen lassen sich fürs Lösen von Challenges bezahlen, und eine gültige Session kann trotzdem den ganzen Tag teure Anfragen senden. Turnstile ist eine Bremsschwelle vor der Session-Erstellung, die Begrenzung übernimmt das Token-Budget pro Session.",{"type":147,"level":148,"id":127,"text":128},{"type":140,"content":432},[433],"Beide großen Anbieter erlauben eine monatliche Ausgabengrenze, und man sollte sie unter dem Betrag setzen, der weh täte. Aber man sollte das Verhalten kennen: Diese Limits stoppen sämtlichen Verkehr in ihrem Geltungsbereich, für alle Nutzer, bis der Monat zurückgesetzt wird oder man sie anhebt.",{"type":165,"head":435,"rows":442},[436,438,440],[437],"Aspekt",[439],"Claude API (Anthropic)",[441],"OpenAI API",[443,450,468,479],[444,446,448],[445],"Selbst gesetztes Limit",[447],"Spend Limit pro Organisation unter der Tier-Grenze; eigene Spend- und Rate Limits pro Workspace",[449],"Hard Spend Limit pro Organisation oder Projekt; Spend Alerts, die nur benachrichtigen",[451,453,459],[452],"Antwort beim Erreichen",[454,455,458],"HTTP 400 ",{"tag":234,"children":456},[457],"invalid_request_error",", „You have reached your specified API usage limits“",[460,461,464,465],"HTTP 429 mit ",{"tag":234,"children":462},[463],"organization_spend_limit_exceeded"," oder ",{"tag":234,"children":466},[467],"project_spend_limit_exceeded",[469,471,477],[470],"Eingebaute Obergrenze",[472,473,476],"Monatliche Tier-Grenze: Start 500, Build 1.000, Scale 200.000 USD; 429 ",{"tag":234,"children":474},[475],"enforced_spend_limit_reached"," ohne retry-after",[478],"Monatliche Nutzungslimits pro Usage Tier",[480,482,484],[481],"Einschränkung",[483],"Für den Default-Workspace lassen sich keine Workspace-Limits setzen",[485],"„Enforcement is not instantaneous“; die erfasste Ausgabe kann das Limit leicht überschreiten",{"type":140,"content":487},[488],"Zwei praktische Folgen. Jeder öffentlichen Funktion einen eigenen Anthropic-Workspace oder ein eigenes OpenAI-Projekt mit eigenem Schlüssel und Limit geben, damit ein außer Kontrolle geratener Chatbot nicht Ihre internen Werkzeuge stoppt. Und die Spend-Limit-Antworten ausdrücklich behandeln: Die SDKs wiederholen 429er automatisch, und laut Anthropic schlagen Wiederholungen fehl, bis der Zugang zurückkehrt. Auf einen freundlichen Zustand „vorübergehend nicht verfügbar“ abbilden und jemanden alarmieren.",{"type":147,"level":148,"id":130,"text":131},{"type":140,"content":491},[492],"Zu den Maßnahmen von OWASP gehören umfassendes Logging mit Anomalieerkennung und Graceful Degradation. In einer Nuxt-App heißt das einige Kennzahlen pro Funktion, protokolliert mit Nutzer-ID und Gesprächs-ID:",{"type":494,"items":495},"list",[496,502,507,512],[497,501],{"tag":498,"children":499},"strong",[500],"Tokens pro Nutzer und Tag",", mit Alarm, wenn der Spitzennutzer weit über dem Median liegt. Trittbrettfahrer tauchen hier zuerst auf.",[503,506],{"tag":498,"children":504},[505],"Ablehnungen pro Schicht",": 401, 413 und 429 pro Stunde. Ein plötzlicher Anstieg der Budget-429er heißt, dass jemand sondiert.",[508,511],{"tag":498,"children":509},[510],"Eingabe-Tokens pro Anfrage"," als Verteilung, nicht als Durchschnitt. Ein langer Ausläufer bedeutet zu lockere Verlaufs- oder Größenlimits.",[513,516],{"tag":498,"children":514},[515],"Tageskosten pro Funktion gegen eine Baseline",", mit einem Spend Alert beim Anbieter deutlich unter dem Hard Limit.",{"type":140,"content":518},[519,520,408],"Abstufen, bevor man abschaltet. Überschreiten die Ausgaben eine Schwelle, zuerst anonyme Nutzer abschalten, dann eingeloggte Nutzer auf ein kleineres Modell umstellen und die Funktion für zahlende Kunden am Leben halten. Das ist ein weit besserer Ausfallmodus, als wenn das Anbieterlimit alles auf einmal abschaltet. Zum Tracing der Aufrufe hinter diesen Zahlen siehe ",{"tag":246,"to":521,"children":522},"\u002Fblog\u002Fagent-observability-opentelemetry",[523],"Observability für LLM-Agenten",{"type":147,"level":148,"id":133,"text":134},{"type":494,"ordered":526,"items":527},true,[528,530,532,537,539,541,543,545,547],[529],"Der API-Schlüssel liegt nur in der Server-Runtime-Config; der Client sieht ihn nie.",[531],"Die Route verlangt eine Session; anonyme Sessions gibt es nur nach serverseitig geprüfter Bot-Kontrolle.",[533,534,536],"Modell, System-Prompt, Tools und ",{"tag":234,"children":535},[243]," sind auf dem Server festgelegt.",[538],"Anfragelimits pro IP an der Edge und pro Nutzer im Handler, in einem gemeinsamen Speicher.",[540],"Body-Limit, Schema-Validierung, serverseitiger Verlauf und Token-Obergrenze vor dem Aufruf.",[542],"Ein tägliches Token-Budget pro Nutzer mit atomarem Reservieren und Abrechnen.",[544],"Begrenzte Retries, Tool-Schritte und Timeouts, damit der eigene Code nicht in Schleifen gerät.",[546],"Ein eigener Workspace oder ein eigenes Projekt beim Anbieter pro Funktion, mit Spend Alert und Hard Limit.",[548],"Alarme bei Token-Ausreißern pro Nutzer, Ablehnungsspitzen und Tageskosten, dazu ein Abstufungsplan.",{"type":140,"content":550},[551,552,408],"Kostenmissbrauch ist die eine Hälfte des Risikos; die andere ist, was das Modell mit feindlicher Eingabe macht. Dazu siehe ",{"tag":246,"to":553,"children":554},"\u002Fblog\u002Fprompt-injection-lethal-trifecta-patterns",[555],"Abwehr von Prompt Injection",{"type":147,"level":148,"id":136,"text":137},{"type":494,"ordered":526,"items":558},[559,562,565,568,571,574,577,580,583,586,589],[560],{"tag":153,"href":38,"children":561},[37],[563],{"tag":153,"href":41,"children":564},[40],[566],{"tag":153,"href":44,"children":567},[43],[569],{"tag":153,"href":47,"children":570},[46],[572],{"tag":153,"href":50,"children":573},[49],[575],{"tag":153,"href":53,"children":576},[52],[578],{"tag":153,"href":56,"children":579},[55],[581],{"tag":153,"href":59,"children":582},[58],[584],{"tag":153,"href":62,"children":585},[61],[587],{"tag":153,"href":65,"children":588},[64],[590],{"tag":153,"href":68,"children":591},[67],[593,642,694,733],{"slug":594,"published":5,"minutes":595,"category":7,"tags":596,"keywords":601,"about":612,"sources":620,"cover":636,"og":637,"expertise":71,"locales":638,"lang":74,"title":639,"description":640,"coverAlt":641},"nuxt-i18n-multilingual-seo-hreflang",8,[9,597,598,599,600],"i18n","SEO","hreflang","Multilingual",[602,603,604,605,606,607,608,609,610,611],"nuxt i18n seo","nuxt hreflang","useLocaleHead","nuxt i18n canonical","hreflang x-default","prefix_except_default","multilingual seo nuxt","nuxt i18n sitemap hreflang","hreflang self-referencing","nuxt i18n baseUrl",[613,614,617],{"name":9,"url":34},{"name":615,"url":616},"Hreflang","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHreflang",{"name":618,"url":619},"Internationalization and localization","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FInternationalization_and_localization",[621,624,627,630,633],{"title":622,"url":623},"Nuxt i18n docs: SEO (useLocaleHead)","https:\u002F\u002Fi18n.nuxtjs.org\u002Fdocs\u002Fguide\u002Fseo",{"title":625,"url":626},"Nuxt i18n docs: Routing strategies","https:\u002F\u002Fi18n.nuxtjs.org\u002Fdocs\u002Fguide",{"title":628,"url":629},"Google Search Central: Tell Google about localized versions of your page","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fspecialty\u002Finternational\u002Flocalized-versions",{"title":631,"url":632},"Google Search Central: Managing multi-regional and multilingual sites","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fspecialty\u002Finternational\u002Fmanaging-multi-regional-sites",{"title":634,"url":635},"Google Search Central: How to specify a canonical URL","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fcrawling-indexing\u002Fconsolidate-duplicate-urls","\u002Fimages\u002Fblog\u002Fnuxt-i18n-multilingual-seo-hreflang\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fnuxt-i18n-multilingual-seo-hreflang\u002Fog.jpg",[73,74,75],"Mehrsprachiges SEO mit Nuxt i18n: URL-Strategie, hreflang, Canonicals und Sitemaps","So richtet man Nuxt i18n ein, damit Suchmaschinen jede Sprache indexieren: Routing-Strategie, useLocaleHead, hreflang-Regeln, Teilübersetzungen, Sitemaps.","Drei Sprachversionen einer Seite, Englisch, Deutsch und Ungarisch, mit hreflang-Pfeilen untereinander verknüpft und einem x-default-Fallback.",{"slug":643,"published":644,"minutes":645,"category":7,"tags":646,"keywords":650,"about":658,"sources":665,"cover":687,"og":688,"expertise":689,"locales":690,"lang":74,"title":691,"description":692,"coverAlt":693},"llms-txt-vs-markdown-content-negotiation","2026-10-07",9,[647,648,649,9],"llms.txt","Content negotiation","AI agents",[647,651,652,653,654,655,656,657],"markdown for agents","content negotiation","AI crawlers","generative engine optimization","Accept text\u002Fmarkdown header","llms-full.txt","agent-ready website",[659,662],{"name":660,"url":661},"Markdown","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMarkdown",{"name":663,"url":664},"Hypertext Transfer Protocol","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHypertext_Transfer_Protocol",[666,669,672,675,678,681,684],{"title":667,"url":668},"llmstxt.org: The \u002Fllms.txt file (proposal, version 2)","https:\u002F\u002Fllmstxt.org\u002F",{"title":670,"url":671},"llmstxt.org: Changes from v1 to v2","https:\u002F\u002Fllmstxt.org\u002Fchanges.html",{"title":673,"url":674},"Cloudflare changelog: Markdown for Agents (12 February 2026)","https:\u002F\u002Fdevelopers.cloudflare.com\u002Fchangelog\u002Fpost\u002F2026-02-12-markdown-for-agents\u002F",{"title":676,"url":677},"Cloudflare docs: Markdown for Agents","https:\u002F\u002Fdevelopers.cloudflare.com\u002Ffundamentals\u002Freference\u002Fmarkdown-for-agents\u002F",{"title":679,"url":680},"Ahrefs: 137K sites analyzed, 97% of llms.txt files never get read (June 2026)","https:\u002F\u002Fahrefs.com\u002Fblog\u002Fllmstxt-study\u002F",{"title":682,"url":683},"Checkly: The current state of content negotiation for AI agents (February 2026)","https:\u002F\u002Fwww.checklyhq.com\u002Fblog\u002Fstate-of-ai-agent-content-negotation\u002F",{"title":685,"url":686},"Suganthan: tracking Cloudflare Markdown for Agents on one site","https:\u002F\u002Fsuganthan.com\u002Fblog\u002Fcloudflare-markdown-for-agents\u002F","\u002Fimages\u002Fblog\u002Fllms-txt-vs-markdown-content-negotiation\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fllms-txt-vs-markdown-content-negotiation\u002Fog.jpg","ai-engineer",[73,74,75],"llms.txt versus Markdown Content Negotiation: was Agenten wirklich abrufen","llms.txt ist ein Vorschlag, Markdown Content Negotiation ein Header. Was KI-Agenten abrufen, was die Logs zeigen und wie man beides aus Nuxt und nginx ausliefert.","Vier gestapelte Schichten, die KI-Agenten bedienen: deklarierte WebMCP-Tools, eine Markdown-Kopie jeder Seite, eine per Content Negotiation ausgelieferte Markdown-Antwort und ein llms.txt-Index",{"slug":695,"published":696,"minutes":595,"category":7,"tags":697,"keywords":703,"about":711,"sources":720,"cover":727,"og":728,"expertise":71,"locales":729,"lang":74,"title":730,"description":731,"coverAlt":732},"multiplayer-sailing-game-threejs","2026-10-05",[698,699,700,701,702],"three.js","WebGL","GLSL","WebSockets","Game development",[704,705,706,707,708,709,710,700],"three.js game","three.js multiplayer","Gerstner waves","WebGL ocean shader","day night cycle three.js","WebSocket relay","browser game development",[712,715,717],{"name":713,"url":714},"Three.js","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FThree.js",{"name":699,"url":716},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebGL",{"name":718,"url":719},"WebSocket","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebSocket",[721,724],{"title":722,"url":723},"GPU Gems, Chapter 1: Effective Water Simulation from Physical Models","https:\u002F\u002Fdeveloper.nvidia.com\u002Fgpugems\u002Fgpugems\u002Fpart-i-natural-effects\u002Fchapter-1-effective-water-simulation-physical-models",{"title":725,"url":726},"RFC 6455: The WebSocket Protocol","https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc6455","\u002Fimages\u002Fblog\u002Fmultiplayer-sailing-game-threejs\u002Fdusk.webp","\u002Fimages\u002Fblog\u002Fmultiplayer-sailing-game-threejs\u002Fog.jpg",[73,74,75],"Ein 3D-Segelspiel mit Multiplayer in reinem three.js","Gerstner-Wellen, die GPU und CPU gleich berechnen, eine Himmelsfunktion für Himmel, Wasser und Nebel, ein Tag-Nacht-Wechsel in fünf Minuten und ein winziger WebSocket-Relay – die Technik hinter dem Spiel auf meiner Website.","Eine Dschunke mit Drachenbug und leuchtenden Laternen segelt in der Abenddämmerung über eine bewegte Bucht, bewaldete Hügel auf beiden Seiten.",{"slug":734,"published":735,"minutes":645,"category":7,"tags":736,"keywords":742,"about":751,"sources":760,"cover":773,"og":774,"expertise":71,"locales":775,"lang":74,"title":776,"description":777,"coverAlt":778},"home-assistant-ev-charging-energy-manager","2026-09-16",[737,738,739,740,741],"Home Assistant","EPEX Spot","Energy prices","Tesla","Energy management",[743,744,745,746,747,748,749,750],"EPEX Austria prices","spot tariff Austria EV charging","Home Assistant EV charging","cheapest hours charging","day-ahead price Austria","negative electricity prices Austria","Tesla charging spot prices","load management 3 phase",[752,755,757],{"name":753,"url":754},"EPEX SPOT","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEPEX_SPOT",{"name":737,"url":756},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHome_Assistant",{"name":758,"url":759},"Load management","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLoad_management",[761,764,767,770],{"title":762,"url":763},"aWATTar Austria: market data API","https:\u002F\u002Fwww.awattar.at\u002Fservices\u002Fapi",{"title":765,"url":766},"EPEX SPOT: market results","https:\u002F\u002Fwww.epexspot.com\u002Fen\u002Fmarket-results",{"title":768,"url":769},"Home Assistant developer docs: apps","https:\u002F\u002Fdevelopers.home-assistant.io\u002Fdocs\u002Fapps\u002F",{"title":771,"url":772},"Home Assistant: Tesla Fleet integration","https:\u002F\u002Fwww.home-assistant.io\u002Fintegrations\u002Ftesla_fleet\u002F","\u002Fimages\u002Fblog\u002Fhome-assistant-ev-charging-energy-manager\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fhome-assistant-ev-charging-energy-manager\u002Fog.jpg",[73,74,75],"Laden nach EPEX-Preisen in Österreich: was meine Home-Assistant-App spart","Ein Jahr stündlicher EPEX-Preise für Österreich, nachgespielt für einen Tesla und einen Boiler: Laden in den günstigsten Stunden kostet 6,5 statt 18,9 ct\u002FkWh, knapp 590 € im Jahr, ohne 20-A-Sicherung zu sprengen.","Titelbild: drei Balken vergleichen 18,9, 13,1 und 6,5 ct\u002FkWh für Laden um 18:00, über Nacht und in den günstigsten Stunden.",1792177149397]