{
  "openapi": "3.1.0",
  "info": {
    "title": "Step By Step Crochet Public API",
    "summary": "Read-only access to localized crochet guides.",
    "description": "Search and retrieve the public editorial guides published by Step By Step Crochet. The API is anonymous, read-only, and does not require an API key or access token.",
    "version": "2026-08-31",
    "contact": {
      "name": "Molly Digital Apps",
      "email": "mollydigitalapps@gmail.com",
      "url": "https://stepbystepcrochet.com/en/contact"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://stepbystepcrochet.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Step By Step Crochet developer documentation",
    "url": "https://stepbystepcrochet.com/en/developers"
  },
  "tags": [
    {
      "name": "Guides",
      "description": "Localized crochet tutorials and learning material."
    }
  ],
  "paths": {
    "/api/v1/guides": {
      "get": {
        "operationId": "findCrochetGuides",
        "summary": "Find crochet guides",
        "description": "Returns up to 20 published guide summaries in a supported language. Search matches guide titles and excerpts.",
        "tags": ["Guides"],
        "security": [],
        "parameters": [
          {
            "name": "locale",
            "in": "query",
            "description": "Language of the guides. Defaults to English.",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Locale"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Optional topic or keywords, up to 200 characters.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "default": ""
            },
            "example": "granny square"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of results.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 8
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guide summaries matching the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuideCollectionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "default": {
            "$ref": "#/components/responses/JsonError"
          }
        }
      }
    },
    "/api/v1/guides/search": {
      "post": {
        "operationId": "searchCrochetGuides",
        "summary": "Search crochet guides with a JSON body",
        "description": "Performs the same anonymous, read-only search as GET /api/v1/guides. This endpoint has no side effects and supports Idempotency-Key for retry-safe agent workflows.",
        "tags": ["Guides"],
        "security": [],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional opaque value, 1 to 255 visible ASCII characters. The response echoes a valid supplied key.",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "8b7668ca-55b1-4872-912e-7c9d0474545d"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "locale": { "$ref": "#/components/schemas/Locale" },
                  "query": {
                    "type": "string",
                    "maxLength": 200,
                    "default": ""
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20,
                    "default": 8
                  }
                }
              },
              "example": {
                "locale": "es",
                "query": "granny square",
                "limit": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Guide summaries matching the request.",
            "headers": {
              "Idempotency-Key": {
                "description": "Echo of the supplied retry key, when present.",
                "schema": { "type": "string" }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuideCollectionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "default": {
            "$ref": "#/components/responses/JsonError"
          }
        }
      }
    },
    "/api/v1/guides/{slug}": {
      "get": {
        "operationId": "readCrochetGuide",
        "summary": "Read a crochet guide",
        "description": "Returns a complete guide as Markdown together with its metadata and available translations.",
        "tags": ["Guides"],
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "Guide slug returned by findCrochetGuides.",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 240
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language of the guide. Defaults to English.",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Locale"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The complete guide.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuideDetailResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "The locale and slug do not identify a published guide.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "default": {
            "$ref": "#/components/responses/JsonError"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Locale": {
        "type": "string",
        "enum": ["en", "es", "de", "fr", "pt"],
        "default": "en"
      },
      "GuideSummary": {
        "type": "object",
        "required": [
          "locale",
          "slug",
          "title",
          "excerpt",
          "published",
          "modified",
          "readingMinutes",
          "url",
          "markdownUrl"
        ],
        "properties": {
          "locale": { "$ref": "#/components/schemas/Locale" },
          "slug": { "type": "string" },
          "title": { "type": "string" },
          "excerpt": { "type": "string" },
          "published": { "type": "string", "format": "date" },
          "modified": { "type": "string", "format": "date" },
          "readingMinutes": { "type": "integer", "minimum": 1 },
          "url": { "type": "string", "format": "uri" },
          "markdownUrl": { "type": "string", "format": "uri" }
        }
      },
      "GuideDetail": {
        "allOf": [
          { "$ref": "#/components/schemas/GuideSummary" },
          {
            "type": "object",
            "required": ["contentMarkdown", "tableOfContents", "keyTakeaways", "translations"],
            "properties": {
              "contentMarkdown": { "type": "string" },
              "tableOfContents": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["id", "text", "level"],
                  "properties": {
                    "id": { "type": "string" },
                    "text": { "type": "string" },
                    "level": { "type": "integer", "enum": [2, 3, 4] }
                  }
                }
              },
              "keyTakeaways": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["question", "answer"],
                  "properties": {
                    "question": { "type": "string" },
                    "answer": { "type": "string" }
                  }
                }
              },
              "translations": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/GuideSummary" }
              }
            }
          }
        ]
      },
      "GuideCollectionResponse": {
        "type": "object",
        "required": ["data", "meta", "links"],
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/GuideSummary" }
          },
          "meta": { "type": "object", "additionalProperties": true },
          "links": { "type": "object", "additionalProperties": { "type": "string", "format": "uri" } }
        }
      },
      "GuideDetailResponse": {
        "type": "object",
        "required": ["data", "meta", "links"],
        "properties": {
          "data": { "$ref": "#/components/schemas/GuideDetail" },
          "meta": { "type": "object", "additionalProperties": true },
          "links": { "type": "object", "additionalProperties": { "type": "string", "format": "uri" } }
        }
      },
      "Error": {
        "type": "object",
        "required": ["code", "message", "hint"],
        "properties": {
          "code": { "type": "string" },
          "message": { "type": "string" },
          "hint": { "type": "string" },
          "details": { "type": "object", "additionalProperties": true }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "$ref": "#/components/schemas/Error" }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "One or more request parameters are invalid.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "The requested HTTP method is not supported by this resource.",
        "headers": {
          "Allow": {
            "description": "Methods supported by the resource.",
            "schema": { "type": "string" }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "JsonError": {
        "description": "An error represented as structured JSON.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      }
    }
  }
}
