openapi: 3.1.0
info:
  title: Teneo Agent Book Generation API
  version: "2026-05-25"
  description: >
    Generate a complete, publish-ready non-fiction book (full manuscript + cover +
    PDF/EPUB/DOCX) from a topic or spec. Asynchronous: POST to generate, poll status,
    then download from the output manifest. Teneo is a human-in-the-loop publishing
    system, not a one-prompt generator.
  contact:
    url: https://teneo.io/llms.txt
servers:
  # The agent API is served under the main domain via a CloudFront `/api/agent/*`
  # behavior that routes to the Book API Gateway. Use the clean base below.
  - url: https://teneo.io
    description: Production
  - url: https://staging.teneo.io
    description: Staging
paths:
  /api/agent/capabilities:
    get:
      operationId: getCapabilities
      summary: Discover auth, pricing, request fields, workflow, and output shape.
      responses:
        "200":
          description: Capabilities document.
  /api/agent/books/generate:
    post:
      operationId: generateBook
      summary: Start a publish-ready book generation job.
      description: >
        Returns 202 with a jobId immediately; generation takes ~45 min. Credits are
        quoted as bookCredits + coverCredits, charged to the funded account during
        the workflow, and refunded on failure.
      security:
        - teneoAgentToken: []
        - bearerAuth: []
        - serviceKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookRequest"
            example:
              title: The Indie Author's KDP Launch Playbook
              description: A practical launch system for self-published nonfiction on Amazon KDP.
              target_audience: self-publishing nonfiction authors
              number_of_chapters: 10
              output_formats: [pdf, epub]
              on_behalf_of: "<teneo-user-id>"
      responses:
        "202":
          description: Job queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobAccepted"
        "400":
          description: Invalid request (see code/details).
        "401":
          description: Authentication required.
        "403":
          description: Delegated agent token is invalid for this resource or missing required scopes.
        "402":
          description: Insufficient credits on the funded account (top up at /pricing).
        "409":
          description: Idempotency key conflict.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
          description: Stable retry key. Reusing it with the same body returns the original job; reusing it with a different body returns 409.
  /api/agent/books/status/{jobId}:
    get:
      operationId: getBookStatus
      summary: Poll job status; returns the output manifest when completed.
      security:
        - teneoAgentToken: []
        - bearerAuth: []
        - serviceKey: []
      parameters:
        - name: jobId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Current job status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatus"
        "404":
          description: Job not found.
  /api/agent/tools/{tool}:
    post:
      operationId: runAgentTool
      summary: Run a granular Teneo tool: outline, cover, evaluate, or niche.
      security:
        - teneoAgentToken: []
        - bearerAuth: []
        - serviceKey: []
      parameters:
        - name: tool
          in: path
          required: true
          schema: { type: string, enum: [outline, cover, evaluate, niche] }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
            examples:
              outline:
                value: { title: "KDP Launch Playbook", description: "Launch system", number_of_chapters: 10 }
              cover:
                value: { title: "KDP Launch Playbook", author: "Teneo" }
              evaluate:
                value: { fullBookId: "book_model", evaluationMode: "core" }
              niche:
                value: { topic: "AI-assisted nonfiction publishing", count: 12 }
      responses:
        "200": { description: Tool response. }
        "202": { description: Async tool job queued. }
        "400": { description: Invalid request. }
        "401": { description: Authentication required. }
components:
  securitySchemes:
    teneoAgentToken:
      type: http
      scheme: bearer
      description: >
        Preferred outside-agent auth. Start at https://auth.teneo.io/auth.md,
        register service_auth for client_id 'teneo-production' and resource
        'https://teneo.io/', have the user approve the claim, then exchange the
        claim token for a delegated agent access token. Send it as
        'Authorization: Bearer <token>'. The delegated user is charged; request
        body on_behalf_of is ignored. Required scopes: generate needs
        'books.generate credits.spend'; status needs 'books.status'; tools need
        'books.quote', 'books.generate credits.spend', or 'books.status'
        depending on the tool.
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        Legacy per-agent API key. Create one in your Teneo dashboard, then send
        'Authorization: Bearer tk_...'. Books are charged to your own credit
        balance - no on_behalf_of needed.
    serviceKey:
      type: apiKey
      in: header
      name: x-service-key
      description: >
        Ecosystem service key. Send with 'on_behalf_of' (a Teneo user id) so the
        book is charged to that account.
  schemas:
    BookRequest:
      type: object
      required: [title, description, target_audience]
      properties:
        title: { type: string, description: "Book title (aliases: book_topic, topic)." }
        description: { type: string, description: "What the book covers (alias: desired_outcome)." }
        target_audience: { type: string, description: "Who the book is for." }
        creative_direction: { type: string, description: "Tone / style / angle." }
        number_of_chapters: { type: integer, minimum: 3, maximum: 24, default: 12 }
        topics_per_chapter: { type: integer, minimum: 1, maximum: 6, default: 3 }
        subtopic_count: { type: integer, minimum: 1, maximum: 5, default: 3 }
        model_version: { type: string }
        book_type: { type: string, enum: [nonfiction], default: nonfiction }
        author_name: { type: string }
        output_formats:
          type: array
          items: { type: string, enum: [pdf, epub, docx] }
          default: [pdf, epub, docx]
        callback_url: { type: string, format: uri, description: "Webhook POSTed on completion." }
        on_behalf_of: { type: string, description: "Teneo user id to charge (required with x-service-key)." }
        min_eval_score:
          type: number
          minimum: 0
          maximum: 10
          description: >
            Opt-in quality floor (e.g. 8.0). The pipeline evaluates the finished
            manuscript, rewrites weak sections and re-evaluates (up to
            max_rewrite_rounds) until the score clears the floor, and only then
            finalizes. Evaluation (~0.1-0.4 credits/round) and rewrite
            (~0.01/section) are charged in addition to the book; the job FAILS
            with a full refund if the floor cannot be met. The result carries an
            outputManifest.quality receipt.
        max_rewrite_rounds: { type: integer, minimum: 0, maximum: 3, default: 2, description: "Rewrite rounds allowed under min_eval_score." }
        evaluation_mode: { type: string, enum: [pro, core], default: pro, description: "Evaluation tier used by the quality floor." }
        rewrite_product: { type: string, enum: [core_rewrite, pro_rewrite], default: core_rewrite, description: "Rewrite tier used by the quality floor." }
    JobAccepted:
      type: object
      properties:
        jobId: { type: string }
        status: { type: string, example: queued }
        pollUrl: { type: string }
        estimatedMinutes: { type: integer, example: 45 }
        quote:
          type: object
          properties:
            requiredCredits: { type: number }
            availableCredits: { type: number }
            bookCredits: { type: number }
            coverCredits: { type: number }
    JobStatus:
      type: object
      properties:
        jobId: { type: string }
        status: { type: string, enum: [queued, running, completed, failed] }
        progress: { type: integer, minimum: 0, maximum: 100 }
        currentStep: { type: string }
        message: { type: string }
        estimatedMinutes: { type: integer }
        error: { type: string }
        outputManifest:
          type: object
          properties:
            coverUrl: { type: string }
            fullBookId: { type: string }
            contentVersion: { type: string, description: "Finalized content version (OG, or R1/R2 after quality-floor rewrites)." }
            quality:
              type: object
              description: "Quality-gate receipt (present when min_eval_score was set)."
              properties:
                evalScore: { type: number }
                evalThreshold: { type: number }
                rewriteRounds: { type: integer }
                contentVersion: { type: string }
                gateDecision: { type: string, enum: [passed, below_floor_observed, accepted_below_floor_no_weak_chapters] }
                gatedAt: { type: string, format: date-time }
                routerReceipt:
                  type: object
                  description: "Machine-readable next-action router receipt for delivery, deeper eval, rewrite, panel proof, and publish-packet eligibility."
                  properties:
                    routerName: { type: string, example: quality_gate_publish_ready_router }
                    schemaVersion: { type: string, example: eos.router.v1 }
                    status: { type: string, example: completed }
                    artifactRef:
                      type: object
                      additionalProperties: true
                    signals:
                      type: object
                      additionalProperties: true
                    candidates:
                      type: array
                      items:
                        type: object
                        additionalProperties: true
                    selectedAction: { type: string, example: finalize }
                    recommendation:
                      type: object
                      additionalProperties: true
                    allowedActions:
                      type: array
                      items: { type: string }
                    blockedActions:
                      type: array
                      items: { type: string }
                    learningSignals:
                      type: object
                      additionalProperties: true
                    modelRouterContext:
                      type: object
                      additionalProperties: true
                    calibrationStatus:
                      type: object
                      additionalProperties: true
                    createdAt: { type: string, format: date-time }
            formats:
              type: object
              additionalProperties:
                type: object
                properties:
                  url: { type: string, description: "Time-limited (~1h) download URL." }
