{
  "openapi": "3.1.0",
  "info": {
    "title": "Nech.PL Audio Intelligence API",
    "description": "Stem-aware audio analysis — emotion, features, sample role, loop grading, onsets/tempo, waveform & spectrogram, loudness, naming. The `audio` API of the Nech.PL APIs platform, served at /audio/v1/...; the gateway does central auth + metering (Bearer token).",
    "version": "0.1.0"
  },
  "paths": {
    "/healthz": {
      "get": {
        "tags": [
          "audio"
        ],
        "summary": "Healthz",
        "operationId": "healthz",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "tags": [
          "audio"
        ],
        "summary": "Whoami",
        "description": "Echo the caller's gateway-resolved identity — handy for onboarding smoke tests.",
        "operationId": "whoami",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "tags": [
          "audio"
        ],
        "summary": "Get Job",
        "description": "Poll an async job. 404 if it doesn't exist OR belongs to another tenant\n(we don't leak job existence across accounts).",
        "operationId": "getJob",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Job Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "audio"
        ],
        "summary": "Cancel Job",
        "description": "Cancel a pending/running job (cooperative — a running handler stops at its\nnext progress checkpoint). Idempotent: cancelling a finished job is a no-op.",
        "operationId": "cancelJob",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Job Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/sources": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Submit Source",
        "description": "Fetch a URL's best audio → 44.1k WAV, as an async job. Returns `202` with a\n`job_id`; poll `GET /jobs/{id}` for the source descriptor (content_id +\nartifact_ref). A URL we've already fetched (and whose artifact still exists)\ncomes back as a born-`done` job immediately — no re-download.",
        "operationId": "submitSource",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SourceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/artifacts/{content_id}/{name}": {
      "get": {
        "tags": [
          "audio"
        ],
        "summary": "Get Artifact",
        "description": "Stream a stored artifact (a fetched source WAV, later stems/loops). Any\nauthenticated tenant holding the (opaque, content-addressed) ref may fetch it.\nIn prod the app returns an `X-Accel-Redirect` and nginx streams the bytes\n(Range-capable, zero-copy); in dev it falls back to a direct FileResponse.",
        "operationId": "getArtifact",
        "parameters": [
          {
            "name": "content_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Content Id"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Name"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analyze/emotion": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Analyze Emotion",
        "description": "Upload a short audio clip → valence/arousal + top emotions. Cached (#26):\nthe same audio returns instantly on a repeat instead of recomputing.",
        "operationId": "analyzeEmotion",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_analyzeEmotion"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/features": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Features",
        "description": "Upload a clip → the ~35-dim audio feature stack (spectral moments, MFCCs,\nchroma/key, envelope, + rhythm when `rhythm=true`). Torch-free, cached.",
        "operationId": "features",
        "parameters": [
          {
            "name": "rhythm",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true,
              "title": "Rhythm"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_features"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analyze/samples": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Analyze Samples",
        "description": "Upload a one-shot/loop → per-sample EDA + MEASURED role (percs|bass|melodic|\ntops|atmos) from the spectrum, never the name. Optional `name` only\ndisambiguates breaks/drums. Torch-free, cached.",
        "operationId": "analyzeSamples",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "",
              "title": "Name"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_analyzeSamples"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/onsets": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Onsets",
        "description": "Upload audio → onset hit times (s) + tempo + onset rate. The cheapest tier\n(no model): rhythmic hits for visual sync / slicing. Torch-free, cached.",
        "operationId": "onsets",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_onsets"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/waveform": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Waveform",
        "description": "Upload audio → render-ready waveform: per-bin [min,max] + a 0..1 RMS energy\nenvelope (`?bins=`, ≤4000). For audio-reactive visuals. Torch-free, cached.",
        "operationId": "waveform",
        "parameters": [
          {
            "name": "bins",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 800,
              "title": "Bins"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_waveform"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analyze": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Analyze All",
        "description": "One call → emotion + features + sample role for a clip (fewer round-trips\nfor hexa). Each is the same engine the dedicated routes use. Cached as a unit.",
        "operationId": "analyzeAll",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_analyzeAll"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/grade": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Grade",
        "description": "Upload a loop/one-shot → the Foundry's MECHANICAL quality grade: composite\n0..1 + S/A/B/C/D tier, per-rule sub-scores (seam click, zero-crossing\ncleanliness, DC, bar self-consistency, level, bass mono-compat) + flags.\n`role` ('bass'|'drums'|…) is a hint. WAV/FLAC preferred. Torch-free, cached.",
        "operationId": "grade",
        "parameters": [
          {
            "name": "role",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "",
              "title": "Role"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_grade"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/loudness": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Loudness",
        "description": "Upload audio → BS.1770 integrated LUFS + sample/true peak + crest + the\ngain (dB) to hit each delivery target (-14 streaming, -9 club). Torch-free, cached.",
        "operationId": "loudness",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_loudness"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/spectrum": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Spectrum",
        "description": "FFT-as-a-service: a downsampled, render-ready spectrogram (`bands`×`frames`,\n0..1). `mel=` perceptual vs log-linear; `frames=1` → a single averaged FFT\nspectrum. Bounded payload → caches cheaply. Torch-free.",
        "operationId": "spectrum",
        "parameters": [
          {
            "name": "bands",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 64,
              "title": "Bands"
            }
          },
          {
            "name": "frames",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 200,
              "title": "Frames"
            }
          },
          {
            "name": "mel",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true,
              "title": "Mel"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_spectrum"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/naming": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Naming",
        "description": "Upload a sample → a convention-compliant name (NN_role_character) derived\nfrom the MEASURED role + character, not the file name. Torch-free, cached.",
        "operationId": "naming",
        "parameters": [
          {
            "name": "index",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "title": "Index"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_naming"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/separate": {
      "post": {
        "tags": [
          "audio"
        ],
        "summary": "Separate",
        "description": "Stem separation (demucs/roformer). erable has no GPU and no CPU-viable path,\nso this is a deliberate 503 until a GPU-runner backend is wired (env-selected,\nsame response shape). The route exists now so hexa can code against it and get\na clean, documented \"compute pending\" rather than a 404.",
        "operationId": "separate",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Body_analyzeAll": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_analyzeAll"
      },
      "Body_analyzeEmotion": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_analyzeEmotion"
      },
      "Body_analyzeSamples": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_analyzeSamples"
      },
      "Body_features": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_features"
      },
      "Body_grade": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_grade"
      },
      "Body_loudness": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_loudness"
      },
      "Body_naming": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_naming"
      },
      "Body_onsets": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_onsets"
      },
      "Body_spectrum": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_spectrum"
      },
      "Body_waveform": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_waveform"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "JobStatus": {
        "properties": {
          "job_id": {
            "type": "string",
            "title": "Job Id"
          },
          "type": {
            "type": "string",
            "title": "Type"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "progress": {
            "type": "number",
            "title": "Progress"
          },
          "result": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Result"
          },
          "result_ref": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Result Ref"
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error"
          },
          "created": {
            "type": "number",
            "title": "Created"
          },
          "started": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started"
          },
          "finished": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Finished"
          }
        },
        "type": "object",
        "required": [
          "job_id",
          "type",
          "status",
          "progress",
          "created"
        ],
        "title": "JobStatus",
        "description": "The status of an async job (separate/sources/loops). `result` is populated\nonce `status == \"done\"`; `error` once `status == \"error\"`."
      },
      "SourceRequest": {
        "properties": {
          "url": {
            "type": "string",
            "title": "Url"
          },
          "priority": {
            "type": "integer",
            "title": "Priority",
            "default": 0
          },
          "webhook_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Webhook Url"
          }
        },
        "type": "object",
        "required": [
          "url"
        ],
        "title": "SourceRequest",
        "description": "Submit a URL for audio fetch. `url` is any http(s) URL yt-dlp supports."
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      }
    }
  },
  "servers": [
    {
      "url": "https://api.nech.pl/audio/v1"
    }
  ]
}
