{
 "openapi": "3.1.0",
 "info": {
  "title": "Sign Group public API",
  "version": "1.0.0",
  "summary": "Read-only data about a London sign maker: its services, its finished projects and its online shop.",
  "description": "Sign Group designs, manufactures and installs shop front signs, illuminated signs and 3D built-up letters from its own factory in London. This API returns the same content as the website, as JSON. It is public, read-only and needs no key. Sign prices are not published: every sign is quoted for the job, and a quote is requested at https://signgroup.uk/quote/ (see https://signgroup.uk/pricing/). Shop prices (LED modules, light box LEDs, LCD backpacks) are fixed and are returned here. Every page of the site is also available as Markdown: send `Accept: text/markdown` to its URL. A guide for AI agents is at https://signgroup.uk/llms.txt.\n\nVersioning: every path begins with the major version, /api/v1. Fields may be added to a response at any time; a change that removes or renames a field, or changes its type, is released under a new path (/api/v2) and /api/v1 keeps answering as described here.",
  "contact": {
   "name": "Sign Group",
   "url": "https://signgroup.uk/contact/",
   "email": "info@signgroup.uk"
  }
 },
 "servers": [
  {
   "url": "/",
   "description": "The site this document was fetched from"
  }
 ],
 "tags": [
  {
   "name": "Catalogue",
   "description": "Services, projects and shop products."
  }
 ],
 "paths": {
  "/api/v1/services/": {
   "get": {
    "operationId": "listServices",
    "summary": "List the sign services",
    "description": "Every kind of sign and signage service Sign Group offers, each with its page URL and a one-line summary. Use it to find the right service for a job.",
    "tags": [
     "Catalogue"
    ],
    "responses": {
     "200": {
      "description": "The services.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "required": [
          "count",
          "items"
         ],
         "properties": {
          "count": {
           "type": "integer",
           "description": "How many services are in the list."
          },
          "items": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ServiceSummary"
           }
          }
         }
        }
       }
      }
     },
     "405": {
      "description": "The API is read-only: only GET, HEAD and OPTIONS are accepted.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/services/{slug}/": {
   "get": {
    "operationId": "getService",
    "summary": "Get one service",
    "description": "One service in full: description, what is included, materials and specifications, common questions, and the slugs of finished projects that used it.",
    "tags": [
     "Catalogue"
    ],
    "parameters": [
     {
      "name": "slug",
      "in": "path",
      "required": true,
      "description": "The service's slug, as returned by the list endpoint.",
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9-]+$"
      },
      "example": "shop-front-signs"
     }
    ],
    "responses": {
     "200": {
      "description": "The service.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Service"
        }
       }
      }
     },
     "404": {
      "description": "No item has that slug.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     },
     "405": {
      "description": "The API is read-only: only GET, HEAD and OPTIONS are accepted.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/projects/": {
   "get": {
    "operationId": "listProjects",
    "summary": "List finished projects",
    "description": "Signs Sign Group has designed, made and installed, each with its sector, the services used and, where known, the client, location and year.",
    "tags": [
     "Catalogue"
    ],
    "responses": {
     "200": {
      "description": "The projects.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "required": [
          "count",
          "items"
         ],
         "properties": {
          "count": {
           "type": "integer",
           "description": "How many projects are in the list."
          },
          "items": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ProjectSummary"
           }
          }
         }
        }
       }
      }
     },
     "405": {
      "description": "The API is read-only: only GET, HEAD and OPTIONS are accepted.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/projects/{slug}/": {
   "get": {
    "operationId": "getProject",
    "summary": "Get one project",
    "description": "One finished project in full: sign type, materials, illumination, the write-up and its photos.",
    "tags": [
     "Catalogue"
    ],
    "parameters": [
     {
      "name": "slug",
      "in": "path",
      "required": true,
      "description": "The project's slug, as returned by the list endpoint.",
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9-]+$"
      },
      "example": "the-hilight-by-ghelamco"
     }
    ],
    "responses": {
     "200": {
      "description": "The project.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Project"
        }
       }
      }
     },
     "404": {
      "description": "No item has that slug.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     },
     "405": {
      "description": "The API is read-only: only GET, HEAD and OPTIONS are accepted.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/products/": {
   "get": {
    "operationId": "listProducts",
    "summary": "List shop products",
    "description": "The products sold in the online shop (LED modules and strips, light box LEDs, LCD backpacks) with their prices before and after VAT. Stock is not included: check the product page.",
    "tags": [
     "Catalogue"
    ],
    "responses": {
     "200": {
      "description": "The products.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "required": [
          "count",
          "items"
         ],
         "properties": {
          "count": {
           "type": "integer",
           "description": "How many products are in the list."
          },
          "items": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ProductSummary"
           }
          }
         }
        }
       }
      }
     },
     "405": {
      "description": "The API is read-only: only GET, HEAD and OPTIONS are accepted.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/products/{slug}/": {
   "get": {
    "operationId": "getProduct",
    "summary": "Get one shop product",
    "description": "One shop product in full: SKU, specification summary, variants with their own prices, and photos. Stock is not included: `availability_url` is the page that shows it.",
    "tags": [
     "Catalogue"
    ],
    "parameters": [
     {
      "name": "slug",
      "in": "path",
      "required": true,
      "description": "The product's slug, as returned by the list endpoint.",
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9-]+$"
      },
      "example": "light-box-led-102cm"
     }
    ],
    "responses": {
     "200": {
      "description": "The product.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Product"
        }
       }
      }
     },
     "404": {
      "description": "No item has that slug.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     },
     "405": {
      "description": "The API is read-only: only GET, HEAD and OPTIONS are accepted.",
      "content": {
       "application/problem+json": {
        "schema": {
         "$ref": "#/components/schemas/Problem"
        }
       }
      }
     }
    }
   }
  }
 },
 "components": {
  "schemas": {
   "ServiceSummary": {
    "type": "object",
    "required": [
     "slug",
     "name",
     "url",
     "summary"
    ],
    "properties": {
     "slug": {
      "type": "string",
      "description": "Identifier used in the service's API path."
     },
     "name": {
      "type": "string",
      "description": "Name of the service."
     },
     "url": {
      "type": "string",
      "description": "The service's page on the website.",
      "format": "uri"
     },
     "summary": {
      "type": "string",
      "description": "One line on what the service is."
     }
    }
   },
   "Service": {
    "type": "object",
    "required": [
     "slug",
     "name",
     "url",
     "summary",
     "description"
    ],
    "properties": {
     "slug": {
      "type": "string",
      "description": "Identifier used in the service's API path."
     },
     "name": {
      "type": "string",
      "description": "Name of the service."
     },
     "url": {
      "type": "string",
      "description": "The service's page on the website.",
      "format": "uri"
     },
     "summary": {
      "type": "string",
      "description": "One line on what the service is."
     },
     "description": {
      "type": "string",
      "description": "What the service is, in a short paragraph."
     },
     "included": {
      "type": "array",
      "description": "What every job of this kind includes.",
      "items": {
       "type": "string"
      }
     },
     "specifications": {
      "type": "array",
      "description": "Sign types, materials, finishes and similar, grouped.",
      "items": {
       "type": "object",
       "required": [
        "label",
        "items"
       ],
       "properties": {
        "label": {
         "type": "string",
         "description": "Name of the group, e.g. Materials."
        },
        "items": {
         "type": "array",
         "description": "The entries of the group.",
         "items": {
          "type": "string"
         }
        }
       }
      }
     },
     "faqs": {
      "type": "array",
      "description": "Questions customers ask about this service.",
      "items": {
       "type": "object",
       "required": [
        "question",
        "answer"
       ],
       "properties": {
        "question": {
         "type": "string",
         "description": "A question customers ask."
        },
        "answer": {
         "type": "string",
         "description": "Sign Group's answer."
        }
       }
      }
     },
     "projects": {
      "type": "array",
      "description": "Slugs of finished projects that used this service.",
      "items": {
       "type": "string"
      }
     },
     "quote_url": {
      "type": "string",
      "description": "Where to request a quote for this service.",
      "format": "uri"
     }
    }
   },
   "ProjectSummary": {
    "type": "object",
    "required": [
     "slug",
     "name",
     "url",
     "sector",
     "services"
    ],
    "properties": {
     "slug": {
      "type": "string",
      "description": "Identifier used in the project's API path."
     },
     "name": {
      "type": "string",
      "description": "Name of the project."
     },
     "url": {
      "type": "string",
      "description": "The project's page on the website.",
      "format": "uri"
     },
     "sector": {
      "type": "string",
      "description": "The kind of business the sign was made for."
     },
     "services": {
      "type": "array",
      "description": "Slugs of the services used.",
      "items": {
       "type": "string"
      }
     },
     "client": {
      "type": "string",
      "description": "The client, where it may be named."
     },
     "location": {
      "type": "string",
      "description": "Where the sign was installed."
     },
     "year": {
      "type": "integer",
      "description": "The year of the job, where known."
     },
     "sign_type": {
      "type": "string",
      "description": "The kind of sign."
     }
    }
   },
   "Project": {
    "type": "object",
    "required": [
     "slug",
     "name",
     "url",
     "sector",
     "services",
     "description",
     "images"
    ],
    "properties": {
     "slug": {
      "type": "string",
      "description": "Identifier used in the project's API path."
     },
     "name": {
      "type": "string",
      "description": "Name of the project."
     },
     "url": {
      "type": "string",
      "description": "The project's page on the website.",
      "format": "uri"
     },
     "sector": {
      "type": "string",
      "description": "The kind of business the sign was made for."
     },
     "services": {
      "type": "array",
      "description": "Slugs of the services used.",
      "items": {
       "type": "string"
      }
     },
     "client": {
      "type": "string",
      "description": "The client, where it may be named."
     },
     "location": {
      "type": "string",
      "description": "Where the sign was installed."
     },
     "year": {
      "type": "integer",
      "description": "The year of the job, where known."
     },
     "sign_type": {
      "type": "string",
      "description": "The kind of sign."
     },
     "materials": {
      "type": "string",
      "description": "What the sign is made of."
     },
     "illumination": {
      "type": "string",
      "description": "How the sign is lit."
     },
     "description": {
      "type": "array",
      "description": "The project write-up, one paragraph per item.",
      "items": {
       "type": "string"
      }
     },
     "images": {
      "type": "array",
      "description": "Photos of the finished sign.",
      "items": {
       "type": "object",
       "required": [
        "url",
        "alt"
       ],
       "properties": {
        "url": {
         "type": "string",
         "description": "Absolute URL of the image.",
         "format": "uri"
        },
        "alt": {
         "type": "string",
         "description": "What the image shows."
        },
        "width": {
         "type": "integer"
        },
        "height": {
         "type": "integer"
        }
       }
      }
     }
    }
   },
   "ProductSummary": {
    "type": "object",
    "required": [
     "slug",
     "name",
     "url",
     "currency",
     "price"
    ],
    "properties": {
     "slug": {
      "type": "string",
      "description": "Identifier used in the product's API path."
     },
     "name": {
      "type": "string",
      "description": "Name of the product."
     },
     "url": {
      "type": "string",
      "description": "The product's page in the shop.",
      "format": "uri"
     },
     "category": {
      "type": "array",
      "description": "The shop categories the product is in.",
      "items": {
       "type": "string"
      }
     },
     "currency": {
      "type": "string",
      "description": "Always GBP.",
      "enum": [
       "GBP"
      ]
     },
     "price": {
      "type": "object",
      "required": [
       "ex_vat",
       "inc_vat"
      ],
      "description": "Pounds sterling.",
      "properties": {
       "ex_vat": {
        "type": "number",
        "description": "Price before VAT."
       },
       "inc_vat": {
        "type": "number",
        "description": "Price including UK VAT at 20%."
       }
      }
     },
     "unit": {
      "type": "string",
      "description": "What one unit is, where it is not a single item, e.g. a 50 m roll."
     }
    }
   },
   "Product": {
    "type": "object",
    "required": [
     "slug",
     "name",
     "url",
     "currency",
     "price",
     "variants",
     "images",
     "availability_url"
    ],
    "properties": {
     "slug": {
      "type": "string",
      "description": "Identifier used in the product's API path."
     },
     "name": {
      "type": "string",
      "description": "Name of the product."
     },
     "url": {
      "type": "string",
      "description": "The product's page in the shop.",
      "format": "uri"
     },
     "category": {
      "type": "array",
      "description": "The shop categories the product is in.",
      "items": {
       "type": "string"
      }
     },
     "currency": {
      "type": "string",
      "description": "Always GBP.",
      "enum": [
       "GBP"
      ]
     },
     "price": {
      "type": "object",
      "required": [
       "ex_vat",
       "inc_vat"
      ],
      "description": "Pounds sterling.",
      "properties": {
       "ex_vat": {
        "type": "number",
        "description": "Price before VAT."
       },
       "inc_vat": {
        "type": "number",
        "description": "Price including UK VAT at 20%."
       }
      }
     },
     "unit": {
      "type": "string",
      "description": "What one unit is, where it is not a single item, e.g. a 50 m roll."
     },
     "sku": {
      "type": "string",
      "description": "Stock keeping unit."
     },
     "summary": {
      "type": "string",
      "description": "The product's specification summary."
     },
     "variants": {
      "type": "array",
      "description": "The options the product is sold in, each with its own price. Empty for a product with no options.",
      "items": {
       "type": "object",
       "required": [
        "options",
        "price"
       ],
       "properties": {
        "sku": {
         "type": "string",
         "description": "Stock keeping unit of the variant."
        },
        "options": {
         "type": "object",
         "description": "Option name to chosen value, e.g. Colour temperature: 6500K.",
         "additionalProperties": {
          "type": "string"
         }
        },
        "price": {
         "type": "object",
         "required": [
          "ex_vat",
          "inc_vat"
         ],
         "description": "Pounds sterling.",
         "properties": {
          "ex_vat": {
           "type": "number",
           "description": "Price before VAT."
          },
          "inc_vat": {
           "type": "number",
           "description": "Price including UK VAT at 20%."
          }
         }
        }
       }
      }
     },
     "images": {
      "type": "array",
      "description": "Photos of the product.",
      "items": {
       "type": "object",
       "required": [
        "url",
        "alt"
       ],
       "properties": {
        "url": {
         "type": "string",
         "description": "Absolute URL of the image.",
         "format": "uri"
        },
        "alt": {
         "type": "string",
         "description": "What the image shows."
        },
        "width": {
         "type": "integer"
        },
        "height": {
         "type": "integer"
        }
       }
      }
     },
     "availability_url": {
      "type": "string",
      "description": "The page that shows whether the product is in stock.",
      "format": "uri"
     }
    }
   },
   "Problem": {
    "type": "object",
    "description": "An error, in RFC 9457 form.",
    "required": [
     "title",
     "status",
     "code",
     "detail",
     "hint"
    ],
    "properties": {
     "type": {
      "type": "string",
      "description": "A URI identifying the kind of problem.",
      "format": "uri"
     },
     "title": {
      "type": "string",
      "description": "Short name of the problem."
     },
     "status": {
      "type": "integer",
      "description": "The HTTP status code."
     },
     "code": {
      "type": "string",
      "description": "A stable machine-readable code.",
      "enum": [
       "not_found",
       "method_not_allowed"
      ]
     },
     "detail": {
      "type": "string",
      "description": "What went wrong with this request."
     },
     "hint": {
      "type": "string",
      "description": "What to do instead."
     },
     "docs": {
      "type": "string",
      "description": "Where the API is described.",
      "format": "uri"
     }
    }
   }
  }
 }
}