openapi: 3.1.0
info:
  title: Tetrees AI Pack API
  version: 2.2.0
  description: TAIP/1 trading, Agent AVCP, hosted/BYOK runtime, cited skills, ephemeral run files, auditioned client extensions, owner-reviewed growth and selectable hosted intelligence checkpoints.
servers:
  - url: https://ex.tetrees.ai
components:
  securitySchemes:
    bearerToken:
      type: http
      scheme: bearer
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    AgentExampleUse:
      type: object
      required: [title, scenario, exampleRequest, expectedOutcome]
      properties:
        title: { type: string, minLength: 5, maxLength: 120 }
        scenario: { type: string, minLength: 20, maxLength: 1000 }
        exampleRequest: { type: string, minLength: 10, maxLength: 4000 }
        expectedOutcome: { type: string, minLength: 20, maxLength: 2000 }
    AgentPublicListing:
      type: object
      required: [problem, targetUsers, inputs, workflow, outputs, exampleUses, successCriteria, limitations]
      properties:
        problem: { type: string, minLength: 40, maxLength: 3000 }
        targetUsers: { type: array, minItems: 1, maxItems: 12, items: { type: string } }
        inputs: { type: array, minItems: 1, maxItems: 20, items: { type: string } }
        workflow: { type: array, minItems: 2, maxItems: 16, items: { type: string } }
        outputs: { type: array, minItems: 1, maxItems: 20, items: { type: string } }
        exampleUses: { type: array, minItems: 2, maxItems: 8, items: { $ref: '#/components/schemas/AgentExampleUse' } }
        successCriteria: { type: array, minItems: 1, maxItems: 16, items: { type: string } }
        limitations: { type: array, minItems: 1, maxItems: 16, items: { type: string } }
    ClientExtensionMethod:
      type: object
      additionalProperties: false
      required: [id, title, description, effect, confirmation, inputSchema]
      properties:
        id: { type: string, pattern: '^[a-z0-9][a-z0-9_.-]{1,63}$' }
        title: { type: string, minLength: 3, maxLength: 120 }
        description: { type: string, minLength: 20, maxLength: 1000 }
        effect: { enum: [read, write] }
        confirmation: { enum: [none, per_call], description: Write methods require per_call. }
        inputSchema:
          type: object
          additionalProperties: false
          required: [type, properties, required, additionalProperties]
          properties:
            type: { const: object }
            properties: { type: object }
            required: { type: array, maxItems: 32, items: { type: string } }
            additionalProperties: { const: false }
    ClientExtension:
      type: object
      additionalProperties: false
      required: [id, title, description, kind, execution, required, readOnly, methods]
      properties:
        id: { type: string, pattern: '^[a-z0-9][a-z0-9_.-]{1,63}$' }
        title: { type: string, minLength: 3, maxLength: 120 }
        description: { type: string, minLength: 20, maxLength: 1000 }
        kind: { enum: [mcp, https_api, local_skill] }
        execution: { const: client_only }
        required: { type: boolean }
        readOnly: { type: boolean }
        configurationUrl: { type: string, format: uri, pattern: '^https://' }
        environmentKeys: { type: array, maxItems: 12, items: { type: string, pattern: '^[A-Z][A-Z0-9_]{1,63}$' } }
        methods: { type: array, maxItems: 32, items: { $ref: '#/components/schemas/ClientExtensionMethod' } }
    GrowthCounter:
      type: object
      additionalProperties: false
      required: [slots, bytes]
      properties:
        slots: { type: integer, minimum: 0 }
        bytes: { type: integer, minimum: 0 }
    GrowthUsageScope:
      type: object
      additionalProperties: false
      required: [pack, account]
      properties:
        pack: { $ref: '#/components/schemas/GrowthCounter' }
        account: { $ref: '#/components/schemas/GrowthCounter' }
    GrowthUsage:
      type: object
      additionalProperties: false
      required: [memory, proposals]
      properties:
        memory: { $ref: '#/components/schemas/GrowthUsageScope' }
        proposals: { $ref: '#/components/schemas/GrowthUsageScope' }
    GrowthLimits:
      type: object
      additionalProperties: false
      required: [memorySlotsPerPack, memorySlotsPerAccount, memoryBytesPerPack, memoryBytesPerAccount, memoryBytesPerItem, proposalSlotsPerPack, proposalSlotsPerAccount, proposalBytesPerPack, proposalBytesPerAccount]
      properties:
        memorySlotsPerPack: { type: integer, const: 20 }
        memorySlotsPerAccount: { type: integer, const: 100 }
        memoryBytesPerPack: { type: integer, const: 131072 }
        memoryBytesPerAccount: { type: integer, const: 1048576 }
        memoryBytesPerItem: { type: integer, const: 16384 }
        proposalSlotsPerPack: { type: integer, const: 40 }
        proposalSlotsPerAccount: { type: integer, const: 200 }
        proposalBytesPerPack: { type: integer, const: 262144 }
        proposalBytesPerAccount: { type: integer, const: 2097152 }
    GrowthVersion:
      type: object
      additionalProperties: false
      required: [sequence]
      properties:
        sequence: { type: integer, minimum: 0 }
        base: { type: boolean }
        label: { type: string }
        baseVersion: { type: string }
        acceptedAt: { type: string, format: date-time }
        digest: { type: string }
    GrowthRecord:
      type: object
      additionalProperties: false
      required: [sequence, kind, status, proposal_digest, audition, created_at, accepted_at, rolled_back_at]
      properties:
        sequence: { type: integer, minimum: 1 }
        kind: { enum: [memory, skill, eval] }
        status: { enum: [proposed, normalizing, accepted, rejected, rolled_back] }
        proposal_digest: { type: [string, 'null'] }
        audition: { type: [object, 'null'], additionalProperties: true }
        created_at: { type: string, format: date-time }
        accepted_at: { type: [string, 'null'], format: date-time }
        rolled_back_at: { type: [string, 'null'], format: date-time }
    GrowthState:
      type: object
      additionalProperties: false
      required: [growth, activeSequence, versions, usage, limits, storageScope, normalization]
      properties:
        growth:
          type: array
          items: { $ref: '#/components/schemas/GrowthRecord' }
        activeSequence: { type: integer, minimum: 0, description: Persistent account-and-Pack default. }
        versions:
          type: array
          minItems: 1
          items: { $ref: '#/components/schemas/GrowthVersion' }
        usage: { $ref: '#/components/schemas/GrowthUsage' }
        limits: { $ref: '#/components/schemas/GrowthLimits' }
        storageScope: { type: string }
        normalization:
          type: object
          required: [hostedPoints, byokPoints, portablePoints, note]
          properties:
            hostedPoints: { type: integer, minimum: 0 }
            byokPoints: { type: integer, minimum: 0 }
            portablePoints: { type: integer, minimum: 0 }
            note: { type: string }
paths:
  /api/ai-packs/catalog:
    get:
      summary: Search published AI Packs
      parameters:
        - { in: query, name: q, schema: { type: string, maxLength: 180 } }
        - { in: query, name: limit, schema: { type: integer, minimum: 1, maximum: 48 } }
        - { in: query, name: offset, schema: { type: integer, minimum: 0 } }
      responses:
        "200": { description: Paginated AI Pack catalogue }
  /api/ai-packs/models:
    get:
      summary: List runtime models and Point quote samples
      responses:
        "200": { description: Model registry }
  /api/ai-packs/workload/status:
    get:
      security: [{ bearerToken: [] }]
      summary: Inspect current AI workload capacity
      responses:
        "200": { description: Distributed admission source, global/type limits, active counts and queued count }
  /api/ai-packs/skills:
    get:
      summary: List hosted skills, Point costs, ephemeral file limits and the client-runtime boundary
      responses:
        "200": { description: Public runtime skill registry }
  /api/ai-packs/{id}/runtime-profile:
    get:
      summary: Inspect one published Pack version's execution contract
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200":
          description: Declared hosted skills, file policy and Agent-AVCP-auditioned client extensions
          content:
            application/json:
              schema:
                type: object
                properties:
                  clientExtensions:
                    type: array
                    items: { $ref: '#/components/schemas/ClientExtension' }
  /api/ai-packs/owned:
    get:
      security: [{ bearerToken: [] }]
      summary: List entitled and seller-owned AI Packs
      responses:
        "200": { description: Owned AI Packs }
  /api/ai-packs/runs/quote:
    post:
      security: [{ bearerToken: [] }]
      summary: Quote one hosted or BYOK agent run
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [productId, model]
              properties:
                productId: { type: string, format: uuid }
                model: { type: string }
                fundingMode: { enum: [points, byok, local] }
                enabledSkills: { type: array, maxItems: 12, items: { type: string } }
                growthVersion: { type: integer, minimum: 0, description: "0 selects the signed base Pack; omit for the account default" }
                maxInputTokens: { type: integer, minimum: 100, maximum: 200000 }
                maxOutputTokens: { type: integer, minimum: 100, maximum: 32000 }
      responses:
        "200": { description: Bound quote including availablePoints and canAfford; call this before every Points-funded run }
  /api/ai-packs/{id}/runs:
    post:
      security: [{ bearerToken: [] }]
      summary: Run an owned AI Pack
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
        - { in: header, name: x-tetrees-provider-key, required: false, schema: { type: string }, description: Request-only BYOK secret }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, prompt]
              properties:
                model: { type: string }
                prompt: { type: string, minLength: 2, maxLength: 40000 }
                fundingMode: { enum: [points, byok] }
                enabledSkills: { type: array, maxItems: 12, items: { type: string } }
                growthVersion: { type: integer, minimum: 0, description: "0 selects the signed base Pack; omit for the account default" }
      responses:
        "200": { description: "Agent result, usage, billing and growth proposals" }
        "402": { description: Insufficient Tetrees Points }
        "403": { description: Entitlement or safety boundary failed }
        "429": { description: "AI_WORKLOAD_BUSY with Retry-After; no Points were charged" }
  /api/ai-packs/{id}/runs-with-files:
    post:
      security: [{ bearerToken: [] }]
      summary: Run an owned AI Pack with ephemeral attachments
      description: Raw files and extracted text are held in request memory only. Run history retains bounded file metadata and the requested agent result.
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
        - { in: header, name: x-tetrees-provider-key, required: false, schema: { type: string }, description: Request-only BYOK secret }
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [model, prompt]
              properties:
                model: { type: string }
                prompt: { type: string, minLength: 2, maxLength: 40000 }
                fundingMode: { enum: [points, byok] }
                enabledSkills: { type: string, description: JSON array of Pack-declared hosted skill ids }
                growthVersion: { type: integer, minimum: 0, description: "0 selects the signed base Pack; omit for the account default" }
                maxInputTokens: { type: integer, minimum: 100, maximum: 200000 }
                maxOutputTokens: { type: integer, minimum: 100, maximum: 32000 }
                attachments:
                  type: array
                  maxItems: 5
                  items: { type: string, format: binary }
      responses:
        "200": { description: "Markdown-capable agent result, usage, billing, skills and retained attachment metadata" }
        "400": { description: "Unsupported, malformed, oversized or undeclared attachment capability" }
        "402": { description: Insufficient Points for model plus hosted skills }
        "429": { description: "AI_WORKLOAD_BUSY with Retry-After; no Points were charged" }
  /api/seller/products:
    post:
      security: [{ bearerToken: [] }]
      summary: Create an AI Pack draft
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [assetKind, name]
              properties:
                assetKind: { const: agent_pack }
                name: { type: string, minLength: 3, maxLength: 120 }
                priceUsd: { type: number, minimum: 0 }
                agentSummary:
                  type: object
                  required: [capabilities, models, listing]
                  properties:
                    capabilities: { type: array, minItems: 1, items: { type: string } }
                    models: { type: array, minItems: 1, items: { type: string } }
                    listing: { $ref: '#/components/schemas/AgentPublicListing' }
      responses:
        "201": { description: Draft created }
        "403": { description: Terms not accepted }
  /api/ai-packs/terms/accept:
    post:
      security: [{ bearerToken: [] }]
      summary: Accept the current AI Pack terms
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [surface]
              properties:
                surface: { enum: [web, mcp, api] }
      responses:
        "200": { description: Accepted terms version }
  /api/ai-packs/{id}/acquire-free:
    post:
      security: [{ bearerToken: [] }]
      summary: Acquire a published free Pack
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Active free entitlement }
        "400": { description: Pack is paid or already seller-owned }
  /api/ai-packs/{id}/report:
    get:
      summary: Read the current public Agent AVCP report
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: "Public, version-bound report" }
  /api/ai-packs/runs/{runId}:
    get:
      security: [{ bearerToken: [] }]
      summary: Read an owner-scoped run result
      parameters:
        - { in: path, name: runId, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: "Redacted run, usage and settlement" }
        "404": { description: Run absent or owned by another account }
  /api/ai-packs/runs/{runId}/growth:
    post:
      security: [{ bearerToken: [] }]
      summary: Stage one growth proposal from a successful run
      parameters:
        - { in: path, name: runId, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [proposalIndex]
              properties:
                proposalIndex: { type: integer, minimum: 0, maximum: 100 }
      responses:
        "200": { description: Owner-scoped proposed sequence }
  /api/ai-packs/{id}/growth:
    get:
      security: [{ bearerToken: [] }]
      summary: List growth history, selectable checkpoints, usage and hosted quotas without memory contents
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200":
          description: "Digests, gate state, activeSequence, versions, usage and limits; never private memory contents"
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GrowthState' }
  /api/ai-packs/{id}/growth/select:
    post:
      security: [{ bearerToken: [] }]
      summary: Select the signed base Pack or an accepted owner checkpoint
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sequence]
              properties:
                sequence: { type: integer, minimum: 0 }
      responses:
        "200":
          description: "Persistent account-and-Pack checkpoint selection"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [selected, productId, activeSequence, basePack]
                properties:
                  selected: { const: true }
                  productId: { type: string, format: uuid }
                  activeSequence: { type: integer, minimum: 0 }
                  basePack: { type: boolean }
        "400": { description: "Checkpoint does not exist for this owner" }
  /api/ai-packs/{id}/growth/{sequence}/accept:
    post:
      security: [{ bearerToken: [] }]
      summary: Owner-approve and single-claim a growth delta
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
        - { in: path, name: sequence, required: true, schema: { type: integer, minimum: 1 } }
        - { in: header, name: x-tetrees-provider-key, required: false, schema: { type: string }, description: Request-only provider key for Tetrees Agent normalization }
        - { in: header, name: x-tetrees-growth-normalization, required: false, schema: { enum: [portable] }, description: Provider-free normalization for non-OpenAI BYOK }
      responses:
        "200": { description: Accepted gated delta with admission and normalization billing }
        "402": { description: Insufficient Points for hosted normalization }
        "429": { description: "AI_WORKLOAD_BUSY with Retry-After; no Points were charged" }
        "409": { description: "Already claimed, accepted or changed" }
  /api/seller/products/{id}:
    patch:
      security: [{ bearerToken: [] }]
      summary: Update owner-bound AI Pack listing metadata
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Updated draft }
  /api/seller/products/{id}/versions/upload-url:
    post:
      security: [{ bearerToken: [] }]
      summary: Mint a private product/version-bound upload contract
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [version]
              properties:
                version: { type: string, pattern: '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' }
                contentType: { const: application/zip }
      responses:
        "200": { description: Presigned S3 POST or multipart fallback }
  /api/seller/products/{id}/versions:
    post:
      security: [{ bearerToken: [] }]
      summary: Validate and commit one immutable TAIP/1 version
      description: Send exactly one artifact source. MCP uses the upload-url contract; small/local clients may use multipart artifact.
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [version, s3Key]
              properties:
                version: { type: string }
                s3Key: { type: string }
                changelog: { type: string, maxLength: 10000 }
          multipart/form-data:
            schema:
              type: object
              required: [version, artifact]
              properties:
                version: { type: string }
                changelog: { type: string, maxLength: 10000 }
                artifact: { type: string, format: binary }
      responses:
        "201": { description: Version and manifest committed }
        "400": { description: "Unsafe/invalid TAIP, wrong source or non-increasing version" }
        "409": { description: Immutable version exists or audition is running }
  /api/seller/products/{id}/preview-images:
    post:
      security: [{ bearerToken: [] }]
      summary: Sanitize and attach a catalogue image
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [image]
              properties:
                image: { type: string, format: binary }
      responses:
        "201": { description: Sanitized bounded WebP asset }
  /api/seller/products/{id}/submission-readiness:
    get:
      security: [{ bearerToken: [] }]
      summary: List exact missing Pack, listing and image requirements
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Readiness with missing requirements }
  /api/seller/products/{id}/submit:
    post:
      security: [{ bearerToken: [] }]
      summary: Submit a complete immutable version for Agent AVCP
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Submitted state }
        "400": { description: Completeness gate failed }
  /api/seller/products/{id}/prep-quote:
    get:
      security: [{ bearerToken: [] }]
      summary: Quote Agent AVCP tiers for the current version
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Four size/version-bound Point options }
  /api/seller/products/{id}/prep:
    post:
      security: [{ bearerToken: [] }]
      summary: Run Agent AVCP after explicit Point confirmation
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tier, expectedPoints, expectedVersion, confirmation]
              properties:
                tier: { enum: [verified_listing], description: The one mandatory Agent AVCP audition for this immutable Pack version. }
                expectedPoints: { type: integer, minimum: 1 }
                expectedVersion: { type: string }
                confirmation: { const: RUN_SELLER_PREP }
      responses:
        "200": { description: Completed Agent AVCP and reports }
        "409": { description: Stale quote or concurrent audition }
  /api/seller/products/{id}/report:
    get:
      security: [{ bearerToken: [] }]
      summary: Read private owner-only Agent AVCP feedback
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Safe actionable failed/pass evidence }
  /api/seller/products/{id}/publish:
    post:
      security: [{ bearerToken: [] }]
      summary: Publish the exact signed Agent AVCP-passing digest
      parameters:
        - { in: path, name: id, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Published AI Pack }
        "400": { description: Gate/signature/Connect requirement failed }
