{
  "openapi": "3.1.0",
  "info": {
    "title": "mattwood.fyi Query API",
    "version": "1.0.0",
    "summary": "Public read-only API for mattwood.fyi (Matt Wood's FYI knowledge graph).",
    "description": "Public, read-only API over **mattwood.fyi** \u2014 Matt Wood's FYI, a typed knowledge graph of riffs, links, and essays. No authentication, no API keys. All responses are JSON. Use this to answer questions about what Matt is reading, thinking about, and how ideas connect.\n\n**Versioning:** endpoints live under the stable `/api/fyi/q/` namespace (API v1). Breaking changes will be shipped under a new path prefix (e.g. `/api/fyi/v2/`) and the previous version will carry a `Deprecation` header and a `Sunset` date (RFC 8594) for at least 90 days before removal.\n\n**Rate limits:** there are currently no rate limits. If limits are ever introduced, responses will include the RFC 9331 `RateLimit` header and a `Retry-After` header on HTTP 429 so agents can self-throttle.\n\n**Errors:** error responses use `application/problem+json` (RFC 9457) with a machine-readable `type`/`title`/`status` and a human-readable `detail`.",
    "contact": {
      "name": "Matt Wood",
      "url": "https://mattwood.fyi/contact",
      "email": "hello@mattwood.fyi"
    },
    "license": {
      "name": "Content \u00a9 Matt Wood"
    }
  },
  "servers": [
    {
      "url": "https://mattwood.fyi",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Agent instructions (when-to-use guidance)",
    "url": "https://mattwood.fyi/agents/"
  },
  "paths": {
    "/api/fyi/q/search/{keyword}": {
      "get": {
        "operationId": "searchItems",
        "summary": "Keyword search",
        "description": "Search items by keyword in title and content. Returns matching items with permalinks. Path-based form is preferred because some agent fetch tools strip query-string params.",
        "parameters": [
          {
            "name": "keyword",
            "in": "path",
            "required": true,
            "description": "Keyword or phrase to search for (URL-encoded).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ItemList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/fyi/q/semantic/{query}": {
      "get": {
        "operationId": "semanticSearch",
        "summary": "Semantic (vector) search",
        "description": "Search by meaning, not keywords, using vector embeddings. Returns items ranked by similarity score (0\u20131). Use this for natural-language questions.",
        "parameters": [
          {
            "name": "query",
            "in": "path",
            "required": true,
            "description": "Natural-language query. Use + or %20 for spaces.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Items ranked by semantic similarity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScoredItemList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/fyi/q/items": {
      "get": {
        "operationId": "listItems",
        "summary": "List/filter items",
        "description": "List items filtered by date and/or type, newest first, with pagination metadata.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only items published on/after this date (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter by item type.",
            "schema": {
              "type": "string",
              "enum": [
                "riff",
                "link",
                "essay"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max items to return (default 50).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Items to skip for pagination (default 0).",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of items with total/hasMore metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedItemList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/fyi/q/edges/{itemId}": {
      "get": {
        "operationId": "getEdgesForItem",
        "summary": "Connections for one item",
        "description": "Get all graph connections for a specific item: edge type, direction, confidence, and the reason for the connection. Add ?type= to filter.",
        "parameters": [
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "description": "Short (8-char) item id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter to one edge type.",
            "schema": {
              "type": "string",
              "enum": [
                "supports",
                "challenges",
                "develops_into",
                "related_to"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Edges for the item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EdgeList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/fyi/q/edges": {
      "get": {
        "operationId": "getEdgesByType",
        "summary": "All edges of a type",
        "description": "Get all edges of a specific type across the whole graph (e.g. every 'challenges' relationship, with reasons).",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "description": "Edge type to return.",
            "schema": {
              "type": "string",
              "enum": [
                "supports",
                "challenges",
                "develops_into",
                "related_to"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Edges of the requested type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EdgeList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/fyi/q/summary": {
      "get": {
        "operationId": "getGraphSummary",
        "summary": "Graph snapshot",
        "description": "Pre-computed snapshot: top connected items, recent activity, edge-type distribution, and all tension pairs. Includes a generated_at timestamp; if stale, prefer the live endpoints.",
        "responses": {
          "200": {
            "description": "Point-in-time graph summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphSummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Item": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "description": "Full item id."
          },
          "type": {
            "type": "string",
            "enum": [
              "riff",
              "link",
              "essay"
            ]
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string",
            "description": "Markdown body."
          },
          "url": {
            "type": "string",
            "description": "External source URL (link items)."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "permalink": {
            "type": "string",
            "description": "Stable path, e.g. /i/abcd1234."
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ScoredItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Item"
          },
          {
            "type": "object",
            "properties": {
              "score": {
                "type": "number",
                "description": "Similarity score 0\u20131."
              }
            }
          }
        ]
      },
      "Edge": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "supports",
              "challenges",
              "develops_into",
              "related_to"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "incoming",
              "outgoing"
            ]
          },
          "confidence": {
            "type": "number"
          },
          "reason": {
            "type": "string"
          },
          "otherId": {
            "type": "string"
          }
        }
      },
      "ItemList": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Item"
            }
          }
        }
      },
      "ScoredItemList": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScoredItem"
            }
          }
        }
      },
      "PagedItemList": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Item"
            }
          },
          "total": {
            "type": "integer"
          },
          "hasMore": {
            "type": "boolean"
          },
          "offset": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          }
        }
      },
      "EdgeList": {
        "type": "object",
        "properties": {
          "edges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Edge"
            }
          }
        }
      },
      "GraphSummary": {
        "type": "object",
        "properties": {
          "recent_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Item"
            }
          },
          "tensions": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "graph_url": {
            "type": "string"
          },
          "feed_url": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "RFC 9457 problem detail. Returned for 4xx/5xx responses.",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI identifying the problem type.",
            "default": "about:blank"
          },
          "title": {
            "type": "string",
            "description": "Short, human-readable summary of the problem type."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code."
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation specific to this occurrence."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code."
          }
        },
        "required": [
          "title",
          "status"
        ]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request was malformed (e.g. a missing required parameter).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No resource matched the request.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "An unexpected error occurred.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}