openapi: 3.1.0
info:
  title: Vedøy Developer API
  version: 2026-10-09
  description: >
    Developer control-plane and project APIs. Chat and embedding routes use the
    configured server-side model provider and fail closed when it is unavailable.
servers:
  - url: https://vedoy-dev-portal.vercel.app/api
    description: Production API
paths:
  /v1/modules:
    get:
      tags: [App Modules]
      summary: Discover available API modules and game choices
      description: Public read-only catalog; availability and subscription requirements are descriptive.
      security: []
      responses:
        '200':
          description: Module definitions and selectable legacy game IDs
  /v1/rewards/games:
    get:
      tags: [Rewards]
      summary: Discover games undergoing Rewards migration
      description: Metadata only. Engines are not hosted by this endpoint; no Vedoy Coins awarded.
      security: []
      responses:
        '200':
          description: Current game catalog with legacy status
  /v1/projects/{projectId}/modules:
    parameters:
      - in: path
        name: projectId
        required: true
        schema: {type: string, format: uuid}
    get:
      tags: [App Modules]
      summary: Read enabled modules, selected games, and subscription entitlement
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Modules and games owned by signed-in project owner
        '404':
          description: Project not owned by requester
  /v1/projects/{projectId}/modules/{moduleKey}:
    parameters:
      - in: path
        name: projectId
        required: true
        schema: {type: string, format: uuid}
      - in: path
        name: moduleKey
        required: true
        schema: {type: string, enum: [games, highscores]}
    put:
      tags: [App Modules]
      summary: Activate a module in the owned project
      security:
        - supabaseUser: []
      responses:
        '200': {description: Module activated}
        '402': {description: Active subscription required for premium modules}
        '409': {description: Missing module dependency or unavailable module}
    delete:
      tags: [App Modules]
      summary: Remove a module, respecting its dependencies
      security:
        - supabaseUser: []
      responses:
        '200': {description: Module removed}
  /v1/projects/{projectId}/games/{gameKey}:
    parameters:
      - in: path
        name: projectId
        required: true
        schema: {type: string, format: uuid}
      - in: path
        name: gameKey
        required: true
        schema: {type: string, enum: [chess, ttt, four, hangman, snake, sudoku, '2048']}
    put:
      tags: [Rewards]
      summary: Select a game for an app that has Games enabled
      security:
        - supabaseUser: []
      responses:
        '200': {description: Game selected}
    delete:
      tags: [Rewards]
      summary: Remove selected game if no score history exists
      security:
        - supabaseUser: []
      responses:
        '200': {description: Game deselected}
        '409': {description: Game has scores; score history preserved}
  /v1/rewards/highscores:
    get:
      tags: [Rewards]
      summary: Get top scores for a selected project game
      description: Premium beta. Developer-submitted scores are not verified; no coins are awarded.
      security:
        - vedoyApiKey: []
      parameters:
        - in: query
          name: game
          required: true
          schema: {type: string, example: snake}
      responses:
        '200': {description: Up to 50 scores for the API key's project}
        '402': {description: Active project subscription required}
        '403': {description: Module or game not enabled for project}
    post:
      tags: [Rewards]
      summary: Submit an unverified beta highscore
      description: Requires Games and Highscores enabled on the API-key project and an active project subscription. No coins issued.
      security:
        - vedoyApiKey: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [game, playerAlias, score]
              properties:
                game: {type: string, example: snake}
                playerAlias: {type: string, minLength: 1, maxLength: 36}
                score: {type: integer, minimum: 0, maximum: 100000000}
      responses:
        '201': {description: Accepted unverified score}
        '402': {description: Active project subscription required}
        '403': {description: Module or game not enabled}
        '409': {description: Duplicate Idempotency-Key}
  /v1/deliver/providers:
    get:
      tags: [Delivery]
      summary: List delivery and carrier integrations and their readiness
      description: Descriptive catalogue only; providers without configured adapters cannot be used for live orders.
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Provider capabilities, runtime readiness and booking status
        '401':
          description: Invalid or missing project API key
  /v1/deliver/quotes:
    post:
      tags: [Delivery]
      summary: Request an estimated delivery price
      description: >
        Wolt Drive venueful shipment promise only. Requires merchant credentials
        and a configured venue. Does not create a delivery, charge Vedoy Credits,
        or guarantee availability. Other carriers return 503 pending integration.
      security:
        - vedoyApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [dropoff]
              properties:
                provider:
                  type: string
                  enum: [wolt-drive, bring, helthjem, postnord, porterbuddy, nshift, foodora, uber-direct]
                  default: wolt-drive
                preparationMinutes:
                  type: integer
                  minimum: 0
                  maximum: 60
                scheduledDropoffTime:
                  type: string
                  format: date-time
                dropoff:
                  type: object
                  required: [street]
                  properties:
                    street:
                      type: string
                      description: Street name and house number
                    city:
                      type: string
                    postCode:
                      type: string
                    latitude:
                      type: number
                    longitude:
                      type: number
      responses:
        '200':
          description: Wolt shipment promise estimate in provider-native minor currency units (not a checkout price)
        '422':
          description: Invalid provider, address or quote options
        '503':
          description: Provider has not been configured or enabled
        '502':
          description: Carrier unavailable or incompatible response
  /v1/deliver/orders:
    post:
      tags: [Delivery]
      summary: Reserve and book a delivery (disabled)
      description: >
        Intentionally returns 503 without contacting a carrier until idempotent
        booking, quote storage, billing and reconciliation have been reviewed.
      security:
        - vedoyApiKey: []
      responses:
        '503':
          description: Booking capability is not enabled
  /v1/health:
    get:
      summary: Service health and provider readiness
      security: []
      responses:
        '200':
          description: API health, database connectivity, and capability readiness
  /v1/projects:
    get:
      summary: List projects owned by the signed-in user
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Project list
    post:
      summary: Create a developer project
      security:
        - supabaseUser: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 120
                slug:
                  type: string
      responses:
        '201':
          description: Project created with a welcome credit balance
        '422':
          description: Invalid project name
  /v1/projects/{projectId}/api-keys:
    parameters:
      - in: path
        name: projectId
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: List API keys for an owned project
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Key metadata; secret values are never returned
    post:
      summary: Create a project API key
      description: The complete secret is returned once. Store it only in a trusted server secret store.
      security:
        - supabaseUser: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 80
      responses:
        '201':
          description: Key metadata and one-time secret
        '404':
          description: Project not found or not owned by the user
  /v1/projects/{projectId}/api-keys/{keyId}:
    parameters:
      - in: path
        name: projectId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: keyId
        required: true
        schema:
          type: string
          format: uuid
    delete:
      summary: Revoke a project API key
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Key revoked
        '404':
          description: Key not found or not owned by the user
  /v1/models:
    get:
      summary: List model route descriptors
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Model catalogue with live provider readiness
        '401':
          description: Missing or invalid API key
  /v1/chat/completions:
    post:
      summary: Create a model-backed chat completion
      description: Uses the server-side OpenAI provider configured by Vedøy. Customer API keys never expose provider credentials.
      security:
        - vedoyApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [messages]
              properties:
                model:
                  type: string
                  default: vedoy/chat
                messages:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    properties:
                      role:
                        type: string
                      content:
                        type: string
      responses:
        '200':
          description: Model-backed chat completion
        '422':
          description: Messages array is empty
  /v1/embeddings:
    post:
      summary: Create semantic embeddings
      description: Returns semantic vectors from the configured server-side OpenAI embedding model.
      security:
        - vedoyApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [input]
              properties:
                model:
                  type: string
                  default: vedoy/embeddings
                input:
                  type: string
      responses:
        '200':
          description: Semantic vector with 1536 dimensions
        '422':
          description: Input text is required
  /v1/numbers/available:
    get:
      summary: Search live Twilio number inventory and create short-lived Vedøy quotes
      security:
        - vedoyApiKey: []
      parameters:
        - in: query
          name: country
          schema:
            type: string
            pattern: '^[A-Z]{2}$'
            default: NO
        - in: query
          name: type
          schema:
            type: string
            enum: [local, toll_free, mobile, national, shared_cost]
            default: local
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 20
            default: 10
      responses:
        '200':
          description: Current customer prices and quote IDs
        '401':
          description: Missing or invalid API key
        '503':
          description: Twilio or required database schema is not configured
  /v1/numbers:
    get:
      summary: List numbers owned by the API-key project
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Project number list
  /v1/numbers/purchase:
    post:
      summary: Purchase a quoted number through Twilio
      security:
        - vedoyApiKey: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [quoteId]
              properties:
                quoteId:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Number order accepted
        '402':
          description: Insufficient Vedøy Credits
        '409':
          description: Existing provider operation is pending or requires reconciliation
        '410':
          description: Quote expired
  /v1/messages:
    post:
      summary: Send an SMS from a project-owned number through Twilio
      security:
        - vedoyApiKey: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, text]
              properties:
                from:
                  type: string
                  pattern: '^\+[1-9]\d{6,14}$'
                to:
                  type: string
                  pattern: '^\+[1-9]\d{6,14}$'
                text:
                  type: string
                  minLength: 1
                  maxLength: 1600
      responses:
        '202':
          description: Message accepted
        '402':
          description: Insufficient Vedøy Credits
        '403':
          description: The sending number is not owned by the project
        '409':
          description: Existing provider operation is pending or requires reconciliation
  /v1/webhooks/endpoints:
    get:
      summary: List project webhook endpoints
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Endpoint list without signing secrets
    post:
      summary: Create a signed HTTPS webhook endpoint
      security:
        - vedoyApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                  pattern: '^https://'
                events:
                  type: array
                  minItems: 1
                  maxItems: 50
                  items:
                    type: string
                description:
                  type: string
                  maxLength: 240
      responses:
        '201':
          description: Endpoint created; signingSecret is returned once
        '422':
          description: Invalid URL or event list
        '503':
          description: Webhook signing is not configured
  /v1/webhooks/deliveries:
    get:
      summary: List recent webhook delivery attempts
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Up to 100 recent delivery attempts
  /v1/webhooks/deliveries/{deliveryId}/retry:
    post:
      summary: Retry one webhook delivery
      security:
        - vedoyApiKey: []
      parameters:
        - in: path
          name: deliveryId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '202':
          description: Retry attempted and recorded
        '404':
          description: Delivery not found
        '409':
          description: Endpoint is disabled
  /v1/agents:
    get:
      summary: List saved agents in the project
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Agent configuration list
    post:
      summary: Create an agent configuration with a budget guard
      security:
        - vedoyApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 120
                instructions:
                  type: string
                  maxLength: 16000
                model:
                  type: string
                  default: vedoy/router
                monthlyBudgetMicrounits:
                  type: integer
                  minimum: 0
                status:
                  type: string
                  enum: [draft, active]
                  default: draft
                metadata:
                  type: object
      responses:
        '201':
          description: Agent saved
        '422':
          description: Invalid agent name or budget
  /v1/agents/{agentId}/runs:
    parameters:
      - in: path
        name: agentId
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: List recent runs for an agent
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Agent run list
    post:
      summary: Request an agent run
      description: The run is recorded as blocked until a server-side agent runner is configured.
      security:
        - vedoyApiKey: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '503':
          description: Agent runner is not configured; blocked run is recorded
  /v1/usage:
    get:
      summary: List recent usage for the signed-in user's projects
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Up to 100 usage events
  /v1/credits/balance:
    get:
      summary: Get project credit balances owned by the signed-in user
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Project credit balances
  /v1/credits/transactions:
    get:
      summary: List credit transactions for the signed-in user's projects
      security:
        - supabaseUser: []
      responses:
        '200':
          description: Up to 100 credit ledger entries
  /v1/rate-limits:
    get:
      summary: Get current rate limit status for the API-key project
      security:
        - vedoyApiKey: []
      responses:
        '200':
          description: Per-minute limit and remaining requests
  /v1/billing/products:
    get:
      summary: List fixed Vedøy Credit packs and checkout availability
      security: []
      responses:
        '200': { description: Credit product catalog }
  /v1/billing/checkout:
    post:
      summary: Create a Stripe-hosted checkout for a project credit pack
      security: [{ supabaseUser: [] }]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [projectId, product]
              properties:
                projectId: { type: string, format: uuid }
                product: { type: string, enum: [starter, scale] }
      responses:
        '201': { description: Stripe-hosted checkout URL created }
        '503': { description: Stripe checkout is not configured }
  /v1/billing/overview:
    get:
      summary: List purchases and ledger entries for the signed-in owner
      security: [{ supabaseUser: [] }]
      responses:
        '200': { description: Billing history and credit ledger }
components:
  parameters:
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: true
      schema:
        type: string
        minLength: 8
        maxLength: 200
      description: Stable unique key for this intended provider operation. Reuse it after a timeout.
  securitySchemes:
    supabaseUser:
      type: http
      scheme: bearer
      bearerFormat: Supabase JWT
      description: Supabase access token for the signed-in Vedøy account.
    vedoyApiKey:
      type: http
      scheme: bearer
      bearerFormat: vdy_live_...
      description: "Project API key. Send as Authorization: Bearer vdy_live_... and keep it server-side."
