surfingdog-ai Products API

Finding the products businesses list from their own inboxes, and who offers each barcode.

Operations 2

GET /v1/products Search the products members list #
GET /v1/products/gtin/{gtin}/offers Every business offering one barcode #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/surfingdog-ai:surfingdog-ai-products-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

surfingdog-ai-products-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Surfing Dog Search Products API
  version: '1'
  license:
    name: ODbL-1.0 (the directory data; town names and centres © GeoNames, CC BY 4.0)
    url: https://opendatacommons.org/licenses/odbl/1-0/
  summary: A public directory of businesses an AI can ask, book or buy from, through their own inboxes and other agent doors.
  description: 'Everything here is a public read, no sign-in, the same answer for everyone (a free directory key only lets a list page deeper than 200 places), except two calls about door reports: POST /v1/door-reports, where an agent says how a business''s door went, and GET /v1/door-reports/me, signed, which answers the signer''s own reporting credit. Signed reads may count against a contributor''s own allowance; the answers are the same. The directory''s order is published at GET /v1/ranking and cannot be bought; every query parameter only leaves businesses out. This API takes no orders or payments and books nothing itself; each result lists the business''s own doors (a listing''s doors, or a member''s protocols). A business this network found on its own website is marked as found, with the date it was checked; only facts it published itself are shown, never a phone or an email. Errors are RFC 9457 problem documents. The protocol behind this API, with its JSON Schemas and test vectors, is public; the MCP server at /mcp offers the same reads as tools, and can carry a person''s request to a business''s own inbox when the business allows it.'
servers:
- url: https://network.surfingdog.ai
tags:
- name: Products
  description: Finding the products businesses list from their own inboxes, and who offers each barcode.
paths:
  /v1/products:
    get:
      operationId: searchProducts
      tags:
      - Products
      summary: Search the products members list
      description: 'Products the businesses in the directory list from their own inboxes, as the search_products tool gives them (the same arguments, the same answer). Each product is one barcode, or one business''s own product, with its first offers. An offer is served only while its business is in the directory and answering, and its catalogue was confirmed in the last six hours (checked_at). The same barcode (GTIN-14, global or a book''s) is one product, a business once in it at its lowest price; a product with no barcode is its business''s own and is never matched to another''s. Offers come in the directory''s published order, each in its business''s place (GET /v1/ranking); order=price or order=nearest only when asked. Prices are never converted between currencies. Availability is in_stock, out_of_stock or on_sale, never a count. This API takes no orders or payments: read the price at the business''s own door (order.mcp or order.rest, with list_products), then order there.'
      parameters:
      - name: q
        in: query
        description: Words that must all be in the product's name, brand or description, ignoring case and accents.
        schema:
          type: string
          maxLength: 80
      - name: gtin
        in: query
        description: A barcode (GTIN, EAN, UPC or ISBN) in any length; it replaces q.
        schema:
          type: string
          maxLength: 40
      - name: brand
        in: query
        description: The maker's brand, with mpn.
        schema:
          type: string
          maxLength: 70
      - name: mpn
        in: query
        description: The maker's part number, with brand.
        schema:
          type: string
          maxLength: 70
      - name: domain
        in: query
        description: One business's products.
        schema:
          type: string
          maxLength: 253
      - $ref: '#/components/parameters/OfferNear'
      - $ref: '#/components/parameters/OfferRadius'
      - $ref: '#/components/parameters/OfferCountry'
      - $ref: '#/components/parameters/OfferShipsTo'
      - $ref: '#/components/parameters/OfferCurrency'
      - $ref: '#/components/parameters/OfferPriceMin'
      - $ref: '#/components/parameters/OfferPriceMax'
      - $ref: '#/components/parameters/OfferAvailability'
      - $ref: '#/components/parameters/OfferCondition'
      - $ref: '#/components/parameters/OfferHasInbox'
      - $ref: '#/components/parameters/OfferOrder'
      - $ref: '#/components/parameters/OfferLimit'
      - $ref: '#/components/parameters/OfferCursor'
      responses:
        '200':
          description: A page of products, each with its first offers, and the note that the door's price is the one that counts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductPage'
        '400':
          $ref: '#/components/responses/Problem'
        '410':
          $ref: '#/components/responses/Problem'
        '429':
          $ref: '#/components/responses/ReadLimited'
  /v1/products/gtin/{gtin}/offers:
    get:
      operationId: getOffers
      tags:
      - Products
      summary: Every business offering one barcode
      description: 'As the get_offers tool gives it: each business once, at its lowest price, in the directory''s published order unless order asks for another. When the offers under the barcode name more than one brand, an offer under a brand most of them do not name (and that is not its own business''s name) is left out. A serial''s barcode (an ISSN) is each business''s own: the answer has no offers and says to search by words.'
      parameters:
      - name: gtin
        in: path
        required: true
        description: The barcode, in any length.
        schema:
          type: string
          maxLength: 40
      - $ref: '#/components/parameters/OfferNear'
      - $ref: '#/components/parameters/OfferRadius'
      - $ref: '#/components/parameters/OfferCountry'
      - $ref: '#/components/parameters/OfferShipsTo'
      - $ref: '#/components/parameters/OfferCurrency'
      - $ref: '#/components/parameters/OfferPriceMin'
      - $ref: '#/components/parameters/OfferPriceMax'
      - $ref: '#/components/parameters/OfferAvailability'
      - $ref: '#/components/parameters/OfferCondition'
      - $ref: '#/components/parameters/OfferHasInbox'
      - $ref: '#/components/parameters/OfferOrder'
      - $ref: '#/components/parameters/OfferLimit'
      - $ref: '#/components/parameters/OfferCursor'
      responses:
        '200':
          description: The barcode, how many businesses offer it, and a page of their offers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferPage'
        '400':
          $ref: '#/components/responses/Problem'
        '410':
          $ref: '#/components/responses/Problem'
        '429':
          $ref: '#/components/responses/ReadLimited'
components:
  responses:
    ReadLimited:
      description: 'rate_limited: over 600 requests a minute from one address, in bursts of 200 (/mcp and /a2a together; the directory''s lists apart); a contributor reads on its own allowance first (/report.md). key_allowance_used: a directory key''s places for today are used; it starts again at 00:00 UTC. Try again after Retry-After seconds.'
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Problem:
      description: An RFC 9457 problem document.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  parameters:
    OfferHasInbox:
      name: has_inbox
      in: query
      description: 'Every offer comes from a business''s own inbox: false leaves nothing.'
      schema:
        type: boolean
    OfferPriceMin:
      name: price_min
      in: query
      description: At least this, in the currency's minor unit; needs currency.
      schema:
        type: integer
        minimum: 0
    OfferCurrency:
      name: currency
      in: query
      description: Only offers in this currency (ISO 4217); prices are never converted.
      schema:
        type: string
        pattern: ^[A-Za-z]{3}$
    OfferCursor:
      name: cursor
      in: query
      description: next_cursor from the page before, with the same parameters.
      schema:
        type: string
        maxLength: 512
    OfferRadius:
      name: radius_km
      in: query
      description: 'With near: 10 when left out, at most 1000.'
      schema:
        type: number
        exclusiveMinimum: 0
        maximum: 1000
    OfferLimit:
      name: limit
      in: query
      description: Up to 20; 10 when left out.
      schema:
        type: integer
        minimum: 1
        maximum: 20
    OfferCondition:
      name: condition
      in: query
      description: 'Comma-separated: new, used, refurbished. Any when left out.'
      schema:
        type: string
    OfferCountry:
      name: country
      in: query
      description: Only businesses in this country (ISO 3166-1 alpha-2).
      schema:
        type: string
        pattern: ^[A-Za-z]{2}$
    OfferOrder:
      name: order
      in: query
      description: published (the default), price (needs currency) or nearest (needs near).
      schema:
        type: string
        enum:
        - published
        - price
        - nearest
    OfferNear:
      name: near
      in: query
      description: 'lat,lng: only businesses within radius_km of it, each with its distance.'
      schema:
        type: string
    OfferShipsTo:
      name: ships_to
      in: query
      description: Only businesses that say they deliver to this country.
      schema:
        type: string
        pattern: ^[A-Za-z]{2}$
    OfferAvailability:
      name: availability
      in: query
      description: 'Comma-separated: in_stock, out_of_stock, on_sale. in_stock and on_sale when left out.'
      schema:
        type: string
    OfferPriceMax:
      name: price_max
      in: query
      description: At most this, in the currency's minor unit; needs currency.
      schema:
        type: integer
        minimum: 0
  schemas:
    OfferPage:
      type: object
      required:
      - product
      - offers
      - next_cursor
      - order
      - rules
      - note
      properties:
        product:
          type: object
          description: key, gtin, kind (global, book, serial) and how many businesses offer it.
        offers:
          type: array
          items:
            $ref: '#/components/schemas/Offer'
        next_cursor:
          type:
          - string
          - 'null'
        order:
          type: string
        rules:
          type: object
        note:
          type: string
    Offer:
      type: object
      description: One business's offer of a product, as search_products and get_offers give it (packages/spec/schemas/mcp-search-products-output.json).
      required:
      - id
      - offered_by
      - name
      - gtin
      - price
      - tax
      - availability
      - condition
      - where
      - checked_at
      - order
      properties:
        id:
          type: string
        offered_by:
          type: object
          description: 'The business: name, domain, locality, country, distance_km near a point, standing in words, has_inbox.'
        name:
          type: string
          description: As the business wrote it.
        brand:
          type: string
        gtin:
          type:
          - string
          - 'null'
          description: 14 digits, or null.
        price:
          type: object
          description: value in the currency's minor unit, and currency (ISO 4217), as this network last checked it.
        tax:
          type: string
          enum:
          - included
          - excluded
          - unknown
        availability:
          type: string
          enum:
          - in_stock
          - out_of_stock
          - on_sale
        condition:
          type: string
          enum:
          - new
          - used
          - refurbished
        where:
          type: object
          description: 'pickup, and ships_to: the countries the business says it delivers to.'
        url:
          type: string
          format: uri
        image_url:
          type: string
          format: uri
        checked_at:
          type: string
          format: date-time
        order:
          type: object
          description: The business's own door (mcp, rest), the product_id there, read_first list_products, then create_order.
    Problem:
      type: object
      required:
      - type
      - title
      - status
      properties:
        type:
          type: string
        title:
          type: string
        status:
          type: integer
        code:
          type: string
          description: 'What to do differently, when a status alone does not say: cursor_expired, not_found, too_large, rate_limited, category_unresolved, page_too_deep.'
        detail:
          type: string
        instance:
          type: string
        candidates:
          type: array
          items:
            type: string
          description: 'category_unresolved: up to 5 categories to try.'
    ProductPage:
      type: object
      required:
      - products
      - next_cursor
      - order
      - rules
      - note
      properties:
        products:
          type: array
          items:
            type: object
            description: key, gtin, name, own, offers_count, and up to 3 offers.
            properties:
              offers:
                type: array
                items:
                  $ref: '#/components/schemas/Offer'
        next_cursor:
          type:
          - string
          - 'null'
        order:
          type: string
        rules:
          type: object
        note:
          type: string
externalDocs:
  description: The network protocol
  url: https://github.com/surfingdogai/inbox/blob/main/docs/protocol/network.md