Developer API

Catalog Read API

Public catalog access is available as JSON endpoints for products, brands, product documents, OpenAPI, and product feeds. They return the full active catalog snapshot, including product variants, variant attributes, brand metadata, related catalog context, and file metadata for product PDFs. Write endpoints are intentionally not documented here.

Endpoint

GET /api/public/v1/products

Returns every active product with embedded brand, keyway, star rating, product images, product documents, variants, and variant attributes. Archived products and archived variants are excluded. Results are paginated at 100 products per page; use ?page=2, ?page=3, and so on.

{
  "data": [
    {
      "id": 123,
      "title": "Example Product",
      "slug": "example-product",
      "canonical_url": "https://www.lockandkey.co.uk/product/example-product",
      "updated_at": "2026-06-10T10:00:00+00:00",
      "description_md": "## Description",
      "additional_info_md": null,
      "ai_summary": "Example Product. Example Brand. Euro Cylinders. keyway AB. 3 Star. Description.",
      "availability": "in_stock",
      "dispatch_estimate": "Typically dispatched same working day when in stock and ordered before the published cut-off.",
      "price_exc_vat": "12.50",
      "price": { "exc_vat": "12.50", "inc_vat": "15.00", "currency_symbol": "£", "vat_rate": "0.2000" },
      "price_range": {
        "min": { "exc_vat": "12.50", "inc_vat": "15.00", "currency_symbol": "£", "vat_rate": "0.2000" },
        "max": { "exc_vat": "12.50", "inc_vat": "15.00", "currency_symbol": "£", "vat_rate": "0.2000" },
        "varies": false
      },
      "price_range_inc_vat": { "min": "15.00", "max": "15.00", "varies": false },
      "trade_pricing_available": true,
      "featured": false,
      "category": { "id": 5, "title": "Euro Cylinders", "slug": "euro-cylinders", "path": "locks/euro-cylinders", "url": "..." },
      "category_path": ["Locks", "Euro Cylinders"],
      "brand_id": 9,
      "brand": { "id": 9, "name": "Example Brand", "slug": "example-brand", "logo_url": "..." },
      "keyway": { "id": 4, "name": "AB", "slug": "ab", "allow_keyed_alike": true, "allow_extra_keys": true, "keyed_alike_price_exc_vat": "1.00", "extra_key_price_exc_vat": "2.00" },
      "compatible_keyways": ["AB"],
      "keyed_alike_available": true,
      "star_rating": { "id": 3, "name": "3 Star", "slug": "3-star" },
      "documents": [
        {
          "id": 12,
          "label": "Installation Guide",
          "display_label": "Installation Guide",
          "original_filename": "guide.pdf",
          "mime_type": "application/pdf",
          "size_bytes": 65536,
          "download_url": "..."
        }
      ],
      "images": [{ "id": 77, "image_url": "...", "sort_order": 1 }],
      "variants": [
        {
          "id": 456,
          "product_id": 123,
          "part_number": "EX-001",
          "canonical_url": "https://www.lockandkey.co.uk/product/example-product",
          "description": "Brass / 30-30",
          "availability": "in_stock",
          "available_to_purchase": true,
          "gtin": "5012345678900",
          "mpn": "EX-001",
          "manufacturer_reference": "EX-001-BRASS",
          "price_exc_vat": "12.50",
          "price": { "exc_vat": "12.50", "inc_vat": "15.00", "currency_symbol": "£", "vat_rate": "0.2000" },
          "trade_pricing_available": true,
          "images": [{ "id": 88, "image_url": "...", "sort_order": 0 }],
          "attributes": [{ "name": "Finish", "value": "Brass" }]
        }
      ]
    }
  ],
  "meta": {
    "count": 1,
    "total": 245,
    "page": 1,
    "per_page": 100,
    "last_page": 3,
    "schema_version": "v1"
  }
}

Endpoint

GET /api/public/v1/brands

Returns every brand with storefront-safe metadata, active product counts, and public brand relationships.

{
  "data": [
    {
      "id": 9,
      "name": "Example Brand",
      "slug": "example-brand",
      "logo_url": "...",
      "description_md": "Trusted supplier",
      "country_code": "GB",
      "country_name": "United Kingdom",
      "founded_year": 1984,
      "employee_count_min": 50,
      "employee_count_max": 80,
      "employee_count_estimated": true,
      "company_number": "01234567",
      "is_uk_company": true,
      "turnover_amount": "1000000.00",
      "turnover_currency": "GBP",
      "turnover_estimated": true,
      "product_count": 12,
      "relationships": [
        {
          "related_id": 14,
          "name": "Parent Brand",
          "slug": "parent-brand",
          "url": "...",
          "logo_url": "...",
          "type": "parent",
          "type_label": "Parent company",
          "note": null
        }
      ]
    }
  ],
  "meta": { "count": 1, "schema_version": "v1" }
}

Endpoint

GET /api/public/v1/products/{product_id}/documents

Returns document metadata for a single active product. Use the returned download_url to fetch the PDF file. Archived products return 404.

{
  "data": [
    {
      "id": 12,
      "label": "Installation Guide",
      "display_label": "Installation Guide",
      "original_filename": "guide.pdf",
      "mime_type": "application/pdf",
      "size_bytes": 65536,
      "download_url": "..."
    }
  ],
  "meta": { "count": 1, "schema_version": "v1" }
}

Endpoint

GET /api/public/v1/feeds/products

Returns an agent-friendly product feed with canonical URLs, derived summaries, availability labels, dispatch estimates, price ranges, images, documents, variants, and keyed-alike context. Use this when a consumer needs current catalogue data without scraping storefront pages.

Endpoint

GET /api/public/v1/openapi.json

Returns the machine-readable OpenAPI contract for the public read-only catalogue API.

Notes

  • The products endpoint is paginated at 100 products per page; brands and product documents remain unpaginated.
  • Use canonical_url as the authoritative public URL for products and feed items.
  • ai_summary is derived from existing catalogue copy and metadata; it is not a separate staff-authored claim.
  • Read responses are cached and automatically invalidated when catalog data changes.
  • Trade prices are not exposed on the public catalog API; use trade_pricing_available to show account prompts.
  • Product files are exposed as metadata plus a download_url; binary downloads remain on the public web route.
  • Binary upload and admin write routes are internal-only and are documented in our private engineering docs.