openapi: 3.1.0
info:
  title: ListingForgeAI Public API
  version: 1.0.0
  description: |
    Programmatically enrich product codes (EAN, MPN, SKU/NAME) into ready-to-publish
    e-commerce listings.

    ## Authentication
    All endpoints (except `GET /v1/openapi.yaml`) require an `X-Api-Key` header.
    Generate API keys from your **Profile → API Keys** page in the ListingForgeAI dashboard.

    The base URL for all production calls is `https://services.listingforgeai.it`.

    ## Plan requirements
    The public API is available on **Starter, Professional and Enterprise** plans.
    Free accounts cannot generate API keys and existing keys stop working if a
    paid subscription lapses (`403 Forbidden`).

    ## OpenAI key requirement
    Every enrichment call (`POST /v1/enrich`, `POST /v1/enrich/batch`) routes through
    your **own OpenAI API key**. You must configure it from
    **Profile → Subscription** before the API can serve requests; otherwise the
    endpoint returns `403 Forbidden`.

    ## Rate Limits
    Limits are applied per API key on a rolling 1-minute window:

    | Plan         | Requests / minute |
    |--------------|-------------------|
    | Starter      | 60                |
    | Professional | 300               |
    | Enterprise   | 800               |

    When the limit is exceeded the API returns `429 Too Many Requests` with a
    `Retry-After` header (seconds until the current window resets).

    ## Async vs Sync
    - Single product → `POST /v1/enrich` → synchronous `200` response with the enriched card.
    - Batch (> 1 product) → `POST /v1/enrich/batch` → `202 Accepted` with a `jobId`.
      Poll `GET /v1/jobs/{jobId}` until `status` is `"completed"` or `"failed"`.

servers:
  - url: https://services.listingforgeai.it
    description: Production

security:
  - ApiKeyAuth: []

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key

  schemas:
    ProductCard:
      type: object
      description: Enriched product data returned by the API.
      properties:
        id:
          type: string
          format: uuid
          description: Internal result identifier.
        found:
          type: boolean
          description: Whether the product was identified in knowledge sources.
        title:
          type: string
          nullable: true
          description: Commercial product title.
        ean:
          type: string
          nullable: true
          description: EAN/GTIN barcode.
        manufacturerCode:
          type: string
          nullable: true
          description: Manufacturer part number.
        brand:
          type: string
          nullable: true
        designer:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
          description: SEO-oriented product description (3–4 sentences).
        technicalSpecifications:
          type: array
          items:
            type: string
          description: List of technical spec strings.
        variants:
          type: array
          items:
            type: string
          description: Available product variants (colour, size, etc.).
        images:
          type: array
          items:
            type: string
            format: uri
          description: Product image URLs.
      required:
        - id
        - found

    EnrichRequest:
      type: object
      required:
        - code
      properties:
        code:
          type: string
          minLength: 1
          description: The product code to enrich (EAN, MPN or free-text name).
        codeType:
          type: string
          enum: [EAN, MPN, NAME]
          default: EAN
          description: Hint for the enrichment engine about the code format.
        language:
          type: string
          default: English
          description: Language for the generated description and specifications.
        brandVoiceProfileId:
          type: string
          format: uuid
          description: Optional Brand Voice profile to apply to the generated content.

    EnrichBatchRequest:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          minItems: 1
          items:
            type: object
            required:
              - code
            properties:
              code:
                type: string
                minLength: 1
              codeType:
                type: string
                enum: [EAN, MPN, NAME]
                default: EAN
        language:
          type: string
          default: English
        brandVoiceProfileId:
          type: string
          format: uuid

    JobStatus:
      type: object
      properties:
        jobId:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, completed, failed]
        results:
          type: array
          items:
            $ref: '#/components/schemas/ProductCard'
          description: Present only when status is "completed".
      required:
        - jobId
        - status

    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: object
        remaining:
          type: integer
          description: |
            Quota errors only — number of products still available on the
            current plan in the current billing window.
        limit:
          type: integer
          description: Quota errors only — total products allowed by the current plan.
        planName:
          type: string
          description: Quota errors only — internal plan name (`starter`, `professional`, `enterprise`).
      required:
        - error

paths:
  /v1/enrich:
    post:
      operationId: enrich
      summary: Enrich a single product code
      description: |
        Synchronously enriches a single product code and returns the full ProductCard.
        Response time is typically 5–30 seconds depending on data availability.
      tags:
        - Enrichment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EnrichRequest'
            example:
              code: "8033588716790"
              codeType: EAN
              language: English
      responses:
        '200':
          description: Successfully enriched ProductCard.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductCard'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. One of:
            - the account is on the Free plan (paid plan required);
            - no OpenAI API key has been configured in the user profile;
            - the monthly product quota for the current plan has been exhausted.
            When the cause is quota exhaustion, the body includes `remaining`,
            `limit` and `planName` fields alongside `error`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until the rate limit window resets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: Enrichment backend error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v1/enrich/batch:
    post:
      operationId: enrichBatch
      summary: Enrich multiple product codes asynchronously
      description: |
        Submits a batch of product codes for async enrichment.
        Returns a `jobId` immediately; poll `GET /v1/jobs/{jobId}` for results.
      tags:
        - Enrichment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EnrichBatchRequest'
            example:
              items:
                - code: "8033588716790"
                  codeType: EAN
                - code: "M7WJ2LL/A"
                  codeType: MPN
              language: English
      responses:
        '202':
          description: Batch job accepted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobId:
                    type: string
                    format: uuid
                required:
                  - jobId
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. One of:
            - the account is on the Free plan (paid plan required);
            - no OpenAI API key has been configured in the user profile;
            - the monthly product quota for the current plan has been exhausted.
            When the cause is quota exhaustion, the body includes `remaining`,
            `limit` and `planName` fields alongside `error`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded.
          headers:
            Retry-After:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v1/jobs/{jobId}:
    get:
      operationId: getJob
      summary: Get batch job status and results
      tags:
        - Jobs
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job status (and results if completed).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobStatus'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden. The account no longer has a paid plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded.
          headers:
            Retry-After:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v1/products/{id}:
    get:
      operationId: getProduct
      summary: Get a single enriched product
      tags:
        - Products
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Enriched ProductCard.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductCard'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden. The account no longer has a paid plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Product not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded.
          headers:
            Retry-After:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v1/openapi.yaml:
    get:
      operationId: getOpenApiSpec
      summary: Retrieve this OpenAPI specification
      description: Returns the raw OpenAPI 3.1 YAML document. No authentication required.
      security: []
      tags:
        - Meta
      responses:
        '200':
          description: OpenAPI YAML document.
          content:
            application/yaml:
              schema:
                type: string
