{
  "openapi": "3.1.0",
  "info": {
    "title": "Dynaparts API",
    "version": "1.0.0",
    "summary": "Public read-only API of Dynaparts, a French online store for performance car parts.",
    "description": "Search the Dynaparts catalog (about 27,000 performance and motorsport car parts: exhausts, intakes, suspension, EGR delete kits, engine parts, oils) and read prices in euros (VAT included, France), availability and estimated delivery dates in mainland France.\n\nWhen to use: an end user looks for a performance part for a vehicle or engine, or by manufacturer reference (Milltek, Scorpion, Forge Motorsport, APR, Racingline, ST Suspensions, Eibach, BMC, Dynaparts DP-...), and needs a price, stock status or delivery date. Recommended flow: call searchProducts, then getProduct, then give the user the product `url` (ordering and payment happen on the website; this API never places orders).\n\nNo authentication. Rate limit: 60 requests per 60 seconds per client IP address, reported in every response by the RateLimit-Policy and RateLimit fields (draft-ietf-httpapi-ratelimit-headers) and by RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; above the limit the API answers 429 with Retry-After. Errors use application/problem+json (RFC 9457) with a stable `code` and a `hint` explaining how to fix the request.",
    "termsOfService": "https://dynaparts.fr/fr/content/3-conditions-generales-de-vente",
    "contact": {
      "name": "Dynaparts support",
      "email": "support@dynaparts.fr",
      "url": "https://dynaparts.fr/fr/developers"
    }
  },
  "externalDocs": {
    "description": "Developer and AI agent documentation",
    "url": "https://dynaparts.fr/fr/developers"
  },
  "servers": [
    {
      "url": "https://dynaparts.fr/api/v1",
      "description": "Production"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Products",
      "description": "Catalog search and product details."
    },
    {
      "name": "Meta",
      "description": "API index and links."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getApiIndex",
        "tags": ["Meta"],
        "summary": "Get the API index",
        "description": "Returns the API name and version, the available endpoints with examples, the rate limit, and links to this OpenAPI description and to the documentation.",
        "responses": {
          "200": {
            "description": "API index.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiIndex" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/products/search": {
      "get": {
        "operationId": "searchProducts",
        "tags": ["Products"],
        "summary": "Search parts",
        "description": "Full-text search over the public catalog, with the same ranking as the website search: exact manufacturer or Dynaparts references first, then names, vehicles and engine codes. Returns one page of short product records (id, reference, name, brand, URL, image, price, availability). Use getProduct for the full record. Typo corrections are reported in corrected_query.",
        "parameters": [
          { "$ref": "#/components/parameters/Query" },
          { "$ref": "#/components/parameters/Lang" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Page" }
        ],
        "responses": {
          "200": {
            "description": "One page of search results (possibly empty).",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/products/{id}": {
      "get": {
        "operationId": "getProduct",
        "tags": ["Products"],
        "summary": "Get a product",
        "description": "Returns one public product: price, availability, estimated delivery date, shipping to mainland France, return policy, plain-text description, images, GTIN/MPN, default category, customer rating and the schema.org Product object published on the product page.",
        "parameters": [
          { "$ref": "#/components/parameters/ProductId" },
          { "$ref": "#/components/parameters/Lang" }
        ],
        "responses": {
          "200": {
            "description": "Product details.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProductDetail" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Query": {
        "name": "q",
        "in": "query",
        "required": true,
        "description": "Search text: part name, vehicle, engine code or manufacturer reference (2 to 100 characters).",
        "schema": { "type": "string", "minLength": 2, "maxLength": 100 },
        "examples": {
          "vehicle": { "summary": "Part type and vehicle", "value": "downpipe golf 7" },
          "reference": { "summary": "Manufacturer reference", "value": "SSXAU200" }
        }
      },
      "Lang": {
        "name": "lang",
        "in": "query",
        "required": false,
        "description": "Language of product names, descriptions and URLs.",
        "schema": { "type": "string", "enum": ["fr", "en", "es", "it", "de"], "default": "fr" }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Number of results per page.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "Result page, starting at 1. links.next gives the next page when there is one.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 20, "default": 1 }
      },
      "ProductId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Product id, as returned by searchProducts (results[].id).",
        "schema": { "type": "integer", "minimum": 1 },
        "example": 166784
      }
    },
    "headers": {
      "RateLimit-Policy": {
        "description": "Quota policy (draft-ietf-httpapi-ratelimit-headers): policy name, quota q and window w in seconds, e.g. \"api\";q=60;w=60.",
        "schema": { "type": "string" }
      },
      "RateLimit": {
        "description": "Current state (draft-ietf-httpapi-ratelimit-headers): remaining requests r and seconds t until the quota resets, e.g. \"api\";r=59;t=42.",
        "schema": { "type": "string" }
      },
      "RateLimit-Limit": {
        "description": "Requests allowed per window (older draft form).",
        "schema": { "type": "integer" }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current window (older draft form).",
        "schema": { "type": "integer" }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the window resets (older draft form).",
        "schema": { "type": "integer" }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (RFC 9110).",
        "schema": { "type": "integer" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Missing or invalid parameter (code missing_parameter or invalid_parameter); invalid_params lists each problem.",
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "NotFound": {
        "description": "Unknown endpoint (code not_found) or product missing, removed or not public (code product_not_found).",
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded (code rate_limited). Wait Retry-After seconds.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error (code internal_error).",
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Search engine temporarily unavailable (code search_unavailable). Wait Retry-After seconds.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" }
        },
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      }
    },
    "schemas": {
      "Money": {
        "type": "object",
        "description": "Amount as a decimal string with two decimals, and its ISO 4217 currency.",
        "required": ["amount", "currency"],
        "properties": {
          "amount": { "type": "string", "pattern": "^[0-9]+\\.[0-9]{2}$", "examples": ["12.90"] },
          "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "examples": ["EUR"] }
        }
      },
      "Price": {
        "type": "object",
        "description": "Price for a signed-out visitor in France.",
        "required": ["amount", "regular_amount", "currency", "tax_included"],
        "properties": {
          "amount": { "type": "string", "pattern": "^[0-9]+\\.[0-9]{2}$", "description": "Price to pay.", "examples": ["1101.28"] },
          "regular_amount": { "type": ["string", "null"], "pattern": "^[0-9]+\\.[0-9]{2}$", "description": "Price before discount, only when a discount applies.", "examples": ["1295.63"] },
          "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "examples": ["EUR"] },
          "tax_included": { "type": "boolean", "description": "True when amounts include VAT (French VAT, 20 %)." }
        }
      },
      "Availability": {
        "type": "object",
        "required": ["status", "available_from", "estimated_delivery_date"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["InStock", "BackOrder", "OutOfStock"],
            "description": "InStock: in stock, ships within 0 to 1 business day. BackOrder: can be ordered, being restocked. OutOfStock: cannot be ordered."
          },
          "available_from": { "type": ["string", "null"], "format": "date", "description": "Expected shipping date for BackOrder." },
          "estimated_delivery_date": { "type": ["string", "null"], "format": "date", "description": "Estimated delivery date in mainland France with standard delivery, as shown on the product page." }
        }
      },
      "ProductSummary": {
        "type": "object",
        "required": ["id", "reference", "name", "brand", "url", "image", "price", "availability"],
        "properties": {
          "id": { "type": "integer", "minimum": 1, "examples": [166784] },
          "reference": { "type": ["string", "null"], "description": "Manufacturer reference (MPN) or Dynaparts reference.", "examples": ["SSXAU200"] },
          "name": { "type": "string" },
          "brand": { "type": ["string", "null"], "examples": ["Milltek"] },
          "url": { "type": "string", "format": "uri", "description": "Product page, where the user can order." },
          "image": { "type": ["string", "null"], "format": "uri" },
          "price": {
            "description": "Null when prices are hidden (catalog mode).",
            "oneOf": [
              { "$ref": "#/components/schemas/Price" },
              { "type": "null" }
            ]
          },
          "availability": { "$ref": "#/components/schemas/Availability" }
        }
      },
      "Category": {
        "type": "object",
        "required": ["id", "name", "url"],
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" }
        }
      },
      "Shipping": {
        "type": "object",
        "description": "Cheapest delivery to mainland France for this product alone.",
        "required": ["country", "rate", "handling_days", "transit_days"],
        "properties": {
          "country": { "type": "string", "const": "FR" },
          "rate": { "$ref": "#/components/schemas/Money" },
          "handling_days": {
            "description": "Business days before shipping (null when out of stock).",
            "oneOf": [{ "$ref": "#/components/schemas/DayRange" }, { "type": "null" }]
          },
          "transit_days": {
            "description": "Business days in transit (null when out of stock).",
            "oneOf": [{ "$ref": "#/components/schemas/DayRange" }, { "type": "null" }]
          }
        }
      },
      "DayRange": {
        "type": "object",
        "required": ["min", "max"],
        "properties": {
          "min": { "type": "integer", "minimum": 0 },
          "max": { "type": "integer", "minimum": 0 }
        }
      },
      "ReturnPolicy": {
        "type": "object",
        "required": ["days", "method", "fees"],
        "properties": {
          "days": { "type": "integer", "description": "Withdrawal period in days after delivery.", "examples": [14] },
          "method": { "type": "string", "enum": ["by_mail"] },
          "fees": { "type": "string", "enum": ["customer"], "description": "Return shipping paid by the customer." }
        }
      },
      "Rating": {
        "type": "object",
        "required": ["value", "count"],
        "properties": {
          "value": { "type": "number", "minimum": 0, "maximum": 5 },
          "count": { "type": "integer", "minimum": 1 }
        }
      },
      "ProductDetail": {
        "description": "Product summary plus details.",
        "allOf": [
          { "$ref": "#/components/schemas/ProductSummary" },
          {
            "type": "object",
            "required": ["lang", "description", "images", "gtin", "mpn", "category", "shipping", "return_policy", "rating", "schema_org"],
            "properties": {
              "lang": { "type": "string", "enum": ["fr", "en", "es", "it", "de"] },
              "description": { "type": "string", "description": "Plain-text description." },
              "images": { "type": "array", "items": { "type": "string", "format": "uri" } },
              "gtin": { "type": ["string", "null"], "description": "Valid EAN/GTIN, when known." },
              "mpn": { "type": ["string", "null"] },
              "category": { "oneOf": [{ "$ref": "#/components/schemas/Category" }, { "type": "null" }] },
              "shipping": { "oneOf": [{ "$ref": "#/components/schemas/Shipping" }, { "type": "null" }] },
              "return_policy": { "oneOf": [{ "$ref": "#/components/schemas/ReturnPolicy" }, { "type": "null" }] },
              "rating": { "oneOf": [{ "$ref": "#/components/schemas/Rating" }, { "type": "null" }] },
              "schema_org": { "type": ["object", "null"], "description": "schema.org Product object, identical to the JSON-LD of the product page." }
            }
          }
        ]
      },
      "SearchResult": {
        "type": "object",
        "required": ["query", "corrected_query", "lang", "page", "limit", "total", "results", "links"],
        "properties": {
          "query": { "type": "string" },
          "corrected_query": { "type": ["string", "null"], "description": "Query actually searched after a typo correction, when one was applied." },
          "lang": { "type": "string", "enum": ["fr", "en", "es", "it", "de"] },
          "page": { "type": "integer", "minimum": 1 },
          "limit": { "type": "integer", "minimum": 1, "maximum": 50 },
          "total": { "type": "integer", "minimum": 0, "description": "Number of matching products (capped at 3000)." },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/ProductSummary" } },
          "links": {
            "type": "object",
            "required": ["self", "next", "html"],
            "properties": {
              "self": { "type": "string", "format": "uri" },
              "next": { "type": ["string", "null"], "format": "uri" },
              "html": { "type": "string", "format": "uri", "description": "Same search on the website." }
            }
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "required": ["name", "version", "description", "documentation", "openapi", "authentication", "rate_limit", "endpoints", "contact"],
        "properties": {
          "name": { "type": "string" },
          "version": { "type": "string" },
          "description": { "type": "string" },
          "documentation": { "type": "string", "format": "uri" },
          "openapi": { "type": "string", "format": "uri" },
          "authentication": { "type": "string", "const": "none" },
          "rate_limit": {
            "type": "object",
            "required": ["limit", "window_seconds", "scope"],
            "properties": {
              "limit": { "type": "integer" },
              "window_seconds": { "type": "integer" },
              "scope": { "type": "string" }
            }
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["method", "path", "operationId", "description", "example"],
              "properties": {
                "method": { "type": "string" },
                "path": { "type": "string" },
                "operationId": { "type": "string" },
                "description": { "type": "string" },
                "example": { "type": "string", "format": "uri" }
              }
            }
          },
          "contact": {
            "type": "object",
            "required": ["email", "url"],
            "properties": {
              "email": { "type": "string", "format": "email" },
              "url": { "type": "string", "format": "uri" }
            }
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Problem details (RFC 9457) with the extension members code, hint, documentation and, for 400 responses, invalid_params.",
        "required": ["type", "title", "status", "detail", "code", "hint", "documentation"],
        "properties": {
          "type": { "type": "string", "format": "uri", "description": "Documentation anchor of the problem type." },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "detail": { "type": "string" },
          "instance": { "type": "string", "description": "Request path and query." },
          "code": {
            "type": "string",
            "enum": ["missing_parameter", "invalid_parameter", "not_found", "product_not_found", "method_not_allowed", "authentication_required", "rate_limited", "internal_error", "search_unavailable"],
            "description": "Stable machine-readable error code."
          },
          "hint": { "type": "string", "description": "How to fix the request." },
          "documentation": { "type": "string", "format": "uri" },
          "invalid_params": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["name", "reason"],
              "properties": {
                "name": { "type": "string" },
                "reason": { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}
