{
  "openapi": "3.1.0",
  "info": {
    "title": "Asteon AI Gateway",
    "version": "1.0.0",
    "summary": "Én gateway, 20+ AI-leverandører, full prisinnsikt og forbruksrapportering.",
    "description": "Asteon Gateway proxy-er kall til OpenAI, Anthropic, Google, xAI, Mistral, DeepSeek, Replicate, ElevenLabs og 12+ andre leverandører — med automatisk wallet-debet, smart-routing og enhetlig feilformat.\n\nAlle kall krever en `agw_*`-nøkkel. Generer en under [/developers](https://asteon.ai/developers).",
    "contact": {
      "name": "Asteon Support",
      "email": "support@asteon.ai",
      "url": "https://asteon.ai/help"
    },
    "license": {
      "name": "Asteon Terms of Service",
      "url": "https://asteon.ai/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.asteon.ai/api",
      "description": "Produksjon"
    }
  ],
  "security": [
    { "bearerAuth": [] }
  ],
  "tags": [
    { "name": "Gateway", "description": "Builder-gateway-endepunkter (`/gateway/entitlements`, `/gateway/chat`, `/gateway/aliases`) og leverandør-proxy (`/external/{provider}/{path}`) — samme path og JSON-schema som leverandørens eget API." },
    { "name": "Smart-routing", "description": "Be om en modell-klasse og la Asteon velge billigste tilgjengelige leverandør." },
    { "name": "Spend", "description": "Forbruks- og kostnadsrapportering per prosjekt og leverandør." },
    { "name": "Providers", "description": "Offentlig pris-katalog over alle leverandører Asteon støtter." }
  ],
  "paths": {
    "/external/{provider}/{path}": {
      "post": {
        "tags": ["Gateway"],
        "summary": "Proxy-kall til en spesifikk leverandør",
        "description": "Sender request-en videre til den valgte leverandøren med samme JSON-schema som leverandørens eget API. Wallet-en debiteres når svaret er mottatt og kostnaden er beregnet.\n\nEksempel: `POST /external/openai/v1/chat/completions` oppfører seg identisk med OpenAI sitt eget endepunkt — bare bytt ut base-URL og nøkkel.\n\n**Auth:** Send enten en `agw_…`-nøkkel (`bearerAuth`) eller en OAuth 2.0 / OIDC access-token utstedt til `gameos-studio` eller annen registrert klient (`AsteonOAuth`). Begge aksepteres som `Authorization: Bearer …`.",
        "security": [
          { "bearerAuth": [] },
          { "AsteonOAuth": ["openid", "profile", "email", "tier"] }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "description": "Leverandør-ID (`openai`, `anthropic`, `google`, `xai`, `mistral`, `deepseek`, `groq`, `moonshot`, `openrouter`, `replicate`, `elevenlabs`, `suno`, `leonardo`, `meshy`, `tripo`, `blockade-labs`, `eden-ai`, `firecrawl`, `pixabay`, `huggingface`, `freesound`).",
            "schema": { "type": "string", "example": "openai" }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "Leverandør-spesifikk sti (f.eks. `v1/chat/completions`).",
            "schema": { "type": "string", "example": "v1/chat/completions" }
          },
          {
            "$ref": "#/components/parameters/ProjectIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChatCompletionRequest" },
              "examples": {
                "chat": {
                  "summary": "Enkel chat-completion",
                  "value": {
                    "model": "gpt-4o-mini",
                    "messages": [
                      { "role": "user", "content": "Hei, Asteon!" }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Leverandørens svar, uendret.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatCompletionResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/external/smart/v1/chat/completions": {
      "post": {
        "tags": ["Smart-routing"],
        "summary": "Smart-routet chat-completion",
        "description": "Be om en virtuell modell-klasse (f.eks. `best-cheap-llm`, `best-fast-llm`, `best-quality-llm`) og overlat valg av leverandør til Asteon. Returnerer responsen sammen med `X-Smart-Route-Provider` og `X-Smart-Route-Model` i headeren.\n\n**Auth:** Send enten en `agw_…`-nøkkel (`bearerAuth`) eller en OAuth 2.0 / OIDC access-token (`AsteonOAuth`). Begge aksepteres som `Authorization: Bearer …`.",
        "security": [
          { "bearerAuth": [] },
          { "AsteonOAuth": ["openid", "profile", "email", "tier"] }
        ],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectIdHeader" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SmartRouteRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Beste leverandørs svar, med routing-info i headere.",
            "headers": {
              "X-Smart-Route-Provider": {
                "description": "ID-en til leverandøren som ble valgt.",
                "schema": { "type": "string", "example": "openai" }
              },
              "X-Smart-Route-Model": {
                "description": "Eksakt modell-navn som ble brukt.",
                "schema": { "type": "string", "example": "gpt-4o-mini" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatCompletionResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/gateway/entitlements": {
      "get": {
        "tags": ["Gateway"],
        "summary": "Konsoliderte entitlements (Builder)",
        "description": "Returnerer alt Builder trenger for å rendre AI-panelet i én rundtur: tier, capabilities, wallet-saldo, månedlig allowance, gjenværende kvote per usage-type og modell/alias-allowlist. Cache-bart i 60 s (`Cache-Control: private, max-age=60`).\n\n**Auth:** Send enten en `agw_…`-nøkkel (`bearerAuth`) eller en OAuth 2.0 / OIDC access-token (`AsteonOAuth`).",
        "security": [
          { "bearerAuth": [] },
          { "AsteonOAuth": ["openid", "profile", "email", "tier"] }
        ],
        "responses": {
          "200": {
            "description": "Brukerens effektive tier, wallet, kvoter og modell-allowlist.",
            "headers": {
              "Cache-Control": {
                "description": "Alltid `private, max-age=60`.",
                "schema": { "type": "string", "example": "private, max-age=60" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GatewayEntitlements" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/gateway/chat": {
      "post": {
        "tags": ["Gateway"],
        "summary": "Unified chat-completion (konkret modell eller smart-route-alias)",
        "description": "Ett endepunkt med OpenAI-formet request-body. `model` kan være enten en konkret leverandør-modell-ID (f.eks. `gpt-4o`, `claude-3-5-sonnet`) eller et smart-route-alias (f.eks. `smart-pro`). Asteon resolver aliaset, klamrer resultatet til kallerens tier-band-allowlist, og videresender til upstream. Streaming støttes via `\"stream\": true` (SSE) — upstream-byte-strømmen sendes gjennom uendret, og for OpenAI-resolved kall injiseres `stream_options.include_usage = true` automatisk så siste SSE-chunk inneholder token-tellinger.\n\nNår requesten resolves til Anthropic, oversettes OpenAI-bodyen til Anthropic Messages-shape (system, user/assistant turns, `max_tokens`, `temperature`, `top_p`, `stop_sequences`, `stream`). Provider-spesifikke OpenAI-felter (tools, response_format, …) droppes — bruk `/external/openai/*` eller `/external/anthropic/*` for full feature-paritet.\n\nKvote: hvert kall trekker 1 enhet fra `ai_chat`-kvoten, reserverer en `usage_records`-rad *før* upstream kontaktes (så parallelle kall ikke kan overshoote), og overskriver med faktiske token-tellinger etter at svaret er mottatt. Mislykket upstream (non-2xx) refunderes ved at reservasjonen slettes.\n\n**Auth:** Send enten en `agw_…`-nøkkel (`bearerAuth`) eller en OAuth 2.0 / OIDC access-token (`AsteonOAuth`).",
        "security": [
          { "bearerAuth": [] },
          { "AsteonOAuth": ["openid", "profile", "email", "tier"] }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/GatewayChatRequest" },
              "examples": {
                "non-streaming": {
                  "summary": "Konkret modell, ikke-streaming",
                  "value": {
                    "model": "gpt-4o",
                    "messages": [
                      { "role": "system", "content": "You are a helpful game-design assistant." },
                      { "role": "user", "content": "Brainstorm 3 boss-fight mechanics for a coastal level." }
                    ],
                    "max_tokens": 400
                  }
                },
                "streaming-alias": {
                  "summary": "Smart-route alias, SSE-streaming",
                  "value": {
                    "model": "smart-pro",
                    "stream": true,
                    "messages": [{ "role": "user", "content": "Write a 200-word lore intro." }],
                    "max_tokens": 600
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Passthrough av resolved-leverandørens svar (OpenAI-form for OpenAI-modeller, Anthropic-form for Anthropic). For `stream: true` returneres `text/event-stream` med upstream-chunks, og siste chunk inneholder token-tellinger.",
            "headers": {
              "X-Asteon-Resolved-Provider": {
                "description": "Hvilken upstream som faktisk serverte requesten.",
                "schema": { "type": "string", "enum": ["openai", "anthropic"], "example": "anthropic" }
              },
              "X-Asteon-Resolved-Model": {
                "description": "Den konkrete leverandør-modell-IDen Asteon videresendte til. `usage_records`-raden bokføres alltid mot denne, aldri mot aliaset.",
                "schema": { "type": "string", "example": "claude-sonnet-4" }
              },
              "X-Asteon-Resolved-Via-Alias": {
                "description": "`1` hvis request-`model` var et alias som ble resolved, ellers `0`.",
                "schema": { "type": "string", "enum": ["0", "1"], "example": "1" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatCompletionResponse" }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE-stream av upstream-chunks når `stream: true`. Siste chunk inneholder token-tellinger (OpenAI: `usage`-felt; Anthropic: `message_delta` med `usage`)."
                }
              }
            }
          },
          "400": {
            "description": "Manglende eller ugyldig request-body. `code` er `MISSING_MODEL` eller `MISSING_MESSAGES`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": {
            "description": "Wallet-en har ikke nok saldo (`code: INSUFFICIENT_FUNDS`). `details` inneholder `balance`, `currency` og `monthlyAllowance`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "403": {
            "description": "Modellen (eller alle alias-targets) er utenfor kallerens tier-band-allowlist (`code: MODEL_NOT_ALLOWED`).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "`ai_chat`-kvoten er brukt opp for inneværende periode (`code: QUOTA_EXCEEDED`). Feilmeldingen inneholder oppgraderingstips.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "503": {
            "description": "Upstream-feil eller manglende leverandør-nøkkel (`code: OPENAI_KEY_MISSING`, `ANTHROPIC_KEY_MISSING` eller `UPSTREAM_UNREACHABLE`).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/gateway/aliases": {
      "get": {
        "tags": ["Gateway"],
        "summary": "Tier-skopet alias-katalog (auth valgfri)",
        "description": "Lar Builder rendre modell-velgeren uten først å hente `/gateway/entitlements`. Auth er **valgfri**: med en gateway-token filtreres svaret til kallerens tier; uten token defaulter svaret til `free` så velgeren også kan rendres i marketing-/utlogget-kontekst. Et ugyldig bearer-token behandles stille som anonym (returnerer 200, ikke 401), så Builder-velgeren degraderer grasiøst. Cacheable i 5 min (`Cache-Control: public, max-age=300`).",
        "security": [
          {},
          { "bearerAuth": [] },
          { "AsteonOAuth": ["openid", "profile", "email", "tier"] }
        ],
        "responses": {
          "200": {
            "description": "Tier-skopet liste over smart-route-aliaser, tillatte modeller og smart-route-prioritet.",
            "headers": {
              "Cache-Control": {
                "description": "Alltid `public, max-age=300`.",
                "schema": { "type": "string", "example": "public, max-age=300" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GatewayAliasesResponse" }
              }
            }
          }
        }
      }
    },
    "/portal/spend/summary": {
      "get": {
        "tags": ["Spend"],
        "summary": "Forbrukssammendrag",
        "description": "Returnerer den innloggede brukerens gateway-forbruk over et tidsrom, gruppert per leverandør og per prosjekt-tag. Krever session-cookie (ikke gateway-nøkkel).",
        "security": [{ "sessionAuth": [] }],
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "description": "Tidsvindu, f.eks. `7d`, `30d`, `90d`. Maks 365d.",
            "required": false,
            "schema": { "type": "string", "default": "30d", "example": "30d" }
          }
        ],
        "responses": {
          "200": {
            "description": "Aggregerte totaler + per-provider og per-project bøtter.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SpendSummary" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/portal/spend/projects/{id}": {
      "get": {
        "tags": ["Spend"],
        "summary": "Forbruk for ett prosjekt",
        "description": "Per-leverandør-nedbryting av spend for et spesifikt prosjekt-uid.",
        "security": [{ "sessionAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Prosjekt-uid (f.eks. `prj_abc123`).",
            "schema": { "type": "string", "example": "prj_abc123" }
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "default": "30d", "example": "30d" }
          }
        ],
        "responses": {
          "200": {
            "description": "Per-leverandør spend for prosjektet.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProjectSpend" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/portal/providers/public": {
      "get": {
        "tags": ["Providers"],
        "summary": "Offentlig pris-katalog",
        "description": "Returnerer alle leverandører Asteon støtter med modeller, enheter, retail-priser (med 2x-margin) og rå leverandør-priser. Krever ingen autentisering.",
        "security": [],
        "responses": {
          "200": {
            "description": "Liste over alle leverandører + pris-skjema.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProvidersPublic" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "agw_*",
        "description": "Send `Authorization: Bearer agw_din_nokkel`. Generer nøkler under [/developers](https://asteon.ai/developers)."
      },
      "sessionAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "asteon_session",
        "description": "Session-cookie satt etter innlogging. Brukes for portal-API-er."
      },
      "AsteonOAuth": {
        "type": "openIdConnect",
        "openIdConnectUrl": "https://api.asteon.ai/.well-known/openid-configuration",
        "description": "Asteon OAuth 2.0 + OIDC (PKCE, code grant). Anbefalt for interaktive klienter — send brukeren gjennom `https://api.asteon.ai/api/oauth/authorize` med `client_id=gameos-studio`, og bruk den utstedte 15-min RS256-bearer-tokenen som `Authorization: Bearer <access_token>`. Manuelle `agw_…`-nøkler virker fortsatt parallelt for skript / server-til-server."
      }
    },
    "parameters": {
      "ProjectIdHeader": {
        "name": "X-Asteon-Project-Id",
        "in": "header",
        "required": false,
        "description": "Valgfri prosjekt-tag. Hvis satt blir kostnaden bokført mot prosjektet i forbruksrapporten. Du må ha rolle på prosjektet — ellers ignoreres taggen.",
        "schema": { "type": "string", "example": "prj_abc123" }
      }
    },
    "schemas": {
      "ChatCompletionRequest": {
        "type": "object",
        "required": ["model", "messages"],
        "properties": {
          "model": { "type": "string", "example": "gpt-4o-mini" },
          "messages": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["role", "content"],
              "properties": {
                "role": { "type": "string", "enum": ["system", "user", "assistant", "tool"] },
                "content": { "type": "string" }
              }
            }
          },
          "stream": { "type": "boolean", "default": false, "description": "Hvis `true`, returneres svaret som SSE." },
          "temperature": { "type": "number", "minimum": 0, "maximum": 2 },
          "max_tokens": { "type": "integer", "minimum": 1 }
        }
      },
      "ChatCompletionResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "chatcmpl-abc123" },
          "object": { "type": "string", "example": "chat.completion" },
          "model": { "type": "string", "example": "gpt-4o-mini" },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "message": {
                  "type": "object",
                  "properties": {
                    "role": { "type": "string", "example": "assistant" },
                    "content": { "type": "string", "example": "Hei! Hva kan jeg hjelpe deg med?" }
                  }
                },
                "finish_reason": { "type": "string", "example": "stop" }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": { "type": "integer" },
              "completion_tokens": { "type": "integer" },
              "total_tokens": { "type": "integer" }
            }
          }
        }
      },
      "SmartRouteRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/ChatCompletionRequest" },
          {
            "type": "object",
            "properties": {
              "model": {
                "type": "string",
                "description": "Virtuell modell-klasse.",
                "enum": ["best-cheap-llm", "best-fast-llm", "best-quality-llm"],
                "example": "best-cheap-llm"
              }
            }
          }
        ]
      },
      "SpendSummary": {
        "type": "object",
        "properties": {
          "range": {
            "type": "object",
            "properties": {
              "days": { "type": "integer", "example": 30 },
              "since": { "type": "string", "format": "date-time" }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "calls": { "type": "integer", "example": 1284 },
              "failures": { "type": "integer", "example": 12 },
              "rawCost": { "type": "number", "example": 4.21 },
              "customerCost": { "type": "number", "example": 8.42 }
            }
          },
          "byProvider": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "providerId": { "type": "string", "example": "openai" },
                "calls": { "type": "integer" },
                "failures": { "type": "integer" },
                "rawCost": { "type": "number" },
                "customerCost": { "type": "number" }
              }
            }
          },
          "byProject": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "projectId": { "type": ["string", "null"], "example": "prj_abc123" },
                "projectName": { "type": "string", "example": "Mitt eventyrspill" },
                "calls": { "type": "integer" },
                "failures": { "type": "integer" },
                "rawCost": { "type": "number" },
                "customerCost": { "type": "number" }
              }
            }
          }
        }
      },
      "ProjectSpend": {
        "type": "object",
        "properties": {
          "project": {
            "type": "object",
            "properties": {
              "uid": { "type": "string" },
              "name": { "type": "string" }
            }
          },
          "range": {
            "type": "object",
            "properties": {
              "days": { "type": "integer" },
              "since": { "type": "string", "format": "date-time" }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "calls": { "type": "integer" },
              "rawCost": { "type": "number" },
              "customerCost": { "type": "number" }
            }
          },
          "byProvider": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "providerId": { "type": "string" },
                "calls": { "type": "integer" },
                "rawCost": { "type": "number" },
                "customerCost": { "type": "number" }
              }
            }
          }
        }
      },
      "ProvidersPublic": {
        "type": "object",
        "properties": {
          "providers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string", "example": "openai" },
                "description": { "type": "string" },
                "category": { "type": "string", "enum": ["LLM", "Image", "Video", "Audio", "Search", "3D", "Other"] },
                "baseUrl": { "type": "string", "format": "uri" },
                "hasKey": { "type": "boolean" },
                "effectiveMargin": { "type": "number", "example": 2 },
                "pricing": {
                  "type": "object",
                  "properties": {
                    "kind": { "type": "string", "enum": ["llm", "units"] },
                    "currency": { "type": "string", "example": "USD" },
                    "models": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "model": { "type": "string" },
                          "inputPer1M": { "type": "number" },
                          "outputPer1M": { "type": "number" },
                          "cachedInputPer1M": { "type": "number" },
                          "inputPer1MRetail": { "type": "number" },
                          "outputPer1MRetail": { "type": "number" }
                        }
                      }
                    },
                    "units": { "type": "array" },
                    "lastUpdated": { "type": "string", "format": "date" },
                    "sourceUrl": { "type": "string", "format": "uri" }
                  }
                }
              }
            }
          }
        }
      },
      "GatewayEntitlements": {
        "type": "object",
        "required": ["userId", "tier", "tierDisplayName", "capabilities", "modelBands", "wallet", "quotas", "models", "cacheTtlSeconds"],
        "properties": {
          "userId": { "type": "string", "example": "usr_01HX..." },
          "tier": { "type": "string", "description": "Effektiv tier (org-tier vinner hvis brukeren er medlem av en aktiv org).", "example": "indie_pro" },
          "tierDisplayName": { "type": "string", "example": "Indie Pro" },
          "capabilities": { "type": "array", "items": { "type": "string" }, "example": ["multiplayer", "remote_assets"] },
          "modelBands": { "type": "array", "items": { "type": "string", "enum": ["lite", "standard", "pro", "premium"] }, "example": ["lite", "standard", "pro"] },
          "wallet": {
            "type": "object",
            "properties": {
              "balance": { "type": "number", "example": 12.40 },
              "currency": { "type": "string", "example": "USD" },
              "unlimited": { "type": "boolean" },
              "monthlyAllowance": { "type": "number", "example": 25.00 },
              "monthlyAllowanceUsed": { "type": "number", "example": 12.60 },
              "monthlyAllowanceRemaining": { "type": "number", "description": "monthlyAllowance - sum(spend denne kalendermåneden). Vises som «AI-credits igjen denne måneden».", "example": 12.40 },
              "monthlyAllowanceResetsAt": { "type": "string", "format": "date-time", "example": "2026-05-01T00:00:00.000Z" }
            }
          },
          "quotas": {
            "type": "object",
            "description": "Per-usage-type kvote-status. `limit === -1` og `unlimited === true` betyr «ingen cap på dette planet». Nøklene er `ai_chat`, `image_generation`, `model_3d`, `tts`, `skybox`, `npc_generation`, `sound_effect`.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "used": { "type": "integer" },
                "limit": { "type": "integer" },
                "remaining": { "type": "integer" },
                "unlimited": { "type": "boolean" }
              }
            },
            "example": {
              "ai_chat": { "used": 142, "limit": 1000, "remaining": 858, "unlimited": false },
              "image_generation": { "used": 18, "limit": 200, "remaining": 182, "unlimited": false },
              "model_3d": { "used": 2, "limit": 25, "remaining": 23, "unlimited": false },
              "tts": { "used": 0, "limit": -1, "remaining": -1, "unlimited": true },
              "skybox": { "used": 0, "limit": 10, "remaining": 10, "unlimited": false },
              "npc_generation": { "used": 3, "limit": 50, "remaining": 47, "unlimited": false },
              "sound_effect": { "used": 0, "limit": 100, "remaining": 100, "unlimited": false }
            }
          },
          "models": {
            "type": "object",
            "properties": {
              "allowed": {
                "type": "array",
                "description": "Konkrete modell-IDer kalleren kan sende direkte som `model` i `/gateway/chat`.",
                "items": {
                  "type": "object",
                  "properties": {
                    "provider": { "type": "string", "example": "openai" },
                    "model": { "type": "string", "example": "gpt-4o" },
                    "band": { "type": "string", "enum": ["lite", "standard", "pro", "premium"] }
                  }
                }
              },
              "aliases": {
                "type": "array",
                "description": "Smart-route alias-navn kalleren kan sende som `model`.",
                "items": { "type": "string" },
                "example": ["claude-sonnet", "smart-pro"]
              }
            }
          },
          "cacheTtlSeconds": { "type": "integer", "description": "Speiler `Cache-Control`-headeren så SDK-er kan cache uniformt.", "example": 60 }
        }
      },
      "GatewayChatRequest": {
        "type": "object",
        "required": ["model", "messages"],
        "description": "OpenAI-formet chat-body. `model` kan være enten en konkret leverandør-modell-ID (f.eks. `gpt-4o`, `claude-3-5-sonnet`) eller et smart-route-alias (f.eks. `smart-pro`).",
        "properties": {
          "model": {
            "type": "string",
            "description": "Konkret modell-ID *eller* smart-route-alias. Aliaser resolves serveren-side til en konkret modell innenfor kallerens tier-band.",
            "example": "smart-pro"
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": ["role", "content"],
              "properties": {
                "role": { "type": "string", "enum": ["system", "user", "assistant", "tool"] },
                "content": { "type": "string" }
              }
            }
          },
          "stream": { "type": "boolean", "default": false, "description": "Hvis `true`, returneres svaret som SSE (`text/event-stream`)." },
          "temperature": { "type": "number", "minimum": 0, "maximum": 2 },
          "top_p": { "type": "number", "minimum": 0, "maximum": 1 },
          "max_tokens": { "type": "integer", "minimum": 1 },
          "stop": { "type": "array", "items": { "type": "string" }, "description": "Stop-sekvenser. For Anthropic-resolved kall mappes dette automatisk til `stop_sequences`." }
        }
      },
      "GatewayAliasesResponse": {
        "type": "object",
        "required": ["tier", "modelBands", "aliases", "allowedModels", "smartRouteProviderOrder"],
        "properties": {
          "tier": { "type": "string", "example": "indie_pro" },
          "modelBands": { "type": "array", "items": { "type": "string", "enum": ["lite", "standard", "pro", "premium"] }, "example": ["lite", "standard", "pro"] },
          "aliases": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "alias": { "type": "string", "example": "smart-pro" },
                "targets": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "provider": { "type": "string", "example": "openai" },
                      "model": { "type": "string", "example": "gpt-4o" },
                      "allowed": { "type": "boolean" }
                    }
                  }
                },
                "allowed": { "type": "boolean", "description": "`false` => UI bør gråne ut raden — kallerens tier ekskluderer alle targets." }
              }
            }
          },
          "allowedModels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider": { "type": "string" },
                "model": { "type": "string" },
                "band": { "type": "string", "enum": ["lite", "standard", "pro", "premium"] }
              }
            }
          },
          "smartRouteProviderOrder": {
            "type": "array",
            "items": { "type": "string" },
            "example": ["openai", "anthropic", "groq"]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "Authentication required" },
          "code": { "type": "string", "example": "UNAUTHORIZED" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Manglende eller ugyldig nøkkel.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "PaymentRequired": {
        "description": "Lommeboken har ikke nok saldo.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Ressurs eller leverandør finnes ikke.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Leverandøren har returnert 429.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ServiceUnavailable": {
        "description": "Alle smart-route-kandidater feilet eller leverandør er nede.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
