openapi: 3.1.0
info:
  title: Lee Sugano Digital Solutions - Public Content API
  version: 1.0.0
  summary: Public content and lead-capture API for leesugano.com.
  description: "Public, unauthenticated API for the Lee Sugano Digital Solutions website -\na Brazilian web, mobile, SaaS and AI development agency based in\nGunma-ken, Isesaki-shi, Japan.\n\nUse these endpoints to read the site's editorial content (blog posts,\ncategories and tags, in Portuguese, English, Japanese and Spanish), to\nrecord reactions on posts, and to open a commercial conversation\n(newsletter signup and solution recommendation).\n\nConventions:\n\n- Every response is `application/json`, errors included.\n- Errors carry `error` (human message), `code` (stable, for logic) and\n  `requestId` (for support).\n- Rate-limited endpoints return `RateLimit-Limit`, `RateLimit-Remaining`\n  and `RateLimit-Reset` on every response, plus `Retry-After` on a 429.\n  Honour them instead of retrying in a loop.\n- The same content is available as Markdown on the site's public URLs\n  via `Accept: text/markdown` or a `.md` suffix."
  termsOfService: https://leesugano.com/termos
  contact:
    name: Lee Sugano Digital Solutions
    email: contact@leesugano.com
    url: https://leesugano.com/contact
  license:
    name: Reading, indexing and citation permitted with attribution
    url: https://leesugano.com/termos
externalDocs:
  description: Developer and agent documentation
  url: https://leesugano.com/docs
servers:
- url: https://leesugano.com
  description: Production
tags:
- name: blog
  description: Editorial content published on the blog.
- name: taxonomy
  description: Categories and tags that organise the blog.
- name: engagement
  description: Reader reactions on posts.
- name: leads
  description: Contact capture and solution recommendation.
- name: media
  description: Open Graph image generation.
paths:
  /api/posts:
    get:
      operationId: listPublishedPosts
      tags:
      - blog
      summary: List published blog posts
      description: "Returns the most recent published posts, already translated into the requested language. The post body is not part of this listing - fetch the post's public URL with `Accept: text/markdown` to read the full text. Cover images stored as data URLs are omitted (`null`)."
      parameters:
      - name: locale
        in: query
        required: false
        description: Language of the textual fields. Values outside the list fall back to the default (pt) without error.
        schema:
          type: string
          enum:
          - pt
          - en
          - ja
          - es
          default: pt
      - name: limit
        in: query
        required: false
        description: Maximum number of posts to return.
        schema:
          type: integer
          minimum: 1
          maximum: 50
          default: 4
      - name: featured
        in: query
        required: false
        description: When `true`, returns only posts flagged as featured.
        schema:
          type: boolean
          default: false
      responses:
        "200":
          description: Published posts, newest first.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PostSummary"
        "429":
          description: Rate limit exceeded (60 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to fetch the posts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to fetch the posts.
                code: POSTS_FETCH_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
  /api/categories:
    get:
      operationId: listCategories
      tags:
      - taxonomy
      summary: List blog categories
      description: Every existing category, alphabetically. The `slug` is what appears in `/blog/categoria/{slug}`.
      responses:
        "200":
          description: Registered categories.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Category"
        "429":
          description: Rate limit exceeded (60 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to fetch the categories.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to fetch the categories.
                code: CATEGORIES_FETCH_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
  /api/tags:
    get:
      operationId: listTags
      tags:
      - taxonomy
      summary: List blog tags
      description: "Every existing tag, alphabetically. Not every tag has an indexable page: only those with enough published posts appear in the sitemap."
      responses:
        "200":
          description: Registered tags.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Tag"
        "429":
          description: Rate limit exceeded (60 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to fetch the tags.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to fetch the tags.
                code: TAGS_FETCH_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
  /api/posts/{id}/reactions:
    parameters:
    - name: id
      in: path
      required: true
      description: Numeric post identifier (the `id` field of `listPublishedPosts`).
      schema:
        type: integer
        minimum: 1
    get:
      operationId: getPostReactions
      tags:
      - engagement
      summary: Read a post's reactions
      description: Like and dislike counters for the post, plus the reaction attributed to the caller. The caller is identified by a fingerprint derived from the IP on the server - no cookie, no login.
      responses:
        "200":
          description: Current counters for the post.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReactionState"
        "400":
          description: The `id` given is not a number.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: The `id` given is not a number.
                code: INVALID_POST_ID
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        "404":
          description: No post exists with that id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: No post exists with that id.
                code: POST_NOT_FOUND
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        "429":
          description: Rate limit exceeded (60 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (60).
              schema:
                type: integer
                example: 60
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 59
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to fetch the reactions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to fetch the reactions.
                code: REACTIONS_FETCH_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
    post:
      operationId: reactToPost
      tags:
      - engagement
      summary: Add, switch or remove a reaction
      description: "Sending the reaction that is already recorded removes it (toggle). Sending the opposite one switches it. The operation is idempotent per fingerprint: one IP cannot inflate the counter."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReactionInput"
            example:
              reactionType: like
      responses:
        "200":
          description: The post's state after the reaction.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (20).
              schema:
                type: integer
                example: 20
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 19
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReactionResult"
        "400":
          description: "`reactionType` missing or outside `like`/`dislike`, or invalid `id`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "`reactionType` missing or outside `like`/`dislike`, or invalid `id`."
                code: INVALID_REACTION
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        "404":
          description: No post exists with that id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: No post exists with that id.
                code: POST_NOT_FOUND
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        "429":
          description: Rate limit exceeded (20 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (20).
              schema:
                type: integer
                example: 20
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 19
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to record the reaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to record the reaction.
                code: REACTION_SAVE_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
  /api/newsletter:
    post:
      operationId: subscribeToNewsletter
      tags:
      - leads
      summary: Subscribe an email to the newsletter
      description: The response is identical for a new signup, a reactivated cancellation and an address already subscribed - deliberately, so subscribers cannot be enumerated.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterInput"
            example:
              email: pessoa@exemplo.com
              name: Pessoa
              source: agent
      responses:
        "200":
          description: Signup processed.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (10).
              schema:
                type: integer
                example: 10
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 9
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterResult"
        "400":
          description: Missing or invalid email.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Missing or invalid email.
                code: VALIDATION_ERROR
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        "429":
          description: Rate limit exceeded (10 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (10).
              schema:
                type: integer
                example: 10
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 9
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to process the signup.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to process the signup.
                code: NEWSLETTER_SUBSCRIBE_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
  /api/start/recommend:
    post:
      operationId: recommendSolution
      tags:
      - leads
      summary: Recommend a solution from a business profile
      description: "Takes a short briefing and returns the agency's four service lines ranked by fit, each with an AI-generated reason in the lead's language. The contact is stored before the model call: if the AI fails, the response falls back to a deterministic ranking (`fallback: true`) and the lead is not lost. This endpoint is expensive - honour the 8 req/min limit."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RecommendInput"
            example:
              name: Maria
              businessName: Doceria Sakura
              niche: confeitaria artesanal
              digitalPresence: social
              mainGoal: sellOnline
              email: maria@exemplo.com
              locale: pt
      responses:
        "200":
          description: Solutions ranked for the profile given.
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (8).
              schema:
                type: integer
                example: 8
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 7
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecommendResult"
        "400":
          description: Invalid payload. Either an email or a phone number is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Invalid payload. Either an email or a phone number is required.
                code: VALIDATION_ERROR
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        "429":
          description: Rate limit exceeded (8 req/min per IP).
          headers:
            RateLimit-Limit:
              description: Requests allowed per IP within the 60s window (8).
              schema:
                type: integer
                example: 8
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
                example: 7
            RateLimit-Reset:
              description: Unix timestamp (seconds) at which the window resets.
              schema:
                type: integer
                format: int64
            x-request-id:
              description: Request identifier; quote it when reporting a problem.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 42
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Too Many Requests
                code: RATE_LIMITED
        "500":
          description: Failed to generate the recommendation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to generate the recommendation.
                code: START_RECOMMEND_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
  /api/og:
    get:
      operationId: renderOpenGraphImage
      tags:
      - media
      summary: Render a 1200x630 Open Graph image
      description: Renders a PNG in the site's visual identity. Used by the pages' meta tags; public so external tools can preview it.
      parameters:
      - name: title
        in: query
        required: false
        description: Main headline of the image.
        schema:
          type: string
          default: Lee Sugano
      - name: subtitle
        in: query
        required: false
        description: Supporting line below the headline.
        schema:
          type: string
          default: Agência de Soluções Digitais Premium
      - name: type
        in: query
        required: false
        description: Visual variant of the card.
        schema:
          type: string
          enum:
          - default
          - service
          - portfolio
          - blog
          default: default
      responses:
        "200":
          description: A 1200x630 PNG image.
          content:
            image/png:
              schema:
                type: string
                format: binary
        "500":
          description: Failed to render the image.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Failed to render the image.
                code: OG_IMAGE_FAILED
                requestId: b0d0f1a2-3c4d-5e6f-7a8b-9c0d1e2f3a4b
components:
  schemas:
    Error:
      type: object
      description: Error shape used by the whole API. `code` is stable and meant for logic; `error` is meant for humans.
      required:
      - error
      - code
      properties:
        error:
          type: string
          description: Human-readable message.
        code:
          type: string
          description: Stable error code (SCREAMING_SNAKE_CASE).
          examples:
          - VALIDATION_ERROR
          - RATE_LIMITED
          - NOT_FOUND
        requestId:
          type: string
          description: Request id, also returned in the `x-request-id` header.
        details:
          type: object
          additionalProperties: true
          description: Validation details. Present only in development.
        documentation:
          type: string
          format: uri
          description: URL of the documentation relevant to this error.
    PostSummary:
      type: object
      description: A published post, without the body text.
      required:
      - id
      - title
      - slug
      properties:
        id:
          type: integer
          description: Post identifier.
        title:
          type: string
          description: Title in the requested language.
        slug:
          type: string
          description: Slug in the requested language; the public URL is `/{locale}/blog/{slug}`.
        excerpt:
          type:
          - string
          - "null"
          description: Excerpt in the requested language.
        coverImage:
          type:
          - string
          - "null"
          format: uri
          description: Cover image URL. `null` when the cover is embedded as a data URL.
        publishedAt:
          type:
          - string
          - "null"
          format: date-time
        readTime:
          type:
          - integer
          - "null"
          description: Estimated reading time, in minutes.
        views:
          type:
          - integer
          - "null"
          description: Accumulated page views.
        categoryId:
          type:
          - integer
          - "null"
        categoryName:
          type:
          - string
          - "null"
          description: Category name in the requested language.
        categorySlug:
          type:
          - string
          - "null"
    Category:
      type: object
      required:
      - id
      - name
      - slug
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
          description: Used in `/blog/categoria/{slug}`.
    Tag:
      type: object
      required:
      - id
      - name
      - slug
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
          description: Used in `/blog/tag/{slug}`.
    ReactionType:
      type: string
      enum:
      - like
      - dislike
      description: Accepted reaction types.
    ReactionState:
      type: object
      required:
      - likes
      - dislikes
      - userReaction
      properties:
        likes:
          type: integer
          minimum: 0
        dislikes:
          type: integer
          minimum: 0
        userReaction:
          oneOf:
          - $ref: "#/components/schemas/ReactionType"
          - type: "null"
          description: Reaction already recorded for the caller, or `null`.
    ReactionInput:
      type: object
      required:
      - reactionType
      properties:
        reactionType:
          $ref: "#/components/schemas/ReactionType"
    ReactionResult:
      type: object
      required:
      - success
      - likes
      - dislikes
      - userReaction
      properties:
        success:
          type: boolean
        likes:
          type: integer
          minimum: 0
        dislikes:
          type: integer
          minimum: 0
        userReaction:
          oneOf:
          - $ref: "#/components/schemas/ReactionType"
          - type: "null"
          description: "`null` when the toggle removed the reaction."
    NewsletterInput:
      type: object
      required:
      - email
      properties:
        email:
          type: string
          format: email
          maxLength: 255
        name:
          type: string
          maxLength: 120
        source:
          type: string
          maxLength: 60
          description: Where the signup came from. Agents should identify themselves here.
    NewsletterResult:
      type: object
      required:
      - success
      - message
      properties:
        success:
          type: boolean
        message:
          type: string
    RecommendInput:
      type: object
      description: Business briefing. Either `email` or `phone` is required.
      required:
      - name
      - niche
      - digitalPresence
      - mainGoal
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 120
        businessName:
          type: string
          maxLength: 160
        niche:
          type: string
          minLength: 2
          maxLength: 160
          description: Line of business, free text.
        digitalPresence:
          type: string
          enum:
          - none
          - social
          - basicSite
          - fullSite
          description: "`none` no presence at all; `social` social networks only; `basicSite` an outdated site; `fullSite` a site that already converts."
        mainGoal:
          type: string
          enum:
          - visibility
          - sellOnline
          - automate
          - organize
          - unsure
          description: The business's main goal for the coming months.
        details:
          type: string
          maxLength: 600
        email:
          type: string
          format: email
          maxLength: 255
        phone:
          type: string
          pattern: ^\+?[\d\s().-]{8,25}$
        locale:
          type: string
          enum:
          - pt
          - en
          - ja
          - es
          default: pt
    RecommendationItem:
      type: object
      required:
      - key
      - fit
      properties:
        key:
          type: string
          enum:
          - website
          - ecommerce
          - automation
          - admin
          description: One of the agency's service lines.
        fit:
          type: string
          enum:
          - primary
          - recommended
          - later
          description: How well the solution fits the profile given.
        reason:
          type: string
          description: Reasoning in the lead's language. Absent when `fallback` is `true`.
    RecommendResult:
      type: object
      required:
      - items
      - fallback
      properties:
        items:
          type: array
          description: The four service lines, best fit first.
          items:
            $ref: "#/components/schemas/RecommendationItem"
        pitch:
          type: string
          description: Personalised summary. Absent when `fallback` is `true`.
        fallback:
          type: boolean
          description: "`true` when the AI failed and the ranking came from the deterministic rule."
        leadSaved:
          type: boolean
          description: "`true` when the contact was stored."
        requestId:
          type: string
security: []
