{
 "openapi": "3.1.0",
 "info": {
  "title": "Waypoint API",
  "version": "1",
  "description": "Shared dependency graph. Authenticate with a graph key as a bearer token, or ?key= on any request. Docs for agents: /llms.txt"
 },
 "paths": {
  "/api/graphs": {
   "post": {
    "summary": "Generate a new set of owner/editor/viewer keys (no auth)",
    "responses": {
     "200": {
      "description": "keys"
     }
    }
   }
  },
  "/api/session": {
   "get": {
    "summary": "Role, graph ID and shareable keys for the presented key",
    "security": [
     {
      "bearer": []
     }
    ],
    "responses": {
     "200": {
      "description": "{role, graph, exists, keys}"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}": {
   "get": {
    "summary": "Summary: title and node/link/conflict counts",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/nodes": {
   "get": {
    "summary": "Search nodes by title or key",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "q",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/nodes/{node}": {
   "get": {
    "summary": "One node with links and note",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "node",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/nodes/{node}/neighbours": {
   "get": {
    "summary": "Nodes reachable from a node",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "node",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "direction",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "both",
        "dependencies",
        "dependants"
       ]
      }
     },
     {
      "name": "depth",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/conflicts": {
   "get": {
    "summary": "Links set aside because they would close a cycle",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/full": {
   "get": {
    "summary": "The whole graph as JSON (nodes, links, conflicts, types)",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/dot": {
   "get": {
    "summary": "Graphviz DOT text",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "text/vnd.graphviz": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/positions": {
   "get": {
    "summary": "Absolute node positions",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/log": {
   "get": {
    "summary": "Change log, oldest first",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/apply": {
   "post": {
    "summary": "Apply a batch of ops atomically (editor key)",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "X-Waypoint-Actor",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "what the caller calls itself; may repeat, proves nothing"
     },
     {
      "name": "X-Waypoint-Peer",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "the caller's id for this session; keys its undo history"
     }
    ],
    "responses": {
     "200": {
      "description": "ok"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "403": {
      "description": "key role too low"
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "ops"
        ],
        "properties": {
         "label": {
          "type": "string"
         },
         "ops": {
          "type": "array",
          "items": {
           "oneOf": [
            {
             "type": "object",
             "required": [
              "op",
              "title"
             ],
             "properties": {
              "op": {
               "const": "create_node"
              },
              "title": {
               "type": "string"
              },
              "depends_on": {
               "type": "array",
               "items": {
                "type": "string",
                "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
               }
              },
              "dependants": {
               "type": "array",
               "items": {
                "type": "string",
                "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
               }
              },
              "x": {
               "type": "integer"
              },
              "y": {
               "type": "integer"
              },
              "node_type": {
               "type": "string",
               "description": "name of a node type"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "node",
              "title"
             ],
             "properties": {
              "op": {
               "const": "rename_node"
              },
              "node": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "title": {
               "type": "string"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "node"
             ],
             "properties": {
              "op": {
               "const": "delete_node"
              },
              "node": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "from",
              "to"
             ],
             "properties": {
              "op": {
               "const": "link"
              },
              "from": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "to": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "edge_type": {
               "type": "string",
               "description": "name of an edge type; an existing link is changed to it"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "from",
              "to"
             ],
             "properties": {
              "op": {
               "const": "unlink"
              },
              "from": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "to": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "node",
              "x",
              "y"
             ],
             "properties": {
              "op": {
               "const": "move_node"
              },
              "node": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "x": {
               "type": "integer"
              },
              "y": {
               "type": "integer"
              },
              "with_dependencies": {
               "type": "boolean"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "node",
              "body"
             ],
             "properties": {
              "op": {
               "const": "set_note"
              },
              "node": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "body": {
               "type": "string"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "node",
              "text"
             ],
             "properties": {
              "op": {
               "const": "append_note"
              },
              "node": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "text": {
               "type": "string"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "node",
              "node_type"
             ],
             "properties": {
              "op": {
               "const": "set_node_type"
              },
              "node": {
               "type": "string",
               "description": "key (\"#12\" or \"12\"), UUID, or \"$N\" for the node created by op N of the same batch"
              },
              "node_type": {
               "type": "string",
               "description": "type name, or \"default\""
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "name"
             ],
             "properties": {
              "op": {
               "const": "upsert_node_type"
              },
              "name": {
               "type": "string"
              },
              "new_name": {
               "type": "string"
              },
              "shape": {
               "type": "string",
               "enum": [
                "rect",
                "pill",
                "ellipse",
                "hexagon",
                "rhombus"
               ]
              },
              "bg": {
               "type": "string",
               "description": "#rrggbb"
              },
              "fg": {
               "type": "string",
               "description": "#rrggbb"
              },
              "border": {
               "type": "string",
               "description": "#rrggbb"
              },
              "border_width": {
               "type": "integer",
               "description": "clamped to 0-10"
              },
              "corners": {
               "type": "string",
               "enum": [
                "round",
                "square"
               ],
               "description": "corner style of rect, hexagon and rhombus"
              }
             },
             "description": "Create or change a node type; only given fields change. bg and fg are set together and need contrast >= 4.5:1."
            },
            {
             "type": "object",
             "required": [
              "op",
              "name"
             ],
             "properties": {
              "op": {
               "const": "upsert_edge_type"
              },
              "name": {
               "type": "string"
              },
              "new_name": {
               "type": "string"
              },
              "label": {
               "type": "string"
              },
              "color": {
               "type": "string",
               "description": "#rrggbb"
              },
              "text_color": {
               "type": "string",
               "description": "#rrggbb"
              },
              "width": {
               "type": "integer",
               "description": "clamped to 1-10"
              },
              "line_style": {
               "type": "string",
               "enum": [
                "solid",
                "dashed",
                "dotted"
               ]
              },
              "start": {
               "type": "string",
               "enum": [
                "none",
                "arrow",
                "dot",
                "bar"
               ]
              },
              "end": {
               "type": "string",
               "enum": [
                "none",
                "arrow",
                "dot",
                "bar"
               ]
              }
             },
             "description": "Create or change an edge type. color needs contrast >= 3:1 and text_color >= 4.5:1 against the graph background."
            },
            {
             "type": "object",
             "required": [
              "op",
              "name"
             ],
             "properties": {
              "op": {
               "const": "remove_node_type"
              },
              "name": {
               "type": "string"
              }
             }
            },
            {
             "type": "object",
             "required": [
              "op",
              "name"
             ],
             "properties": {
              "op": {
               "const": "remove_edge_type"
              },
              "name": {
               "type": "string"
              }
             }
            }
           ],
           "discriminator": {
            "propertyName": "op"
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/graphs/{graph}/undo": {
   "post": {
    "summary": "Undo your latest batch (editor key); 409 if none",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "X-Waypoint-Actor",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "what the caller calls itself; may repeat, proves nothing"
     },
     {
      "name": "X-Waypoint-Peer",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "the caller's id for this session; keys its undo history"
     }
    ],
    "responses": {
     "200": {
      "description": "ok"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "403": {
      "description": "key role too low"
     }
    }
   }
  },
  "/api/graphs/{graph}/redo": {
   "post": {
    "summary": "Redo (editor key); 409 if none",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "X-Waypoint-Actor",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "what the caller calls itself; may repeat, proves nothing"
     },
     {
      "name": "X-Waypoint-Peer",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "the caller's id for this session; keys its undo history"
     }
    ],
    "responses": {
     "200": {
      "description": "ok"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "403": {
      "description": "key role too low"
     }
    }
   }
  },
  "/api/graphs/{graph}/title": {
   "put": {
    "summary": "Set the graph title (owner key)",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "X-Waypoint-Actor",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "what the caller calls itself; may repeat, proves nothing"
     },
     {
      "name": "X-Waypoint-Peer",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "the caller's id for this session; keys its undo history"
     }
    ],
    "responses": {
     "200": {
      "description": "ok"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "403": {
      "description": "key role too low"
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "title"
        ],
        "properties": {
         "title": {
          "type": "string"
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/graphs/{graph}/types": {
   "get": {
    "summary": "Node types, edge types and the background colour",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {}
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     }
    }
   }
  },
  "/api/graphs/{graph}/background": {
   "put": {
    "summary": "Set the background colour (owner key); edge types that would stop being readable are recoloured",
    "security": [
     {
      "bearer": []
     }
    ],
    "parameters": [
     {
      "name": "graph",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "X-Waypoint-Actor",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "what the caller calls itself; may repeat, proves nothing"
     },
     {
      "name": "X-Waypoint-Peer",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "the caller's id for this session; keys its undo history"
     }
    ],
    "responses": {
     "200": {
      "description": "ok"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "403": {
      "description": "key role too low"
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "color"
        ],
        "properties": {
         "color": {
          "type": "string",
          "description": "#rrggbb"
         }
        }
       }
      }
     }
    }
   }
  }
 },
 "components": {
  "securitySchemes": {
   "bearer": {
    "type": "http",
    "scheme": "bearer",
    "description": "wp_owner_/wp_editor_/wp_viewer_ key; also accepted as ?key="
   }
  },
  "responses": {
   "Unauthorized": {
    "description": "missing or wrong key",
    "content": {
     "application/json": {
      "schema": {
       "type": "object",
       "properties": {
        "error": {
         "type": "string"
        },
        "docs": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  }
 }
}