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"
