{
  "openapi": "3.1.0",
  "info": {
    "title": "AI+ Pro public API",
    "version": "1.0.0",
    "summary": "Read-only JSON over everything aiplus.pro already publishes.",
    "description": "The public read API for AI+ Pro, the secure all-in-one AI platform published at https://aiplus.pro.\n\nEvery operation is a GET, needs no credentials, and returns data the site already publishes as HTML and Markdown: the live model line-up, the course library and its syllabi, the blog and the FAQ. There is no write surface, and there is no key to request.\n\nThe same operations are exposed as MCP tools over Streamable HTTP at https://aiplus.pro/mcp, generated from this document, so a tool name maps one-to-one onto an operationId here.\n\nEvery page on the site is also available as Markdown: send `Accept: text/markdown`, or append `.md` to any page path. Errors under /api answer as JSON and name where to look next.\n\nThis document describes the public surface only. The AI+ Pro platform itself — workspaces, documents, governance — has no public API; enterprise integrations are scoped per deployment through https://aiplus.pro/contact.",
    "contact": {
      "name": "AI+ Pro",
      "url": "https://aiplus.pro/contact",
      "email": "contact@aiplus.pro"
    },
    "termsOfService": "https://aiplus.pro/terms",
    "license": {
      "name": "Published for reading and citation; see the terms of service.",
      "url": "https://aiplus.pro/terms"
    }
  },
  "servers": [
    {
      "url": "https://aiplus.pro",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer resources",
    "url": "https://aiplus.pro/developers"
  },
  "tags": [
    {
      "name": "Models",
      "description": "The model line-up the workspace can route to."
    },
    {
      "name": "Courses",
      "description": "The AI+ Pro course library and its syllabi."
    },
    {
      "name": "Articles",
      "description": "The AI+ Pro blog."
    },
    {
      "name": "FAQ",
      "description": "Questions the site answers, in nine languages."
    }
  ],
  "paths": {
    "/api/models/roster": {
      "get": {
        "operationId": "getModelRoster",
        "tags": [
          "Models"
        ],
        "summary": "The live model line-up",
        "description": "Returns the models the AI+ Pro workspace can route to, ranked by Artificial Analysis' intelligence index. This is the list the homepage renders, so prefer it over any model name written into page copy, which ages. No credentials. Cached for five minutes at the edge; when the upstream read fails the response carries the pinned fallback list and says so in `source`, rather than an error.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The ranked line-up, its source and when it was fetched.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 300 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelRoster"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/courses": {
      "get": {
        "operationId": "listCourses",
        "tags": [
          "Courses"
        ],
        "summary": "Search the course library",
        "description": "Returns courses from the AI+ Pro library, the same catalogue the /courses pages are built from. Filter by free text, tier or status. Courses with status `complete` have a published page at the `url` field; `listed` courses are in the library but still in production and have no page. Runtime minutes are a floor measured from rendered subtitles, never an estimate.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitive free text, matched against the code, title, subtitle, category and who the course is for. Omit to list everything.",
            "schema": {
              "type": "string"
            },
            "example": "civil servants"
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "description": "1 Foundations · 2 By profession · 3 By skill · 4 Special edition.",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                2,
                3,
                4
              ]
            },
            "example": 2
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "`complete` for courses with a published page, `listed` for those still in production.",
            "schema": {
              "type": "string",
              "enum": [
                "complete",
                "listed"
              ]
            },
            "example": "complete"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum courses to return, 1 to 100. Defaults to 50. `total` reports the match count before this cap.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "example": 20
          }
        ],
        "responses": {
          "200": {
            "description": "Matching courses and the total before `limit`.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 3600 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseList"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, the wrong type, or outside its declared range. `error` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/courses/{code}": {
      "get": {
        "operationId": "getCourse",
        "tags": [
          "Courses"
        ],
        "summary": "One course with its syllabus",
        "description": "Returns a single course by its code, including the module-by-module syllabus, who it is for, its outcomes and its other language editions. Codes are case-insensitive. Answers 404 for a code that is not in the library.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "The course code, e.g. T1-01 or T2-20.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9-]{2,20}$"
            },
            "example": "T1-01"
          }
        ],
        "responses": {
          "200": {
            "description": "The course and its full syllabus.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 3600 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDetail"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, the wrong type, or outside its declared range. `error` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record. The body names where to look next.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/blog/posts": {
      "get": {
        "operationId": "listBlogPosts",
        "tags": [
          "Articles"
        ],
        "summary": "Published articles",
        "description": "Returns every published article on the AI+ Pro blog with its full body. Each article is also a page at /blog-posts/{slug} and a Markdown twin at /blog-posts/{slug}.md.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Published articles, newest first.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 300 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArticleList"
                }
              }
            }
          },
          "500": {
            "description": "The content store did not answer. Retry; nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/blog/posts/{slug}": {
      "get": {
        "operationId": "getBlogPost",
        "tags": [
          "Articles"
        ],
        "summary": "One article by slug",
        "description": "Returns a single article with its full body. Answers 404 for a slug that does not exist. The same article is served as Markdown at /blog-posts/{slug}.md.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's path segment under /blog-posts/.",
            "schema": {
              "type": "string"
            },
            "example": "shadow-ai-at-work"
          }
        ],
        "responses": {
          "200": {
            "description": "The article.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 300 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, the wrong type, or outside its declared range. `error` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record. The body names where to look next.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The content store did not answer. Retry; nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/faq/categories": {
      "get": {
        "operationId": "listFaqCategories",
        "tags": [
          "FAQ"
        ],
        "summary": "FAQ categories",
        "description": "Returns the FAQ categories, each with its name in the 9 languages the FAQ is translated into (en, es, zh, ja, th, vi, fr, de, ms). The same corpus is rendered at /faq.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Categories in display order.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 3600 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaqCategoryList"
                }
              }
            }
          },
          "500": {
            "description": "The content store did not answer. Retry; nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/faq/entries": {
      "get": {
        "operationId": "listFaqEntries",
        "tags": [
          "FAQ"
        ],
        "summary": "FAQ questions and answers",
        "description": "Returns FAQ entries, each with its question and answer in the 9 languages the FAQ is translated into (en, es, zh, ja, th, vi, fr, de, ms). Pass `categoryId` to narrow to one category; omit it for the whole corpus.",
        "parameters": [
          {
            "name": "categoryId",
            "in": "query",
            "required": false,
            "description": "Restrict to one category, using an `id` from the categories operation.",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Entries in display order.",
            "headers": {
              "Cache-Control": {
                "description": "Public, cacheable for 3600 seconds.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaqEntryList"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, the wrong type, or outside its declared range. `error` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The content store did not answer. Retry; nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "RosterModel": {
        "type": "object",
        "description": "One model in the line-up the workspace can route to.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Model name as its vendor publishes it.",
            "examples": [
              "Claude Opus 5"
            ]
          },
          "vendor": {
            "type": "string",
            "description": "Display vendor.",
            "examples": [
              "Anthropic"
            ]
          },
          "score": {
            "type": "number",
            "description": "Artificial Analysis' intelligence index. Higher is more capable. It ranks text models only, so image and video models never appear here."
          },
          "openWeight": {
            "type": "boolean",
            "description": "The vendor publishes the weights. Not a claim about the licence being OSI-approved."
          }
        },
        "required": [
          "name",
          "vendor",
          "score",
          "openWeight"
        ],
        "additionalProperties": false
      },
      "ModelRoster": {
        "type": "object",
        "description": "The model line-up shown on the aiplus.pro homepage.",
        "properties": {
          "models": {
            "type": "array",
            "description": "Ranked by score, most capable first.",
            "items": {
              "$ref": "#/components/schemas/RosterModel"
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "openrouter",
              "fallback"
            ],
            "description": "`openrouter` is a live upstream read. `fallback` is the pinned list this site ships, served when the upstream call failed; treat `updatedAt` as unknown in that case."
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the upstream list was fetched. Null whenever `source` is `fallback`."
          }
        },
        "required": [
          "models",
          "source",
          "updatedAt"
        ],
        "additionalProperties": false
      },
      "CourseSummary": {
        "type": "object",
        "description": "One course in the AI+ Pro library, as the /courses index lists it.",
        "properties": {
          "code": {
            "type": "string",
            "description": "Course code, and the path segment of its page.",
            "examples": [
              "T1-01"
            ]
          },
          "title": {
            "type": "string",
            "description": "Course title."
          },
          "subtitle": {
            "type": "string",
            "description": "One-line subtitle."
          },
          "tier": {
            "type": "integer",
            "enum": [
              1,
              2,
              3,
              4
            ],
            "description": "1 Foundations · 2 By profession · 3 By skill · 4 Special edition."
          },
          "tierLabel": {
            "type": "string",
            "description": "The tier's name, as the site prints it."
          },
          "category": {
            "type": "string",
            "description": "The audience or skill family the course sits in."
          },
          "locale": {
            "type": "string",
            "description": "BCP 47 language tag of the course itself.",
            "examples": [
              "en"
            ]
          },
          "lessonCount": {
            "type": "integer",
            "description": "Lessons across every module."
          },
          "runtimeMinutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A FLOOR, in whole minutes, measured from rendered subtitles. Null when none have been rendered. Never round it up."
          },
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "listed"
            ],
            "description": "`complete` courses have a published page. `listed` courses are in the library but still in production and have no URL."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The course page. Null for a `listed` course, which has none."
          }
        },
        "required": [
          "code",
          "title",
          "subtitle",
          "tier",
          "tierLabel",
          "category",
          "locale",
          "lessonCount",
          "runtimeMinutes",
          "status",
          "url"
        ],
        "additionalProperties": false
      },
      "CourseModule": {
        "type": "object",
        "description": "One module of a course syllabus.",
        "properties": {
          "n": {
            "type": "integer",
            "description": "Module number, from 1."
          },
          "title": {
            "type": "string",
            "description": "Module title."
          },
          "description": {
            "type": "string",
            "description": "What the module covers."
          }
        },
        "required": [
          "n",
          "title",
          "description"
        ],
        "additionalProperties": false
      },
      "CourseEdition": {
        "type": "object",
        "description": "A translation or regional cut of the same course.",
        "properties": {
          "code": {
            "type": "string",
            "description": "The edition's own course code.",
            "examples": [
              "T1-01-TH"
            ]
          },
          "locale": {
            "type": "string",
            "description": "BCP 47 language tag."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "Region the cut is written for, when it has one."
          }
        },
        "required": [
          "code",
          "locale",
          "region"
        ],
        "additionalProperties": false
      },
      "CourseDetail": {
        "type": "object",
        "description": "A course with its full syllabus, as its /courses/{code} page publishes it.",
        "properties": {
          "code": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "subtitle": {
            "type": "string"
          },
          "tier": {
            "type": "integer",
            "enum": [
              1,
              2,
              3,
              4
            ]
          },
          "tierLabel": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "locale": {
            "type": "string"
          },
          "lessonCount": {
            "type": "integer"
          },
          "runtimeMinutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A floor in whole minutes; null when unmeasured."
          },
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "listed"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "whoFor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who the course is written for."
          },
          "outcomes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What a learner can do afterwards."
          },
          "benefits": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What the organisation gets."
          },
          "modules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CourseModule"
            },
            "description": "The syllabus, in order."
          },
          "faqs": {
            "type": "array",
            "description": "Questions answered on the course page.",
            "items": {
              "type": "object",
              "properties": {
                "q": {
                  "type": "string"
                },
                "a": {
                  "type": "string"
                }
              },
              "required": [
                "q",
                "a"
              ],
              "additionalProperties": false
            }
          },
          "editions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CourseEdition"
            },
            "description": "Other language or regional cuts."
          }
        },
        "required": [
          "code",
          "title",
          "subtitle",
          "tier",
          "tierLabel",
          "category",
          "locale",
          "lessonCount",
          "runtimeMinutes",
          "status",
          "url",
          "whoFor",
          "outcomes",
          "benefits",
          "modules",
          "faqs",
          "editions"
        ],
        "additionalProperties": false
      },
      "CourseList": {
        "type": "object",
        "description": "A page of the course library.",
        "properties": {
          "total": {
            "type": "integer",
            "description": "Courses matching the filters, before `limit` was applied."
          },
          "courses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CourseSummary"
            }
          }
        },
        "required": [
          "total",
          "courses"
        ],
        "additionalProperties": false
      },
      "ArticleSection": {
        "type": "object",
        "description": "One block of an article body.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "heading",
              "paragraph",
              "bullets",
              "callout",
              "text"
            ],
            "description": "Block kind. `bullets` carries `items`; the rest carry `text`."
          },
          "text": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "type"
        ],
        "additionalProperties": true
      },
      "Article": {
        "type": "object",
        "description": "One article from the AI+ Pro blog.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Row id. Stable, but prefer `slug` as the identifier."
          },
          "slug": {
            "type": "string",
            "description": "Path segment under /blog-posts/."
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string",
            "description": "The summary the page and its metadata carry."
          },
          "content": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSection"
            },
            "description": "The body, in order."
          },
          "category": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "description": "Publication date as the article prints it."
          },
          "readTime": {
            "type": "string",
            "description": "Estimated reading time, e.g. \"6 min read\"."
          },
          "image": {
            "type": "string",
            "description": "Card image path. Empty string when unset."
          },
          "heroImage": {
            "type": "string",
            "description": "Hero image path. Empty string when unset."
          },
          "author": {
            "type": "string"
          },
          "authorRole": {
            "type": "string",
            "description": "Empty string when unset."
          },
          "published": {
            "type": "boolean",
            "description": "Always true on the public endpoints."
          },
          "order": {
            "type": "integer",
            "description": "Sort key used by the index."
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "excerpt",
          "content",
          "category",
          "date",
          "readTime",
          "author",
          "published"
        ],
        "additionalProperties": true
      },
      "ArticleList": {
        "type": "array",
        "description": "Published articles, newest first.",
        "items": {
          "$ref": "#/components/schemas/Article"
        }
      },
      "FaqCategory": {
        "type": "object",
        "description": "One FAQ category, with its name in every language the site carries.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string",
            "examples": [
              "getting-started"
            ]
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "description": "Lucide icon name used by the site's own UI."
          },
          "order": {
            "type": "integer"
          },
          "translations": {
            "type": "object",
            "description": "Category name keyed by language tag (en, es, zh, ja, th, vi, fr, de, ms).",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "slug",
          "order",
          "translations"
        ],
        "additionalProperties": true
      },
      "FaqCategoryList": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/FaqCategory"
        }
      },
      "FaqEntry": {
        "type": "object",
        "description": "One question and answer, in every language the site carries.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "categoryId": {
            "type": "integer",
            "description": "The category this entry belongs to."
          },
          "slug": {
            "type": "string"
          },
          "order": {
            "type": "integer"
          },
          "translations": {
            "type": "object",
            "description": "Question and answer keyed by language tag.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "question": {
                  "type": "string"
                },
                "answer": {
                  "type": "string"
                }
              },
              "required": [
                "question",
                "answer"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "id",
          "categoryId",
          "slug",
          "order",
          "translations"
        ],
        "additionalProperties": true
      },
      "FaqEntryList": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/FaqEntry"
        }
      },
      "Error": {
        "type": "object",
        "description": "Every error under /api has this shape.",
        "properties": {
          "error": {
            "type": "string",
            "description": "A stable machine-readable code, e.g. `not_found`."
          },
          "path": {
            "type": "string",
            "description": "The path that was requested."
          },
          "see": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Where to look next: the sitemap, llms.txt and the developer page."
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      }
    }
  },
  "security": []
}
