{
  "openapi": "3.1.0",
  "info": {
    "title": "IIESMS public website API",
    "summary": "Read-only access to published IIESMS content.",
    "description": "Public, read-only API for iiesms.ie, the website of the Institute of Industrial Engineers & Safety Management Systems (Ireland). It exposes published pages, news posts, categories and the site search index as JSON, and every public page as Markdown through content negotiation. No authentication is needed or supported for these endpoints: unpublished drafts, members, documents, payments and the admin panel are not part of this API. Anonymous requests are limited to 120 per 60 seconds per client; see the RateLimit-* headers. Human-readable documentation: https://iiesms.ie/developers",
    "version": "1.0.0",
    "contact": {
      "name": "IIESMS enquiries",
      "email": "info@iiesms.ie"
    }
  },
  "servers": [
    {
      "url": "https://iiesms.ie",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://iiesms.ie/developers"
  },
  "tags": [
    {
      "name": "Content",
      "description": "Published pages, posts and categories."
    },
    {
      "name": "Search",
      "description": "The site search index."
    },
    {
      "name": "Agents",
      "description": "Machine-readable site descriptions and Markdown pages."
    }
  ],
  "paths": {
    "/api/pages": {
      "get": {
        "operationId": "listPages",
        "tags": [
          "Content"
        ],
        "summary": "List published pages",
        "description": "Returns published website pages (layout blocks, hero and SEO metadata) as paginated JSON. Use where[slug][equals] to fetch one page by slug.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Documents per page (default 10). Use 0 for no limit.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 10
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "depth",
            "in": "query",
            "description": "How many levels of related documents to expand (0 returns ids only).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "default": 0
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Field to sort by; prefix with - for descending, e.g. -publishedAt.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[slug][equals]",
            "in": "query",
            "description": "Return only the document with this exact slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[title][like]",
            "in": "query",
            "description": "Case-insensitive substring match on the title.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of pages.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagesList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/pages/{id}": {
      "get": {
        "operationId": "getPage",
        "tags": [
          "Content"
        ],
        "summary": "Get one published page by id",
        "description": "Returns a single published page by its numeric id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Document id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "description": "How many levels of related documents to expand (0 returns ids only).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The page.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/posts": {
      "get": {
        "operationId": "listPosts",
        "tags": [
          "Content"
        ],
        "summary": "List published news posts",
        "description": "Returns published news and article posts, newest first when sorted by -publishedAt, as paginated JSON.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Documents per page (default 10). Use 0 for no limit.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 10
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "depth",
            "in": "query",
            "description": "How many levels of related documents to expand (0 returns ids only).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "default": 0
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Field to sort by; prefix with - for descending, e.g. -publishedAt.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[slug][equals]",
            "in": "query",
            "description": "Return only the document with this exact slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[title][like]",
            "in": "query",
            "description": "Case-insensitive substring match on the title.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of posts.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostsList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/posts/{id}": {
      "get": {
        "operationId": "getPost",
        "tags": [
          "Content"
        ],
        "summary": "Get one published post by id",
        "description": "Returns a single published post by its numeric id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Document id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "description": "How many levels of related documents to expand (0 returns ids only).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The post.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/categories": {
      "get": {
        "operationId": "listCategories",
        "tags": [
          "Content"
        ],
        "summary": "List post categories",
        "description": "Returns the categories used to organise news posts.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Documents per page (default 10). Use 0 for no limit.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 10
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "depth",
            "in": "query",
            "description": "How many levels of related documents to expand (0 returns ids only).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "default": 0
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Field to sort by; prefix with - for descending, e.g. -publishedAt.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[slug][equals]",
            "in": "query",
            "description": "Return only the document with this exact slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of categories.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoriesList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "searchSite",
        "tags": [
          "Search"
        ],
        "summary": "Search pages and posts",
        "description": "Queries the site search index. Each result points to the page or post it was built from through doc.relationTo and doc.value. Filter with where[title][like] or where[slug][equals].",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Documents per page (default 10). Use 0 for no limit.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 10
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "depth",
            "in": "query",
            "description": "How many levels of related documents to expand (0 returns ids only).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "default": 0
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Field to sort by; prefix with - for descending, e.g. -publishedAt.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[slug][equals]",
            "in": "query",
            "description": "Return only the document with this exact slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where[title][like]",
            "in": "query",
            "description": "Case-insensitive substring match on the title.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of search results.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResultsList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/{slug}": {
      "get": {
        "operationId": "getPageAsMarkdown",
        "tags": [
          "Agents"
        ],
        "summary": "Read any public page as Markdown or HTML",
        "description": "Every public page (and the home page at /) is available as Markdown. Send Accept: text/markdown to receive Markdown with Vary: Accept; send Accept: text/html (or use a browser) for HTML. Unknown paths return HTTP 404, with a Markdown body that links to the sitemap and llms.txt when Markdown was requested.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Page path without the leading slash, e.g. memberships or our-history.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "text/markdown for Markdown, text/html for HTML.",
            "schema": {
              "type": "string",
              "enum": [
                "text/markdown",
                "text/html"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The page in the requested representation.",
            "headers": {
              "Vary": {
                "description": "Always includes Accept.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such page. A Markdown explanation is returned for Accept: text/markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": [
          "Agents"
        ],
        "summary": "Get the agent-oriented site summary",
        "description": "Plain-text/Markdown summary of what the Institute is, when an agent should use this site, and links to the key pages and this API (llms.txt convention).",
        "responses": {
          "200": {
            "description": "The llms.txt document.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemapIndex",
        "tags": [
          "Agents"
        ],
        "summary": "Get the sitemap index",
        "description": "XML sitemap index listing the page and post sitemaps (/pages-sitemap.xml, /posts-sitemap.xml).",
        "responses": {
          "200": {
            "description": "Sitemap index.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/graphql": {
      "post": {
        "operationId": "queryGraphql",
        "tags": [
          "Content"
        ],
        "summary": "GraphQL endpoint (introspection disabled)",
        "description": "GraphQL access to the same published content as the REST endpoints. Introspection is disabled; use the REST endpoints above or the schema of this document to know what is available. Counts toward the same rate limit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "GraphQL query document."
                  },
                  "variables": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL result (errors are reported inside the body).",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per 60-second window (120).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "NotFound": {
        "description": "The document does not exist or is not published.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded (120 requests per 60 seconds). Wait Retry-After seconds.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Limit": {
            "description": "Requests allowed per 60-second window (120).",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests left in the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the current window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "message"
              ],
              "properties": {
                "message": {
                  "type": "string",
                  "description": "What went wrong."
                }
              }
            }
          }
        }
      },
      "Meta": {
        "type": [
          "object",
          "null"
        ],
        "description": "SEO metadata.",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "image": {
            "description": "Media id, or the media object at depth > 0.",
            "type": [
              "integer",
              "object",
              "string",
              "null"
            ]
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": [
          "totalDocs",
          "limit",
          "totalPages",
          "page",
          "hasPrevPage",
          "hasNextPage"
        ],
        "properties": {
          "totalDocs": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "totalPages": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pagingCounter": {
            "type": "integer"
          },
          "hasPrevPage": {
            "type": "boolean"
          },
          "hasNextPage": {
            "type": "boolean"
          },
          "prevPage": {
            "type": [
              "integer",
              "null"
            ]
          },
          "nextPage": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Page": {
        "type": "object",
        "description": "A website page built from a hero and layout blocks.",
        "required": [
          "id",
          "title",
          "slug"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "title": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "URL path segment; the page is at /{slug}."
          },
          "hero": {
            "type": "object",
            "additionalProperties": true
          },
          "layout": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Post": {
        "type": "object",
        "description": "A news or article post.",
        "required": [
          "id",
          "title",
          "slug"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "title": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "URL path segment; the post is at /posts/{slug}."
          },
          "content": {
            "type": "object",
            "additionalProperties": true,
            "description": "Lexical rich-text document."
          },
          "categories": {
            "type": "array",
            "items": {
              "type": [
                "integer",
                "object"
              ]
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Category": {
        "type": "object",
        "required": [
          "id",
          "title",
          "slug"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "title": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "doc"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "slug": {
            "type": "string"
          },
          "doc": {
            "type": "object",
            "required": [
              "relationTo",
              "value"
            ],
            "properties": {
              "relationTo": {
                "type": "string",
                "enum": [
                  "pages",
                  "posts"
                ]
              },
              "value": {
                "type": [
                  "integer",
                  "string",
                  "object"
                ]
              }
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PagesList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Pagination"
          },
          {
            "type": "object",
            "required": [
              "docs"
            ],
            "properties": {
              "docs": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          }
        ]
      },
      "PostsList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Pagination"
          },
          {
            "type": "object",
            "required": [
              "docs"
            ],
            "properties": {
              "docs": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          }
        ]
      },
      "CategoriesList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Pagination"
          },
          {
            "type": "object",
            "required": [
              "docs"
            ],
            "properties": {
              "docs": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          }
        ]
      },
      "SearchResultsList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Pagination"
          },
          {
            "type": "object",
            "required": [
              "docs"
            ],
            "properties": {
              "docs": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SearchResult"
                }
              }
            }
          }
        ]
      }
    }
  }
}