{
  "openapi": "3.1.1",
  "info": {
    "title": "Project File Identifier API",
    "version": "1.0.0",
    "summary": "Find out which usefulweb app opens a project file and what it leaves out.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://bundlekeepprojectidentifier.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Find out which usefulweb app opens a project file and what it leaves out.",
        "description": "Paste a project file from any usefulweb premium app: the tool names the app and format, counts embedded images, computes a SHA-256 checksum, suggests a file name and warns when documents, media or secrets need extra care.\n\nMethodology:\n- Parses the JSON and reads its format marker (the `format` field, or `app` for Opsdeck). 54 formats of 27 apps are known; Kinetica and Threadsmith files without a marker are recognised by their required fields.\n- Counts base64 data URLs (embedded images and other files) and computes the SHA-256 checksum of the exact UTF-8 text, so a later copy can be compared byte for byte.\n- Warns about parts a project file does not contain: Paperwell document files (unless the dossier was saved with its files), Reelcraft media files, and Keysmith projects that keep secrets.\n- The file is not changed, stored or opened in an app; the app link opens an empty workspace where you can open the downloaded file.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "bundlekeep_project_identifier",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BundlekeepProjectIdentifierInput"
              },
              "examples": {
                "example1": {
                  "summary": "Identify an Ohmforge circuit project",
                  "value": {}
                },
                "example2": {
                  "summary": "Identify a Paperwell dossier without its documents",
                  "value": {
                    "project": "{\"format\":\"paperwell-project\",\"version\":1,\"name\":\"Tenancy\",\"documents\":[{\"id\":\"d1\",\"name\":\"lease.pdf\"},{\"id\":\"d2\",\"name\":\"amendment.pdf\"}]}"
                  }
                },
                "example3": {
                  "summary": "Check an unknown JSON file",
                  "value": {
                    "project": "{\"title\":\"Shopping list\",\"items\":[\"milk\",\"bread\"]}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BundlekeepProjectIdentifierResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BundlekeepProjectIdentifierInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "project": {
            "title": "Project file (JSON)",
            "description": "The text of a project file saved by a usefulweb app, at most 10 MB.",
            "type": "string",
            "maxLength": 10000000,
            "default": "{\n  \"format\": \"ohmforge\",\n  \"version\": 1,\n  \"name\": \"LED with series resistor\",\n  \"parts\": [\n    {\n      \"id\": \"BT1\",\n      \"kind\": \"battery\",\n      \"x\": 0,\n      \"y\": 0,\n      \"rot\": 0,\n      \"flip\": false,\n      \"props\": {\n        \"voltage\": \"9\",\n        \"rint\": \"1\"\n      }\n    },\n    {\n      \"id\": \"SW1\",\n      \"kind\": \"switch\",\n      \"x\": 4,\n      \"y\": -6,\n      \"rot\": 0,\n      \"flip\": false,\n      \"props\": {\n        \"closed\": true,\n        \"ron\": \"10m\"\n      }\n    },\n    {\n      \"id\": \"R1\",\n      \"kind\": \"resistor\",\n      \"x\": 10,\n      \"y\": -6,\n      \"rot\": 0,\n      \"flip\": false,\n      \"props\": {\n        \"value\": \"390\",\n        \"power\": \"0.25\",\n        \"tolerance\": \"5%\"\n      }\n    },\n    {\n      \"id\": \"D1\",\n      \"kind\": \"led\",\n      \"x\": 14,\n      \"y\": -4,\n      \"rot\": 90,\n      \"flip\": false,\n      \"props\": {\n        \"colour\": \"red\",\n        \"imax\": \"20m\"\n      }\n    },\n    {\n      \"id\": \"GND1\",\n      \"kind\": \"ground\",\n      \"x\": 0,\n      \"y\": 4,\n      \"rot\": 0,\n      \"flip\": false,\n      \"props\": {}\n    }\n  ],\n  \"wires\": [\n    {\n      \"id\": \"w1\",\n      \"pts\": [\n        [\n          0,\n          -2\n        ],\n        [\n          0,\n          -6\n        ],\n        [\n          2,\n          -6\n        ]\n      ]\n    },\n    {\n      \"id\": \"w2\",\n      \"pts\": [\n        [\n          6,\n          -6\n        ],\n        [\n          8,\n          -6\n        ]\n      ]\n    },\n    {\n      \"id\": \"w3\",\n      \"pts\": [\n        [\n          12,\n          -6\n        ],\n        [\n          14,\n          -6\n        ]\n      ]\n    },\n    {\n      \"id\": \"w4\",\n      \"pts\": [\n        [\n          14,\n          -2\n        ],\n        [\n          14,\n          4\n        ],\n        [\n          0,\n          4\n        ],\n        [\n          0,\n          2\n        ]\n      ]\n    }\n  ],\n  \"notes\": [\n    {\n      \"id\": \"n1\",\n      \"x\": 17,\n      \"y\": -7,\n      \"text\": \"R = (9 V − 1.9 V) / 0.02 A ≈ 355 Ω → 390 Ω\"\n    },\n    {\n      \"id\": \"n2\",\n      \"x\": 17,\n      \"y\": -5,\n      \"text\": \"Try 47 Ω to see the LED burn out\"\n    }\n  ],\n  \"sim\": {\n    \"mode\": \"op\",\n    \"tran\": {\n      \"tstop\": \"10m\",\n      \"tstep\": \"10u\",\n      \"method\": \"trap\",\n      \"uic\": false\n    },\n    \"ac\": {\n      \"fstart\": \"10\",\n      \"fstop\": \"1Meg\",\n      \"ppd\": 20\n    },\n    \"dc\": {\n      \"start\": \"0\",\n      \"stop\": \"10\",\n      \"step\": \"0.1\"\n    },\n    \"speed\": 0.001\n  },\n  \"description\": \"Sizing an LED resistor: R = (Vs − Vf) / If = (9 V − 1.9 V) / 20 mA ≈ 355 Ω → 390 Ω (E12), giving ≈ 18 mA.\",\n  \"instruments\": {\n    \"meter\": {\n      \"mode\": \"A\",\n      \"part\": \"D1\"\n    }\n  }\n}"
          }
        },
        "required": []
      },
      "BundlekeepProjectIdentifierResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "app",
              "appName",
              "format",
              "label",
              "known",
              "projectName",
              "bytes",
              "embeddedDataUrls",
              "sha256",
              "suggestedFileName",
              "warnings",
              "openApp"
            ],
            "properties": {
              "app": {
                "type": "string"
              },
              "appName": {
                "type": "string"
              },
              "format": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "known": {
                "type": "boolean"
              },
              "projectName": {
                "type": "string"
              },
              "bytes": {
                "type": "integer"
              },
              "embeddedDataUrls": {
                "type": "integer"
              },
              "sha256": {
                "type": "string"
              },
              "suggestedFileName": {
                "type": "string"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "openApp": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}