{
  "components": {
    "schemas": {
      "AddInventoryProductInput": {
        "additionalProperties": false,
        "properties": {
          "brand": {
            "description": "optional brand",
            "type": "string"
          },
          "category": {
            "description": "optional category",
            "type": "string"
          },
          "name": {
            "description": "product name",
            "type": "string"
          },
          "sku": {
            "description": "optional SKU or UPC",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "AddWatchPatternInput": {
        "additionalProperties": false,
        "properties": {
          "pattern": {
            "description": "websearch pattern: unquoted words are AND, OR is or, -term excludes, quoted phrases match as a unit",
            "type": "string"
          },
          "weight": {
            "description": "optional weight 0-8, default 4",
            "maximum": 8,
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "pattern"
        ],
        "type": "object"
      },
      "CheckTrackedProductsInput": {
        "additionalProperties": false,
        "properties": {
          "limit": {
            "default": 3,
            "description": "maximum matches to return",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "offset": {
            "description": "number of matches to skip",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "CreateAPIKeyInput": {
        "additionalProperties": false,
        "properties": {
          "kind": {
            "description": "optional kind: user or agent, default agent",
            "enum": [
              "user",
              "agent"
            ],
            "type": "string"
          },
          "name": {
            "description": "optional key name",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ExtractedData": {
        "properties": {
          "contact": {
            "properties": {
              "emails": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "phones": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "websites": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "establishments": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "images": {
            "description": "Public HTTPS URLs of recalled-product photos hosted by Recall Kitchen",
            "items": {
              "format": "uri",
              "type": "string"
            },
            "type": "array"
          },
          "locations": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "products": {
            "items": {
              "properties": {
                "lotCodes": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "modelNumbers": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "packaging": {
                  "type": "string"
                },
                "productName": {
                  "type": "string"
                },
                "upcs": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "useByDates": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "stores": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "GetProductRecallInput": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "description": "alias for recall_id",
            "type": "string"
          },
          "recall_id": {
            "description": "recall id returned by search tools",
            "type": "string"
          }
        },
        "type": "object"
      },
      "HTTPRecall": {
        "properties": {
          "description": {
            "type": "string"
          },
          "extractedData": {
            "$ref": "#/components/schemas/ExtractedData"
          },
          "id": {
            "type": "string"
          },
          "publishedOn": {
            "format": "date-time",
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "source": {
            "enum": [
              "cpsc",
              "fdafoodsafety",
              "FDAMedWatch",
              "usda",
              "nhtsa",
              "canada",
              "costco",
              "target",
              "walmart",
              "openfda"
            ],
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "ListInventoryInput": {
        "additionalProperties": false,
        "properties": {
          "limit": {
            "default": 3,
            "description": "number of products to return",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "offset": {
            "description": "number of products to skip",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "query": {
            "description": "optional inventory search",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ListRecallNotificationsInput": {
        "additionalProperties": false,
        "properties": {
          "limit": {
            "default": 3,
            "description": "number of notifications to return",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "offset": {
            "description": "number of notifications to skip",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "unread": {
            "description": "if true, only unread notifications; if false, include read. Default: true (unread only)",
            "type": [
              "null",
              "boolean"
            ]
          }
        },
        "type": "object"
      },
      "LookupProductInput": {
        "additionalProperties": false,
        "properties": {
          "upc": {
            "description": "UPC or EAN barcode digits",
            "type": "string"
          }
        },
        "required": [
          "upc"
        ],
        "type": "object"
      },
      "MCPJSONRPCRequest": {
        "properties": {
          "id": {
            "description": "JSON-RPC id",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "jsonrpc": {
            "enum": [
              "2.0"
            ],
            "type": "string"
          },
          "method": {
            "enum": [
              "tools/call",
              "initialize",
              "tools/list",
              "ping",
              "resources/list",
              "resources/read",
              "prompts/list",
              "prompts/get"
            ],
            "type": "string"
          },
          "params": {
            "properties": {
              "arguments": {
                "additionalProperties": true,
                "description": "Tool arguments. Shape depends on name; see components.schemas.*Input.",
                "type": "object"
              },
              "name": {
                "description": "MCP tool name (tools/call)",
                "enum": [
                  "search_product_recalls",
                  "search_product_recalls_by_upc",
                  "search_product_recalls_from_image",
                  "get_product_recall",
                  "search_recalls_by_identifier",
                  "lookup_product",
                  "signup",
                  "create_api_key",
                  "list_api_keys",
                  "revoke_api_key",
                  "check_tracked_products",
                  "list_watch_patterns",
                  "add_watch_pattern",
                  "remove_watch_pattern",
                  "list_inventory",
                  "add_inventory_product",
                  "remove_inventory_product",
                  "list_recall_notifications",
                  "mark_notification_read"
                ],
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "MCPJSONRPCResponse": {
        "properties": {
          "error": {
            "type": "object"
          },
          "id": {},
          "jsonrpc": {
            "type": "string"
          },
          "result": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "MarkNotificationReadInput": {
        "additionalProperties": false,
        "properties": {
          "read": {
            "description": "if true, mark read; if false, mark unread. Default true",
            "type": [
              "null",
              "boolean"
            ]
          },
          "recall_id": {
            "description": "recall id from list_recall_notifications",
            "type": "string"
          }
        },
        "required": [
          "recall_id"
        ],
        "type": "object"
      },
      "PaymentRequired": {
        "description": "x402 payment challenge. Runtime accepts[].amount is USDC atomic units (6 decimals), not decimal dollars.",
        "type": "object"
      },
      "PublicRecallResponse": {
        "properties": {
          "extracted": {
            "$ref": "#/components/schemas/ExtractedData"
          },
          "recall": {
            "$ref": "#/components/schemas/HTTPRecall"
          }
        },
        "type": "object"
      },
      "RemoveInventoryProductInput": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "description": "inventory product id from list_inventory",
            "type": "integer"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "RemoveWatchPatternInput": {
        "additionalProperties": false,
        "properties": {
          "pattern": {
            "description": "watch pattern to remove",
            "type": "string"
          }
        },
        "required": [
          "pattern"
        ],
        "type": "object"
      },
      "RevokeAPIKeyInput": {
        "additionalProperties": false,
        "properties": {
          "key_id": {
            "description": "key_id from list_api_keys",
            "type": "string"
          }
        },
        "required": [
          "key_id"
        ],
        "type": "object"
      },
      "ScanEvent": {
        "properties": {
          "data": {
            "additionalProperties": true,
            "type": "object"
          },
          "event": {
            "enum": [
              "scan.started",
              "scan.progress",
              "scan.need_closer_shot",
              "scan.error",
              "scan.done",
              "item.opened",
              "item.upc",
              "item.text",
              "item.fields",
              "item.match",
              "item.product",
              "item.recalls",
              "item.replaced_by",
              "item.closed"
            ],
            "type": "string"
          },
          "item_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "scan_id": {
            "type": "string"
          },
          "seq": {
            "type": "integer"
          },
          "ts": {
            "format": "date-time",
            "type": "string"
          },
          "v": {
            "const": 1,
            "type": "integer"
          }
        },
        "required": [
          "v",
          "scan_id",
          "ts",
          "seq",
          "event",
          "data"
        ],
        "type": "object"
      },
      "ScanStreamRequest": {
        "properties": {
          "image_url": {
            "description": "HTTPS URL or data:image/...;base64 URI. Same 8 MiB / MIME allow-list as MCP image tools.",
            "type": "string"
          },
          "include_category_matches": {
            "default": false,
            "type": "boolean"
          },
          "url": {
            "description": "alias for image_url",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SearchProductRecallsByUPCInput": {
        "additionalProperties": false,
        "properties": {
          "barcode": {
            "description": "alias for upc",
            "type": "string"
          },
          "image_url": {
            "description": "optional HTTPS URL or data:image/...;base64 URI of a barcode image",
            "type": "string"
          },
          "limit": {
            "default": 3,
            "description": "number of recalls to return, 1-100, default 3",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "offset": {
            "description": "number of recalls to skip",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "upc": {
            "description": "UPC or EAN barcode digits",
            "type": "string"
          },
          "url": {
            "description": "alias for image_url",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SearchProductRecallsFromImageInput": {
        "additionalProperties": false,
        "properties": {
          "image_url": {
            "description": "HTTPS URL or data:image/...;base64 URI of a photo containing products. Local file paths are not supported.",
            "type": "string"
          },
          "include_category_matches": {
            "description": "if true, attach keyword recalls for generic items such as cups; default false",
            "type": "boolean"
          },
          "limit": {
            "default": 3,
            "description": "number of recalls to return per product, 1-100, default 3",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "url": {
            "description": "alias for image_url",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SearchProductRecallsInput": {
        "additionalProperties": false,
        "properties": {
          "limit": {
            "default": 3,
            "description": "number of recalls to return, 1-100, default 3",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "location": {
            "description": "optional country, region, or place; ANDed with the query. USA, Canada, Mexico, EU, and Europe match places in that region. CA is California, not Canada.",
            "type": "string"
          },
          "offset": {
            "description": "number of recalls to skip",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "query": {
            "description": "websearch query: unquoted words are AND, OR is or, -term excludes, quoted phrases match as a unit. Example: Generac Generator -Portable",
            "type": "string"
          },
          "since": {
            "description": "optional published-on start date YYYY-MM-DD",
            "type": "string"
          },
          "source": {
            "description": "optional source filter: cpsc, fdafoodsafety, FDAMedWatch, usda, nhtsa, canada, costco, target, walmart, or openfda. Aliases: FDA and food map to fdafoodsafety; medwatch maps to FDAMedWatch; vehicle, vin, and car map to nhtsa; healthcanada, cfia, and hc-sc map to canada; sams maps to walmart; enforcement, res, and fdaenforcement map to openfda. For more than one source, pass sources.",
            "enum": [
              "cpsc",
              "fdafoodsafety",
              "FDAMedWatch",
              "usda",
              "nhtsa",
              "canada",
              "costco",
              "target",
              "walmart",
              "openfda"
            ],
            "type": "string"
          },
          "sources": {
            "description": "optional sources to include (OR). Same values as source",
            "items": {
              "enum": [
                "cpsc",
                "fdafoodsafety",
                "FDAMedWatch",
                "usda",
                "nhtsa",
                "canada",
                "costco",
                "target",
                "walmart",
                "openfda"
              ],
              "type": "string"
            },
            "type": "array",
            "uniqueItems": true
          },
          "until": {
            "description": "optional published-on end date YYYY-MM-DD",
            "type": "string"
          },
          "years": {
            "description": "optional published window in years (1-50). Ignored when since is set",
            "maximum": 50,
            "minimum": 1,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "SearchRecallsByIdentifierInput": {
        "additionalProperties": false,
        "properties": {
          "limit": {
            "default": 3,
            "description": "number of recalls to return",
            "maximum": 100,
            "minimum": 1,
            "type": "integer"
          },
          "lot_code": {
            "description": "lot or batch code as stored on the recall",
            "type": "string"
          },
          "model_number": {
            "description": "model number as stored on the recall",
            "type": "string"
          },
          "offset": {
            "description": "number of recalls to skip",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "product_name": {
            "description": "extracted product name (exact match)",
            "type": "string"
          },
          "upc": {
            "description": "product UPC or EAN as stored on the recall",
            "type": "string"
          },
          "vin": {
            "description": "17-character vehicle identification number. Decoded locally to year and make, then matched against NHTSA campaigns. Does not call NHTSA live.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SearchSourcesResponse": {
        "properties": {
          "recalls": {
            "items": {
              "$ref": "#/components/schemas/HTTPRecall"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SignupInput": {
        "additionalProperties": false,
        "properties": {
          "email": {
            "description": "email address for the new account",
            "type": "string"
          },
          "name": {
            "description": "optional display name",
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyHeader": {
        "description": "API key as X-API-Key: rk_.... Required for Grok. Same keys as Bearer.",
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      },
      "BearerAuth": {
        "bearerFormat": "rk",
        "description": "API key as Authorization: Bearer rk_.... Claude and Cursor can use this. Grok must not: its HTTP MCP client treats Bearer as OAuth.",
        "scheme": "bearer",
        "type": "http"
      },
      "cookieAuth": {
        "description": "Logged-in app session cookie. Required for POST /api/scan/stream.",
        "in": "cookie",
        "name": "user-session",
        "type": "apiKey"
      }
    }
  },
  "externalDocs": {
    "description": "Developer docs",
    "url": "https://recallkitchen.com/docs/"
  },
  "info": {
    "contact": {
      "email": "support@recallkitchen.com",
      "name": "Recall Kitchen",
      "url": "https://recallkitchen.com"
    },
    "description": "Food, product, and NHTSA vehicle recall search for U.S. CPSC, FDA food, FDA MedWatch, USDA, and NHTSA notices.",
    "title": "Recall Kitchen",
    "version": "1.0.0",
    "x-guidance": "Prefer an API key (X-API-Key: rk_...; Claude/Cursor may send Authorization: Bearer rk_...). Without a key, paid search is x402 USDC on Base at $0.025 per call. MCP is POST https://app.recallkitchen.com/mcp (streamable-http JSON-RPC). Grok clients must use X-API-Key, not Bearer OAuth. signup is free and does not require a key or x402. A few anonymous MCP search calls per IP are free; image search is not. Docs: https://recallkitchen.com/docs/"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/recalls/{recall_id}": {
      "get": {
        "description": "Full recall JSON including extracted lots, UPCs, models, locations, and contacts. No authentication. HTML permalink: https://app.recallkitchen.com/r/{recall_id}.",
        "operationId": "getRecall",
        "parameters": [
          {
            "description": "Recall id returned by search",
            "in": "path",
            "name": "recall_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRecallResponse"
                }
              }
            },
            "description": "Recall and extracted data"
          },
          "404": {
            "description": "Unknown recall id"
          }
        },
        "security": [],
        "summary": "Get a public recall by id",
        "tags": [
          "Recalls"
        ]
      }
    },
    "/api/scan/stream": {
      "post": {
        "description": "Session-authenticated. Accepts multipart field image (or file), or JSON {image_url, include_category_matches}. Returns application/x-ndjson: one event envelope per line (scan.started, item.upc, item.fields, item.product, item.recalls, scan.done). MCP search_product_recalls_from_image runs the same pipeline but returns the final fused JSON.",
        "operationId": "scanStream",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScanStreamRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "file": {
                    "description": "alias for image",
                    "format": "binary",
                    "type": "string"
                  },
                  "image": {
                    "format": "binary",
                    "type": "string"
                  },
                  "include_category_matches": {
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/ScanEvent"
                }
              }
            },
            "description": "NDJSON event stream"
          },
          "400": {
            "description": "Invalid image"
          },
          "403": {
            "description": "Sign-in required"
          }
        },
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "summary": "Stream a product-image scan",
        "tags": [
          "Scan"
        ]
      }
    },
    "/api/sources": {
      "get": {
        "description": "Keyword search of indexed CPSC, FDA, USDA, and NHTSA recalls. Query uses websearch syntax: unquoted words are AND, OR is or, -term excludes, quoted phrases match as a unit. Send an API key, a logged-in session, or pay x402 USDC on Base ($0.025).",
        "operationId": "searchSources",
        "parameters": [
          {
            "description": "Search query (websearch syntax)",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional source filter. Repeat or comma-separate to include multiple: cpsc, fdafoodsafety, FDAMedWatch, usda, nhtsa, canada, costco, target, walmart, openfda",
            "in": "query",
            "name": "source",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional rolling published window in years (1–50). Ignored when since is set. The app sends since instead.",
            "in": "query",
            "name": "years",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Optional published-on start date YYYY-MM-DD. The Recalls page computes this from Last year / 3 years / 5 years.",
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional published-on end date YYYY-MM-DD",
            "in": "query",
            "name": "until",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional country or place. USA, Canada, Mexico, EU, and Europe match that region. CA is California, not Canada.",
            "in": "query",
            "name": "location",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum recalls to return",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Number of recalls to skip",
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchSourcesResponse"
                }
              }
            },
            "description": "Matching recalls"
          },
          "401": {
            "description": "Invalid API key"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequired"
                }
              }
            },
            "description": "Payment required. Pay x402 USDC on Base ($0.025 per call) or send an API key."
          },
          "403": {
            "description": "Authentication required and x402 is not enabled"
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "BearerAuth": []
          },
          {}
        ],
        "summary": "Search product recalls",
        "tags": [
          "Search"
        ],
        "x-payment-info": {
          "price": {
            "amount": "0.025",
            "currency": "USD",
            "mode": "fixed"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        }
      }
    },
    "/mcp": {
      "post": {
        "description": "Streamable-http MCP JSON-RPC at /mcp. Protocol methods (initialize, tools/list, ping) do not require a key or payment.\n\nPublic tools (API key, a few anonymous calls per IP, or x402 after the quota): search_product_recalls, search_recalls_by_identifier, search_product_recalls_by_upc, lookup_product, get_product_recall. search_product_recalls_from_image needs a key or x402 (not in the anonymous quota).\n\nsignup is free (email, no key, no x402) and returns an API key once.\n\nAccount tools require an API key: create_api_key, list_api_keys, revoke_api_key, check_tracked_products, list_watch_patterns, add_watch_pattern, remove_watch_pattern, list_inventory, add_inventory_product, remove_inventory_product, list_recall_notifications, mark_notification_read.\n\nUnauthenticated probes with an empty or non-JSON-RPC body receive HTTP 402 when x402 is enabled.",
        "operationId": "mcpToolsCall",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MCPJSONRPCRequest"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MCPJSONRPCResponse"
                }
              }
            },
            "description": "JSON-RPC result (or SSE if Accept includes text/event-stream)"
          },
          "401": {
            "description": "Invalid API key"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequired"
                }
              }
            },
            "description": "Payment required. Pay x402 USDC on Base ($0.025 per call) or send an API key."
          },
          "429": {
            "description": "Rate limited (anonymous IP quota or account limits)"
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "BearerAuth": []
          },
          {}
        ],
        "summary": "Call an MCP tool (JSON-RPC 2.0)",
        "tags": [
          "MCP"
        ],
        "x-payment-info": {
          "price": {
            "amount": "0.025",
            "currency": "USD",
            "mode": "fixed"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        }
      }
    }
  },
  "servers": [
    {
      "description": "Production",
      "url": "https://app.recallkitchen.com"
    }
  ],
  "tags": [
    {
      "description": "HTTP recall search",
      "name": "Search"
    },
    {
      "description": "Public recall notices",
      "name": "Recalls"
    },
    {
      "description": "Session product-image scan stream",
      "name": "Scan"
    },
    {
      "description": "Model Context Protocol tools over streamable-http",
      "name": "MCP"
    }
  ]
}
