{
  "openapi": "3.1.0",
  "info": {
    "title": "AMDY Answering Machine Detection API",
    "version": "1.0.0",
    "summary": "Real-time answering machine detection for outbound telephony.",
    "description": "AMDY tells an outbound dialer whether a live person or a voicemail system answered a call, in time to act on it. Audio is streamed to a detection server over a WebSocket and a verdict is returned in under a second, so predictive dialers can route real conversations to agents and drop machines automatically.\n\nThis document describes the REST control surface. Detection itself runs over the WebSocket endpoint described under `x-websocket`, which streams 8 kHz 16-bit little-endian PCM in 320-sample (20 ms) frames.\n\nOnly endpoints verified against the running service are documented here.",
    "contact": { "name": "AMDY", "email": "nick@amdy.io", "url": "https://amdy.io" },
    "license": { "name": "Proprietary", "url": "https://amdy.io/legal/terms" },
    "termsOfService": "https://amdy.io/legal/terms"
  },
  "servers": [{ "url": "https://amdy.io", "description": "Production" }],
  "externalDocs": { "description": "API documentation", "url": "https://amdy.io/docs/api/reference" },
  "x-logo": { "url": "https://amdy.io/icon.svg", "altText": "AMDY" },
  "x-websocket": {
    "url": "wss://api.amdy.io:2700",
    "description": "Detection stream. Send raw 8 kHz 16-bit signed little-endian mono PCM in 320-sample (20 ms) frames. The server replies with a JSON verdict carrying the detection result, the audio duration consumed, and a confidence score.",
    "documentation": "https://amdy.io/docs/api/streaming"
  },
  "tags": [
    { "name": "Service", "description": "Service health." },
    { "name": "Detection", "description": "Per-client detection configuration consumed by detection nodes." },
    { "name": "Provisioning", "description": "Registration of the source IPs permitted to reach the detection service." }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "tags": ["Service"],
        "operationId": "getHealth",
        "summary": "Service health",
        "description": "Liveness probe. Requires no authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is reachable.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/v1/config": {
      "get": {
        "tags": ["Detection"],
        "operationId": "getConfig",
        "summary": "Detection settings for the authenticated client",
        "description": "Returns the detection tuning a node should apply for this client. Detection servers poll this roughly every 60 seconds so sensitivity and maximum detection time changes take effect without a restart.",
        "responses": {
          "200": {
            "description": "Current detection settings.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DetectionConfig" },
                "example": { "clientId": 1042, "detectionSensitivity": 3, "maxDetectionMs": 8000, "updatedAt": "2026-08-28T14:02:11.000Z" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/client-settings": {
      "get": {
        "tags": ["Detection"],
        "operationId": "getClientSettings",
        "summary": "Client settings",
        "description": "Returns the settings associated with the API key presented.",
        "responses": {
          "200": {
            "description": "Client settings.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/v1/ips": {
      "get": {
        "tags": ["Provisioning"],
        "operationId": "listIps",
        "summary": "List registered source IPs",
        "description": "Returns the source IP addresses currently permitted to reach the detection service for this client.",
        "responses": {
          "200": {
            "description": "Registered IP addresses.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/v1/ips/register": {
      "post": {
        "tags": ["Provisioning"],
        "operationId": "registerIp",
        "summary": "Register a source IP",
        "description": "Registers a source IP address so traffic from that host is accepted by the detection service.",
        "responses": {
          "200": {
            "description": "IP registered.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An `amd_live_*` API key presented as `Authorization: Bearer <api_key>`."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The same API key presented as `X-API-Key: <api_key>`. Equivalent to the bearer form."
      }
    },
    "schemas": {
      "DetectionConfig": {
        "type": "object",
        "description": "Detection tuning applied to this client's calls.",
        "properties": {
          "clientId": { "type": "integer", "description": "The client this configuration belongs to." },
          "detectionSensitivity": { "type": "integer", "description": "Detection sensitivity. Higher favours classifying an answer as a machine.", "default": 3 },
          "maxDetectionMs": { "type": "integer", "description": "Upper bound in milliseconds on how long detection may run before returning its best verdict.", "default": 8000 },
          "updatedAt": { "type": ["string", "null"], "format": "date-time", "description": "When these settings last changed." }
        },
        "required": ["clientId", "detectionSensitivity", "maxDetectionMs"]
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string", "description": "Human-readable reason." } },
        "required": ["error"]
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The API key was missing, invalid, or inactive.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Missing Authorization: Bearer <api_key>" }
          }
        }
      },
      "NotFound": {
        "description": "No client matches the presented key.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Client not found" }
          }
        }
      }
    }
  },
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }]
}
