openapi: 3.1.0
info:
  title: Datanest Agent Commerce API
  version: 1.0.0
  description: >
    Agent-friendly HTTP API over the Datanest digital-product catalogue. Backed by
    the same shared service layer as the Datanest MCP server (https://datanest-stores.pages.dev/mcp).
    Read endpoints are public, rate-limited, and input-validated. Checkout creation
    resolves price server-side from trusted catalogue data and never accepts a
    client-supplied price, amount, currency, product name, or Stripe price id. The
    buyer must explicitly authorize payment in Stripe Checkout; no autonomous charge.
  license:
    name: Product content is MIT-licensed; API is proprietary to Datanest.
servers:
  - url: https://datanest-stores.pages.dev/api/agent/v1
paths:
  /products:
    get:
      operationId: listProducts
      summary: List / filter products
      parameters:
        - { name: q, in: query, schema: { type: string, maxLength: 200 } }
        - { name: domain, in: query, schema: { type: string, enum: [data, ai, cloud, dev, security, career] } }
        - { name: category, in: query, schema: { type: string } }
        - { name: tag, in: query, description: Repeatable; ALL must match, schema: { type: array, items: { type: string } } }
        - { name: max_price_usd, in: query, schema: { type: number } }
        - { name: verified_only, in: query, schema: { type: boolean } }
        - { name: include_unsellable, in: query, description: Include unaudited/deprecated (flagged, not buyable), schema: { type: boolean } }
        - { name: page, in: query, schema: { type: integer, minimum: 1 } }
        - { name: page_size, in: query, schema: { type: integer, minimum: 1, maximum: 50 } }
      responses:
        '200':
          description: Paginated product cards
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SearchResult' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /products/{idOrSlug}:
    get:
      operationId: getProduct
      summary: Get full product details
      parameters:
        - name: idOrSlug
          in: path
          required: true
          description: 'Product id ("store:slug"), SKU, or unambiguous slug.'
          schema: { type: string }
      responses:
        '200':
          description: Product details
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductDetails' }
        '404': { $ref: '#/components/responses/Error' }
  /search:
    get:
      operationId: searchProducts
      summary: Search the catalogue
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: domain, in: query, schema: { type: string } }
        - { name: category, in: query, schema: { type: string } }
        - { name: tag, in: query, schema: { type: array, items: { type: string } } }
        - { name: max_price_usd, in: query, schema: { type: number } }
        - { name: verified_only, in: query, schema: { type: boolean } }
        - { name: include_unsellable, in: query, schema: { type: boolean } }
        - { name: page, in: query, schema: { type: integer } }
        - { name: page_size, in: query, schema: { type: integer } }
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SearchResult' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /checkout-links:
    post:
      operationId: createCheckoutLink
      summary: Create a short-lived Stripe checkout link (side-effecting)
      description: >
        Resolves the product + Stripe price server-side and creates a short-lived
        Stripe Checkout Session (or returns the trusted pre-created payment link).
        Rejects any client-supplied price/amount/currency/stripe_price_id/product_name/
        file_url. Products that are unaudited/deprecated/not agent-sellable are rejected
        (422). Idempotent via idempotency_key or the Idempotency-Key header.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CheckoutRequest' }
      responses:
        '201':
          description: Checkout link created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CheckoutResult' }
        '400': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '422':
          description: Product not available for agent checkout (unaudited/deprecated)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ErrorBody' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /orders/{sessionId}:
    get:
      operationId: getOrderStatus
      summary: Poll checkout/order status (no PII)
      parameters:
        - name: sessionId
          in: path
          required: true
          description: Stripe checkout session id (cs_...) from create_checkout_link.
          schema: { type: string }
      responses:
        '200':
          description: Coarse order status
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OrderStatus' }
        '400': { $ref: '#/components/responses/Error' }
components:
  responses:
    Error:
      description: Error
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorBody' }
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorBody' }
  schemas:
    ErrorBody:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
            details: { type: object, additionalProperties: true }
    ProductCard:
      type: object
      properties:
        product_id: { type: string }
        slug: { type: string }
        store: { type: string }
        title: { type: string }
        short_description: { type: string }
        price_usd: { type: [number, 'null'] }
        currency: { type: string }
        domain: { type: string }
        category: { type: string }
        tags: { type: array, items: { type: string } }
        verification_status: { type: string, enum: [verified, community, unaudited, deprecated] }
        agent_sellable: { type: boolean }
        is_bundle: { type: boolean }
        product_url: { type: string, format: uri }
        preview_url: { type: [string, 'null'] }
    SearchResult:
      type: object
      properties:
        query: { type: [string, 'null'] }
        page: { type: integer }
        page_size: { type: integer }
        total: { type: integer }
        total_pages: { type: integer }
        results: { type: array, items: { $ref: '#/components/schemas/ProductCard' } }
    ProductDetails:
      type: object
      properties:
        product_id: { type: string }
        sku: { type: string }
        slug: { type: string }
        store: { type: string }
        store_name: { type: [string, 'null'] }
        domain: { type: string }
        category: { type: string }
        tags: { type: array, items: { type: string } }
        title: { type: string }
        short_description: { type: string }
        detailed_description: { type: string }
        price_usd: { type: [number, 'null'] }
        currency: { type: string }
        is_bundle: { type: boolean }
        license: { type: string }
        version: { type: string }
        changelog_at: { type: [string, 'null'] }
        last_verified_at: { type: [string, 'null'] }
        verification_status: { type: string }
        agent_sellable: { type: boolean }
        agent_checkout_available: { type: boolean }
        install_time: { type: [string, 'null'] }
        prerequisites: { type: array, items: { type: string } }
        supported_versions: { type: array, items: { type: string } }
        compatibility_notes: { type: [string, 'null'] }
        included_files: { type: array, items: { type: string } }
        download_size: { type: string }
        product_url: { type: string }
        preview_url: { type: [string, 'null'] }
        demo_url: { type: [string, 'null'] }
        status_warning: { type: [string, 'null'] }
    CheckoutRequest:
      type: object
      required: [product_id]
      additionalProperties: false
      properties:
        product_id: { type: string, description: 'Product id, SKU, or unambiguous slug.' }
        quantity: { type: integer, enum: [1], default: 1 }
        buyer_email: { type: string, format: email }
        success_url: { type: string, format: uri, description: 'Must be on the allowlisted host.' }
        cancel_url: { type: string, format: uri, description: 'Must be on the allowlisted host.' }
        idempotency_key: { type: string }
    CheckoutResult:
      type: object
      properties:
        checkout_url: { type: string, format: uri }
        checkout_session_id: { type: [string, 'null'] }
        expires_at: { type: string, format: date-time }
        method: { type: string, enum: [stripe_checkout_session, stripe_payment_link] }
        requires_buyer_authorization: { type: boolean, enum: [true] }
        status_poll: { type: [string, 'null'] }
        notice: { type: string }
        product:
          type: object
          properties:
            product_id: { type: string }
            title: { type: string }
            store: { type: string }
            price_usd: { type: [number, 'null'] }
            currency: { type: string }
            license: { type: string }
    OrderStatus:
      type: object
      properties:
        checkout_session_id: { type: string }
        status: { type: string, enum: [paid, open, expired, unknown] }
        paid: { type: boolean }
        expires_at: { type: [string, 'null'] }
        product_slug: { type: [string, 'null'] }
        store: { type: [string, 'null'] }
        next_step: { type: string }
