Every API here is available over the APIs.io API and to AI agents over MCP.
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