{
  "openapi": "3.1.1",
  "info": {
    "title": "FFT Spectrum Calculator for Numeric Data API",
    "version": "1.0.0",
    "summary": "Paste samples or a time/value table: amplitude and phase spectrum, peaks and a CSV.",
    "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://prooflinefftspectrum.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Paste samples or a time/value table: amplitude and phase spectrum, peaks and a CSV.",
        "description": "Computes the discrete Fourier transform of numeric samples of any length (exact, no truncation) and shows the single-sided amplitude and phase spectrum with a proper frequency axis, the strongest peaks with interpolated frequencies, a plot of the signal and the spectrum, and a CSV of every bin. Windows: rectangular, Hann or Hamming; optional mean removal and zero padding. For audio files use the Soundscape spectrum analyzer; this tool takes numbers. Part of the Proofline math notebook.\n\nMethodology:\n- One FFT in the code base: the radix-2 Cooley–Tukey FFT of the shared audio engine (Soundscape). Lengths that are not a power of two are transformed exactly with Bluestein’s chirp-z algorithm, whose convolution runs on that same FFT.\n- Single-sided amplitude: 2·|X_k| / Σw (|X_0| / Σw and the Nyquist bin without the factor 2), so a sinusoid that falls on a bin shows its true amplitude with any window. Phase atan2(Im, Re) in degrees, relative to a cosine starting at the first sample; set to 0 where the amplitude is below 10⁻⁹ of the maximum.\n- Frequency of bin k is k·fs/n. Peaks are local maxima; their frequency is refined by a parabola through the logarithms of the three neighbouring amplitudes.\n- Verified against a direct O(n²) DFT for power-of-two and other lengths, Parseval’s theorem and the exact amplitudes and phases of test sinusoids.\n\nFrequencies above half the sample rate fold back (aliasing) and cannot be told apart from lower ones. A finite record smears frequencies that do not fall on a bin (leakage); windows reduce but do not remove this.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "proofline_fft_spectrum",
        "x-owner-app": "proofline",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProoflineFftSpectrumInput"
              },
              "examples": {
                "example1": {
                  "summary": "5 Hz sine + 12 Hz cosine at 100 Hz",
                  "value": {
                    "samples": "0.500000\n0.673501\n0.619181\n0.490305\n0.454999\n0.595492\n0.857366\n1.076930\n1.072077\n0.747170\n0.154508\n-0.521907\n-1.052673\n-1.273905\n-1.163946\n-0.845492\n-0.512903\n-0.324725\n-0.319872\n-0.402708\n-0.404508\n-0.187040\n0.269073\n0.840412\n1.315541\n1.500000\n1.315541\n0.840412\n0.269073\n-0.187040\n-0.404508\n-0.402708\n-0.319872\n-0.324725\n-0.512903\n-0.845492\n-1.163946\n-1.273905\n-1.052673\n-0.521907\n0.154508\n0.747170\n1.072077\n1.076930\n0.857366\n0.595492\n0.454999\n0.490305\n0.619181\n0.673501\n0.500000\n0.055467\n-0.556390\n-1.127729\n-1.447114\n-1.404508\n-1.044747\n-0.541104\n-0.103494\n0.129136\n0.154508\n0.096127\n0.122897\n0.344129\n0.738167\n1.154508\n1.389210\n1.293309\n0.855699\n0.215326\n-0.404508\n-0.805074\n-0.906497\n-0.777622\n-0.586572\n-0.500000\n-0.586572\n-0.777622\n-0.906497\n-0.805074\n-0.404508\n0.215326\n0.855699\n1.293309\n1.389210\n1.154508\n0.738167\n0.344129\n0.122897\n0.096127\n0.154508\n0.129136\n-0.103494\n-0.541104\n-1.044747\n-1.404508\n-1.447114\n-1.127729\n-0.556390\n0.055467\n0.500000\n0.673501\n0.619181\n0.490305\n0.454999\n0.595492\n0.857366\n1.076930\n1.072077\n0.747170\n0.154508\n-0.521907\n-1.052673\n-1.273905\n-1.163946\n-0.845492\n-0.512903\n-0.324725\n-0.319872\n-0.402708\n-0.404508\n-0.187040\n0.269073\n0.840412\n1.315541\n1.500000\n1.315541\n0.840412\n0.269073\n-0.187040\n-0.404508\n-0.402708\n-0.319872\n-0.324725\n-0.512903\n-0.845492\n-1.163946\n-1.273905\n-1.052673\n-0.521907\n0.154508\n0.747170\n1.072077\n1.076930\n0.857366\n0.595492\n0.454999\n0.490305\n0.619181\n0.673501\n0.500000\n0.055467\n-0.556390\n-1.127729\n-1.447114\n-1.404508\n-1.044747\n-0.541104\n-0.103494\n0.129136\n0.154508\n0.096127\n0.122897\n0.344129\n0.738167\n1.154508\n1.389210\n1.293309\n0.855699\n0.215326\n-0.404508\n-0.805074\n-0.906497\n-0.777622\n-0.586572\n-0.500000\n-0.586572\n-0.777622\n-0.906497\n-0.805074\n-0.404508\n0.215326\n0.855699\n1.293309\n1.389210\n1.154508\n0.738167\n0.344129\n0.122897\n0.096127\n0.154508\n0.129136\n-0.103494\n-0.541104\n-1.044747\n-1.404508\n-1.447114\n-1.127729\n-0.556390\n0.055467",
                    "sampleRate": 100,
                    "window": "rectangular"
                  }
                },
                "example2": {
                  "summary": "Time/value table (sample rate from the time column)",
                  "value": {
                    "samples": "time,value\n0,1\n0.25,0\n0.5,-1\n0.75,0\n1,1\n1.25,0\n1.5,-1\n1.75,0",
                    "window": "rectangular",
                    "removeMean": false
                  }
                },
                "example3": {
                  "summary": "Same signal with a Hann window and zero padding",
                  "value": {
                    "samples": "0.500000\n0.673501\n0.619181\n0.490305\n0.454999\n0.595492\n0.857366\n1.076930\n1.072077\n0.747170\n0.154508\n-0.521907\n-1.052673\n-1.273905\n-1.163946\n-0.845492\n-0.512903\n-0.324725\n-0.319872\n-0.402708\n-0.404508\n-0.187040\n0.269073\n0.840412\n1.315541\n1.500000\n1.315541\n0.840412\n0.269073\n-0.187040\n-0.404508\n-0.402708\n-0.319872\n-0.324725\n-0.512903\n-0.845492\n-1.163946\n-1.273905\n-1.052673\n-0.521907\n0.154508\n0.747170\n1.072077\n1.076930\n0.857366\n0.595492\n0.454999\n0.490305\n0.619181\n0.673501\n0.500000\n0.055467\n-0.556390\n-1.127729\n-1.447114\n-1.404508\n-1.044747\n-0.541104\n-0.103494\n0.129136\n0.154508\n0.096127\n0.122897\n0.344129\n0.738167\n1.154508\n1.389210\n1.293309\n0.855699\n0.215326\n-0.404508\n-0.805074\n-0.906497\n-0.777622\n-0.586572\n-0.500000\n-0.586572\n-0.777622\n-0.906497\n-0.805074\n-0.404508\n0.215326\n0.855699\n1.293309\n1.389210\n1.154508\n0.738167\n0.344129\n0.122897\n0.096127\n0.154508\n0.129136\n-0.103494\n-0.541104\n-1.044747\n-1.404508\n-1.447114\n-1.127729\n-0.556390\n0.055467\n0.500000\n0.673501\n0.619181\n0.490305\n0.454999\n0.595492\n0.857366\n1.076930\n1.072077\n0.747170\n0.154508\n-0.521907\n-1.052673\n-1.273905\n-1.163946\n-0.845492\n-0.512903\n-0.324725\n-0.319872\n-0.402708\n-0.404508\n-0.187040\n0.269073\n0.840412\n1.315541\n1.500000\n1.315541\n0.840412\n0.269073\n-0.187040\n-0.404508\n-0.402708\n-0.319872\n-0.324725\n-0.512903\n-0.845492\n-1.163946\n-1.273905\n-1.052673\n-0.521907\n0.154508\n0.747170\n1.072077\n1.076930\n0.857366\n0.595492\n0.454999\n0.490305\n0.619181\n0.673501\n0.500000\n0.055467\n-0.556390\n-1.127729\n-1.447114\n-1.404508\n-1.044747\n-0.541104\n-0.103494\n0.129136\n0.154508\n0.096127\n0.122897\n0.344129\n0.738167\n1.154508\n1.389210\n1.293309\n0.855699\n0.215326\n-0.404508\n-0.805074\n-0.906497\n-0.777622\n-0.586572\n-0.500000\n-0.586572\n-0.777622\n-0.906497\n-0.805074\n-0.404508\n0.215326\n0.855699\n1.293309\n1.389210\n1.154508\n0.738167\n0.344129\n0.122897\n0.096127\n0.154508\n0.129136\n-0.103494\n-0.541104\n-1.044747\n-1.404508\n-1.447114\n-1.127729\n-0.556390\n0.055467",
                    "sampleRate": 100,
                    "window": "hann",
                    "zeroPad": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProoflineFftSpectrumResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "ProoflineFftSpectrumInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "samples": {
            "title": "Samples",
            "description": "One value per line (or separated by spaces/commas), or two columns time, value with equal time steps (the sample rate then follows from the time column). At most 16384 samples.",
            "type": "string",
            "maxLength": 400000
          },
          "sampleRate": {
            "title": "Sample rate",
            "description": "Samples per second (or per unit of your x axis). Frequencies are reported in the same unit.",
            "type": "number",
            "minimum": 0,
            "default": 1,
            "x-unit": "Hz"
          },
          "window": {
            "title": "Window",
            "type": "string",
            "enum": [
              "hann",
              "hamming",
              "rectangular"
            ],
            "default": "hann"
          },
          "removeMean": {
            "title": "Remove the mean (DC) first",
            "type": "boolean",
            "default": true
          },
          "zeroPad": {
            "title": "Zero-pad to the next power of two",
            "description": "Interpolates the spectrum on a finer grid; does not add real resolution.",
            "type": "boolean",
            "default": false
          },
          "peaks": {
            "title": "Peaks to list",
            "type": "integer",
            "minimum": 0,
            "maximum": 50,
            "default": 5
          }
        },
        "required": [
          "samples"
        ]
      },
      "ProoflineFftSpectrumResult": {
        "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": [
              "samples",
              "n",
              "sampleRate",
              "df",
              "nyquist",
              "window",
              "mean",
              "timeColumn",
              "peaks",
              "frequency",
              "amplitude",
              "phaseDeg",
              "parseval"
            ],
            "properties": {
              "samples": {
                "type": "integer"
              },
              "n": {
                "type": "integer",
                "description": "Transform length after optional zero padding."
              },
              "sampleRate": {
                "type": "number"
              },
              "df": {
                "type": "number"
              },
              "nyquist": {
                "type": "number"
              },
              "window": {
                "type": "string",
                "enum": [
                  "rectangular",
                  "hann",
                  "hamming"
                ]
              },
              "mean": {
                "type": "number"
              },
              "timeColumn": {
                "type": "boolean"
              },
              "peaks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "frequency",
                    "interpolatedFrequency",
                    "amplitude",
                    "phaseDeg",
                    "bin"
                  ],
                  "properties": {
                    "frequency": {
                      "type": "number"
                    },
                    "interpolatedFrequency": {
                      "type": "number"
                    },
                    "amplitude": {
                      "type": "number"
                    },
                    "phaseDeg": {
                      "type": "number"
                    },
                    "bin": {
                      "type": "integer"
                    }
                  }
                }
              },
              "frequency": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "amplitude": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "phaseDeg": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "parseval": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "time",
                  "frequency"
                ],
                "properties": {
                  "time": {
                    "type": "number"
                  },
                  "frequency": {
                    "type": "number"
                  }
                },
                "description": "Σx² and Σ|X|²/n of the windowed signal: equal by Parseval’s theorem."
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}