{
  "openapi": "3.1.0",
  "info": {
    "title": "Lavigne Labs public JSON API",
    "version": "v1",
    "description": "Read-only static JSON describing Lavigne Labs: articles, services, FAQ, portfolio, contact details, calculator specs and a search index. Start at /api/v1/index.json. Every response is wrapped in a shared envelope (version, generated, site).",
    "contact": {
      "name": "Brian Barrett",
      "email": "hello@lavignelabs.com"
    }
  },
  "servers": [
    {
      "url": "https://lavignelabs.com"
    }
  ],
  "paths": {
    "/api/v1/index.json": {
      "get": {
        "operationId": "get_index",
        "summary": "API index",
        "description": "Directory of every API endpoint.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "endpoints": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/IndexEntry"
                          }
                        }
                      },
                      "required": [
                        "endpoints"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/image-credits.json": {
      "get": {
        "operationId": "get_image_credits",
        "summary": "Image-credits",
        "description": "Provenance of every stock (non-original) image used in articles: file, article, source URL, photographer, licence (Unsplash/Pexels) and alt text.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/articles.json": {
      "get": {
        "operationId": "get_articles",
        "summary": "Articles",
        "description": "Every article: slug, title, date, tag, excerpt, readingTime and links to the page, markdown and JSON.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "articles": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ArticleListItem"
                          }
                        }
                      },
                      "required": [
                        "articles"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/series.json": {
      "get": {
        "operationId": "get_series",
        "summary": "Series",
        "description": "Article series (e.g. Field Notes from China): slug, title, intro, url and the ordered list of published articles.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/services.json": {
      "get": {
        "operationId": "get_services",
        "summary": "Services",
        "description": "Services offered, plus engagement models and the process for working together.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "services": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Service"
                          }
                        },
                        "engagement": {
                          "type": "object",
                          "properties": {
                            "intro": {
                              "type": "string"
                            },
                            "models": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/EngagementModel"
                              }
                            }
                          },
                          "required": [
                            "intro",
                            "models"
                          ]
                        },
                        "process": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ProcessStep"
                          }
                        }
                      },
                      "required": [
                        "services",
                        "engagement",
                        "process"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/faq.json": {
      "get": {
        "operationId": "get_faq",
        "summary": "Faq",
        "description": "All frequently asked questions with answers.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "faqs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Faq"
                          }
                        }
                      },
                      "required": [
                        "faqs"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/portfolio.json": {
      "get": {
        "operationId": "get_portfolio",
        "summary": "Portfolio",
        "description": "Selected projects with summaries, tags and images.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "projects": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Project"
                          }
                        }
                      },
                      "required": [
                        "projects"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/search-index.json": {
      "get": {
        "operationId": "get_search_index",
        "summary": "Search-index",
        "description": "Flat search index: one record (id, type, title, url, text) per article, FAQ, service and project, with full plain-text article bodies.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "records": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/SearchRecord"
                          }
                        }
                      },
                      "required": [
                        "records"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/contact.json": {
      "get": {
        "operationId": "get_contact",
        "summary": "Contact",
        "description": "How to reach Lavigne Labs: email, WeChat, location and hours.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "contact": {
                          "$ref": "#/components/schemas/Contact"
                        }
                      },
                      "required": [
                        "contact"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools.json": {
      "get": {
        "operationId": "get_tools",
        "summary": "Tools",
        "description": "Machine-readable specs for the site's calculators and reference sheets: listing with links to tools/<id>.json (inputs with units and ranges, outputs, formula, worked examples).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "tools": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ToolListItem"
                          }
                        }
                      },
                      "required": [
                        "tools"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}.json": {
      "get": {
        "operationId": "get_tools_by_id",
        "summary": "One tool spec",
        "description": "Machine-readable spec for one calculator or reference sheet: inputs with units and ranges, outputs, formula, worked examples.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "One of the 7 published values.",
            "schema": {
              "type": "string",
              "enum": [
                "fastener-sheet-metric",
                "fastener-sheet-inch",
                "holding-force-metric",
                "holding-force-inch",
                "torque-metric",
                "torque-inch",
                "pcb"
              ]
            },
            "example": "fastener-sheet-metric"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "$ref": "#/components/schemas/ToolSpec"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/articles/{slug}.json": {
      "get": {
        "operationId": "get_articles_by_slug",
        "summary": "One article",
        "description": "A single article: metadata, links, and the body as Markdown and rendered HTML.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "One of the 10 published values.",
            "schema": {
              "type": "string",
              "enum": [
                "cantilever-snap-fits",
                "designing-moldable-parts",
                "fast-injection-molds",
                "fast-prototyping-china",
                "lowest-cost-manufacturing-china",
                "taobao-factory-sourcing",
                "vetting-a-factory",
                "wechat-shared-history",
                "what-landed-cost-includes",
                "yes-doesnt-always-mean-yes"
              ]
            },
            "example": "cantilever-snap-fits"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "slug": {
                          "type": "string"
                        },
                        "title": {
                          "type": "string"
                        },
                        "date": {
                          "type": "string",
                          "description": "Publication date, YYYY-MM-DD."
                        },
                        "excerpt": {
                          "type": "string"
                        },
                        "tag": {
                          "type": "string"
                        },
                        "readingTime": {
                          "type": "integer",
                          "description": "Minutes."
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "markdownUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "jsonUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "markdown": {
                          "type": "string"
                        },
                        "html": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "slug",
                        "title",
                        "date",
                        "excerpt",
                        "tag",
                        "readingTime",
                        "url",
                        "markdownUrl",
                        "jsonUrl",
                        "markdown",
                        "html"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Envelope": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string",
            "const": "v1"
          },
          "generated": {
            "type": "string",
            "format": "date-time",
            "description": "Build time of this file."
          },
          "site": {
            "type": "string",
            "format": "uri",
            "description": "Site origin."
          }
        },
        "required": [
          "version",
          "generated",
          "site"
        ]
      },
      "ArticleMeta": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "description": "Publication date, YYYY-MM-DD."
          },
          "excerpt": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "readingTime": {
            "type": "integer",
            "description": "Minutes."
          }
        },
        "required": [
          "slug",
          "title",
          "date",
          "excerpt",
          "tag",
          "readingTime"
        ]
      },
      "ArticleLinks": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          },
          "jsonUrl": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "url",
          "markdownUrl",
          "jsonUrl"
        ]
      },
      "ArticleListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ArticleMeta"
          },
          {
            "$ref": "#/components/schemas/ArticleLinks"
          }
        ]
      },
      "Service": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "points": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "title",
          "summary",
          "points"
        ]
      },
      "EngagementModel": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "tag",
          "body"
        ]
      },
      "ProcessStep": {
        "type": "object",
        "properties": {
          "step": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        },
        "required": [
          "step",
          "title",
          "body"
        ]
      },
      "Faq": {
        "type": "object",
        "properties": {
          "q": {
            "type": "string"
          },
          "a": {
            "type": "string"
          },
          "featured": {
            "type": "boolean"
          }
        },
        "required": [
          "q",
          "a"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "blurb": {
            "type": "string"
          },
          "image": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "featured": {
            "type": "boolean"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "format": "uri"
          },
          "galleryUrls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        },
        "required": [
          "slug",
          "title",
          "blurb",
          "image",
          "tags",
          "url",
          "imageUrl",
          "galleryUrls"
        ]
      },
      "SearchRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "<type>:<key>, e.g. article:my-slug, faq:1."
          },
          "type": {
            "type": "string",
            "enum": [
              "article",
              "faq",
              "service",
              "project",
              "page"
            ]
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "text": {
            "type": "string",
            "description": "Plain text."
          },
          "slug": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "type",
          "title",
          "url",
          "text"
        ]
      },
      "Contact": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "wechat": {
            "type": "string"
          },
          "location": {
            "type": "string"
          },
          "contactPage": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "name",
          "email",
          "wechat",
          "location",
          "contactPage"
        ]
      },
      "ToolListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "jsonUrl": {
            "type": "string",
            "format": "uri"
          },
          "pageUrl": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title",
          "kind",
          "summary",
          "jsonUrl",
          "pageUrl"
        ]
      },
      "ToolInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "unit": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "number",
              "enum",
              "boolean",
              "set"
            ]
          },
          "min": {
            "type": "number"
          },
          "max": {
            "type": "number"
          },
          "step": {
            "type": "number"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": [
                    "string",
                    "number"
                  ]
                },
                "label": {
                  "type": "string"
                }
              },
              "required": [
                "value",
                "label"
              ]
            }
          },
          "default": {},
          "activeWhen": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "label",
          "unit",
          "type"
        ]
      },
      "ToolOutput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "unit": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "label",
          "unit"
        ]
      },
      "ToolExample": {
        "type": "object",
        "properties": {
          "inputs": {
            "type": "object",
            "additionalProperties": true
          },
          "outputs": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "inputs",
          "outputs"
        ]
      },
      "ToolCalculator": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "inputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolInput"
            }
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolOutput"
            }
          },
          "formula": {
            "type": "string"
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "examples": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolExample"
            }
          },
          "data": {},
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title",
          "kind",
          "summary",
          "inputs",
          "outputs",
          "formula",
          "examples"
        ]
      },
      "ToolSpec": {
        "type": "object",
        "description": "Calculators carry inputs/outputs/formula/examples; reference sheets carry data; the pcb page carries calculators.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "jsonUrl": {
            "type": "string",
            "format": "uri"
          },
          "inputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolInput"
            }
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolOutput"
            }
          },
          "formula": {
            "type": "string"
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "examples": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolExample"
            }
          },
          "data": {},
          "calculators": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolCalculator"
            }
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title",
          "kind",
          "summary",
          "url",
          "jsonUrl"
        ]
      },
      "IndexEntry": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "path": {
            "type": "string",
            "description": "Site-relative path."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string"
          },
          "mediaType": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "path",
          "url",
          "description",
          "mediaType"
        ]
      }
    }
  }
}