openapi: 3.1.0
info:
  title: PepChile Public API
  version: 1.0.0
  summary: Read-only API for the PepChile research-peptide catalog in Chile.
  description: "PepChile sells research-grade peptides in Chile. This API gives an agent the same\ncatalog, prices and shipping quotes that the storefront shows.\n\n## Onboarding\n\nThere is no API key. There is no account. There is no quota to request.\nEvery endpoint below is free and public. To make your first call, send:\n\n    curl https://pepchile.com/api/products\n\nUse the sandbox server for development. The sandbox runs the same code with a\nseparate database and test data.\n\n## Limits\n\nThe catalog endpoints have no quota for each IP address. Be reasonable: read the\nfull catalog one time instead of one product at a time.\nThe two shipping endpoints enforce 60 requests each minute for each IP address.\nAbove that limit they return 429.\n\n## Conventions\n\nAll prices are Chilean pesos (CLP). The price includes IVA. Text content is Spanish (es-CL). A product URL slug has no dose\nsuffix (\"bpc-157\"). A database id can have one (\"bpc-157-10mg\").\n\n## Other protocols\n\nThe same data is available through the Model Context Protocol at /api/mcp and\nthrough A2A JSON-RPC at /api/a2a. Read\nhttps://pepchile.com/.well-known/agent-instructions.md first."
  termsOfService: https://pepchile.com/legal/terminos/
  contact:
    name: PepChile
    url: https://pepchile.com/developers/
    email: contacto@pepchile.com
  license:
    name: Proprietary
    url: https://pepchile.com/legal/terminos/
externalDocs:
  description: Developer documentation and agent instructions.
  url: https://pepchile.com/developers/
servers:
  - url: https://pepchile.com
    description: Production. Live catalog and live prices.
  - url: https://pepchile-dev.pages.dev
    description: Sandbox. The same code, a separate database and test data.
security: []
tags:
  - name: catalog
    description: Products, prices and stock.
  - name: content
    description: Questions, reviews and peptide information.
  - name: shipping
    description: Courier quotes for Chile.
  - name: store
    description: Storefront configuration.
  - name: agents
    description: Protocol endpoints for autonomous agents.
  - name: orders
    description: Order creation. A person must complete the payment.
paths:
  /api/products:
    get:
      tags:
        - catalog
      operationId: listProducts
      summary: Get the full catalog.
      description: Returns every published product with its price, stock and purity. This is the cheapest way to answer a price question or a stock question.
      responses:
        "200":
          description: The catalog.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Product"
  /api/products/{id}:
    get:
      tags:
        - catalog
      operationId: getProduct
      summary: Get one product.
      description: The id must match the database id exactly, for example "bpc-157-10mg". To match a bare compound slug, use the MCP tool get_product.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          examples:
            peptide:
              value: bpc-157-10mg
      responses:
        "200":
          description: The product.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
        "404":
          description: No product has this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        default:
          $ref: "#/components/responses/NotFound"
  /api/faqs:
    get:
      tags:
        - content
      operationId: listFaqs
      summary: Get the storefront questions and answers.
      responses:
        "200":
          description: The questions.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  required:
                    - id
                    - question
                    - answer
                  properties:
                    id:
                      type: string
                    question:
                      type: string
                    answer:
                      type: string
                    displayOrder:
                      type: integer
  /api/reviews/{productId}:
    get:
      tags:
        - content
      operationId: listProductReviews
      summary: Get the published reviews for one product.
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
          examples:
            peptide:
              value: bpc-157-10mg
      responses:
        "200":
          description: The reviews. The list is empty if the product has none.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    productId:
                      type: string
                    rating:
                      type: integer
                      minimum: 1
                      maximum: 5
                    comment:
                      type:
                        - string
                        - "null"
                    customerName:
                      type:
                        - string
                        - "null"
                    createdAt:
                      type:
                        - string
                        - "null"
                      format: date-time
  /api/peptide-info:
    get:
      tags:
        - content
      operationId: listPeptideInfo
      summary: Get the reference article for every peptide.
      description: Each entry holds a slug, a name and a long description in Spanish.
      responses:
        "200":
          description: The entries.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PeptideInfo"
  /api/peptide-info/{slug}:
    get:
      tags:
        - content
      operationId: getPeptideInfo
      summary: Get the reference article for one peptide.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
          examples:
            peptide:
              value: bpc-157
      responses:
        "200":
          description: The entry.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PeptideInfo"
        "404":
          description: No entry has this slug.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/exchange-rate:
    get:
      tags:
        - store
      operationId: getExchangeRate
      summary: Get the reference rate from USD to CLP.
      description: Use this rate to show an approximate foreign price. The customer always pays in CLP.
      responses:
        "200":
          description: The rate.
          content:
            application/json:
              schema:
                type: object
                required:
                  - rate
                  - date
                properties:
                  rate:
                    type: number
                    examples:
                      - 915.21
                  date:
                    type: string
                    format: date
                  source:
                    type: string
  /api/store/config:
    get:
      tags:
        - store
      operationId: getStoreConfig
      summary: Get the public storefront configuration.
      description: Shows whether the store accepts orders, which payment gateway is active, and the limit for bacteriostatic water in one cart.
      responses:
        "200":
          description: The configuration.
          content:
            application/json:
              schema:
                type: object
                properties:
                  storeEnabled:
                    type: boolean
                  paymentGateway:
                    type: string
                    examples:
                      - zeleri
                  bannerText:
                    type:
                      - string
                      - "null"
                  maxBacWaterQuantity:
                    type: integer
                  bacWaterExtraPerVial:
                    type: integer
                  affiliatePointsPercent:
                    type: number
                  affiliateReferralDiscountPercent:
                    type: number
  /api/contact-config:
    get:
      tags:
        - store
      operationId: getContactConfig
      summary: Get the Turnstile site key for the contact form.
      responses:
        "200":
          description: The key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  siteKey:
                    type: string
  /api/merchant-feed:
    get:
      tags:
        - catalog
      operationId: getMerchantFeed
      summary: Get the product feed for Google Merchant Center.
      responses:
        "200":
          description: An RSS 2.0 feed with the Google Merchant namespace.
          content:
            application/xml:
              schema:
                type: string
  /api/guides/{slug}:
    get:
      tags:
        - content
      operationId: getGuidePdf
      summary: Download the PDF guide for one product.
      description: The guide gives the reconstitution volume and the dose table. The guide is the authority for those numbers.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
          examples:
            peptide:
              value: bpc-157-10mg
      responses:
        "200":
          description: The PDF.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "302":
          description: The object store did not answer. The response redirects to the CDN copy of the same PDF.
          headers:
            Location:
              schema:
                type: string
                format: uri
        "404":
          description: The product has no guide.
          content:
            text/plain:
              schema:
                type: string
  /api/shipit/rates:
    post:
      tags:
        - shipping
      operationId: quoteShipping
      summary: Quote the courier price to a Chilean commune.
      description: Returns one option for each available courier. PepChile ships only inside Chile.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - communeName
              properties:
                communeName:
                  type: string
                  examples:
                    - Providencia
                deliveryMode:
                  type: string
                  enum:
                    - home_delivery
                    - branch_pickup
                  default: home_delivery
                vials:
                  type: integer
                  minimum: 1
                  maximum: 500
                  default: 1
      responses:
        "200":
          description: The quote.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  deliveryMode:
                    type: string
                  destinyCommune:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type: string
                      regionId:
                        type:
                          - integer
                          - "null"
                  options:
                    type: array
                    items:
                      type: object
                      properties:
                        courier:
                          type: string
                          enum:
                            - starken
                            - bluexpress
                            - chilexpress
                        courierLabel:
                          type: string
                        priceClp:
                          type: integer
                        days:
                          type:
                            - integer
                            - "null"
        "400":
          description: The body is not valid.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Too many requests. The limit is 60 each minute for each IP address.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/shipit/resolve-commune:
    post:
      tags:
        - shipping
      operationId: resolveCommune
      summary: Find the courier id of a Chilean commune.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - communeName
              properties:
                communeName:
                  type: string
                  examples:
                    - Puente Alto
      responses:
        "200":
          description: The commune.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  commune:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type: string
                      regionId:
                        type:
                          - integer
                          - "null"
        "400":
          description: The body is not valid.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Too many requests. The limit is 60 each minute for each IP address.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/shipit/branch-offices:
    get:
      tags:
        - shipping
      operationId: listBranchOffices
      summary: List the courier branches in one commune.
      parameters:
        - name: courier
          in: query
          required: true
          schema:
            type: string
            enum:
              - starken
              - bluexpress
              - chilexpress
        - name: communeId
          in: query
          required: true
          schema:
            type: string
          examples:
            providencia:
              value: "317"
      responses:
        "200":
          description: The branches.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400":
          description: A parameter is absent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/mcp:
    post:
      tags:
        - agents
      operationId: callMcp
      summary: Call the Model Context Protocol server.
      description: Streamable HTTP transport, protocol version 2025-06-18. The server has no session. Send initialize first, then tools/list. It also answers resources/list, resources/read and prompts/list.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JsonRpcRequest"
      responses:
        "200":
          description: The JSON-RPC response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
        "202":
          description: A notification. The body is empty.
    get:
      tags:
        - agents
      operationId: describeMcp
      summary: Get the reason why GET is not available.
      description: The server sends no events, so it offers no event stream. Use POST.
      responses:
        "405":
          description: Method not allowed. The body names the server card.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/a2a:
    post:
      tags:
        - agents
      operationId: callA2a
      summary: Call the A2A agent.
      description: "Send message/send. Put the skill in a DataPart, for example {\"skill\": \"search_products\", \"query\": \"bpc-157\"}."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JsonRpcRequest"
      responses:
        "200":
          description: The JSON-RPC response with an A2A message.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
    get:
      tags:
        - agents
      operationId: describeA2a
      summary: Get the location of the agent card.
      responses:
        "200":
          description: The agent summary.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/cart/validate:
    post:
      tags:
        - orders
      operationId: validateCart
      summary: Check a cart against live stock and live prices.
      description: Use this before you show a total. It rejects a line that is out of stock and it corrects a price that changed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - items
              properties:
                items:
                  type: array
                  items:
                    type: object
                    required:
                      - productId
                      - quantity
                    properties:
                      productId:
                        type: string
                      quantity:
                        type: integer
                        minimum: 1
      responses:
        "200":
          description: The result of the check.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400":
          description: The body is not valid.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/checkout:
    post:
      tags:
        - orders
      operationId: createOrder
      summary: Create an order.
      description: An agent cannot complete a purchase. This endpoint needs a Supabase anonymous session, and the payment step needs a person with a card or a bank transfer. Give the customer the product URL instead.
      security:
        - supabaseAnonymousSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: The order.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "401":
          description: No session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/contact:
    post:
      tags:
        - store
      operationId: sendContactMessage
      summary: Send a message to the contact address.
      description: The request needs a Cloudflare Turnstile token. Get the site key from /api/contact-config.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: The message went out.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400":
          description: The body is not valid, or the token failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  securitySchemes:
    supabaseAnonymousSession:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: A Supabase anonymous session token. Only /api/checkout needs it. Every other endpoint is open.
  schemas:
    Product:
      type: object
      description: One product in the catalog.
      required:
        - id
        - name
        - price
        - inStock
        - category
      properties:
        id:
          type: string
          description: Database id. It can carry a dose suffix, for example "bpc-157-10mg".
          examples:
            - bpc-157-10mg
        name:
          type: string
          examples:
            - BPC-157 - 10mg
        dosage:
          type:
            - string
            - "null"
          examples:
            - 10mg
        useCase:
          type:
            - string
            - "null"
          description: The research goal of the product, in Spanish.
        price:
          type: integer
          description: Price in CLP. All prices are Chilean pesos (CLP). The price includes IVA.
          examples:
            - 80000
        priceFormatted:
          type:
            - string
            - "null"
          examples:
            - $80.000
        inStock:
          type: boolean
        stock:
          type:
            - integer
            - "null"
          description: Units available. The value is rounded, because exact inventory is not public.
        purity:
          type:
            - string
            - "null"
          examples:
            - ">99%"
        description:
          type:
            - string
            - "null"
        janoshikKey:
          type:
            - string
            - "null"
          description: Reference for the Janoshik certificate of analysis.
        coaImageUrl:
          type:
            - string
            - "null"
          format: uri
        category:
          type: string
          enum:
            - peptide
            - accessory
        benefits:
          type:
            - array
            - "null"
          items:
            type: string
        bestPriceReference:
          type:
            - object
            - "null"
          additionalProperties: true
        discountPercent:
          type: integer
        isDiscounted:
          type: boolean
        originalPrice:
          type:
            - integer
            - "null"
        displayOrder:
          type: integer
        imageUrl:
          type:
            - string
            - "null"
          format: uri
        guideUrl:
          type:
            - string
            - "null"
          format: uri
          description: PDF guide for the product.
        published:
          type: boolean
        unitsSold:
          type: integer
        createdAt:
          type:
            - string
            - "null"
          format: date-time
        updatedAt:
          type:
            - string
            - "null"
          format: date-time
    PeptideInfo:
      type: object
      properties:
        id:
          type: string
        slug:
          type: string
        name:
          type: string
        description:
          type: string
    Error:
      type: object
      properties:
        message:
          type: string
          description: The reason, in Spanish.
    AgentError:
      type: object
      properties:
        error:
          type: string
          enum:
            - not_found
            - method_not_allowed
        message:
          type: string
        catalog:
          type: string
          format: uri
        documentation:
          type: string
          format: uri
    JsonRpcRequest:
      type: object
      required:
        - jsonrpc
        - method
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          type:
            - string
            - integer
            - "null"
        method:
          type: string
        params:
          type: object
          additionalProperties: true
    JsonRpcResponse:
      type: object
      required:
        - jsonrpc
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          type:
            - string
            - integer
            - "null"
        result: {}
        error:
          type: object
          properties:
            code:
              type: integer
            message:
              type: string
  responses:
    NotFound:
      description: This origin serves no endpoint at this path.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/AgentError"
