openapi: 3.0.3
info:
  title: H-ear World Enterprise API
  description: |
    Audio noise classification API. Classify audio into 521 sound categories
    with confidence scores, timestamped events, and Excel reports.

    **Quick start:** POST audio (file upload or URL) to /classify
    with your API key. Get back classifications with confidence scores.

    ## Authentication
    Two authentication methods are supported:

    **API Key** (server-to-server):
    - `X-NCM-Api-Key: ncm_sk_...` (recommended)
    - `Ocp-Apim-Subscription-Key` (APIM routing)
    - Get a key at https://h-ear.world/enterprise-api/dashboard

    **OAuth Bearer Token** (browser/app):
    - `Authorization: Bearer <token>` (JWT validated by APIM)
    - Requires active enterprise subscription for the authenticated user

    ## Modes
    - **Sync** (default): Omit callbackUrl. API returns results when ready (up to 5 min).
    - **Async**: Include callbackUrl. API returns 202 and POSTs results to your webhook.

    ## Rate Limits
    | Tier | Price | ML min/mo | Calls/day | Calls/hour |
    |------|-------|-----------|-----------|------------|
    | Starter | $25 AUD | 7,500 | 1,000 | 100 |
    | Enterprise | $125 AUD | 45,000 | 50,000 | 2,000 |

    Response headers: `X-RateLimit-Remaining-Hour`, `X-RateLimit-Remaining-Day`

    ## Supported Formats
    MP3, WAV, FLAC, OGG, M4A, AAC, MP4, MOV, WEBM | Max 25MB | Max 10 min
    Video files (MP4, MOV, WebM) are accepted — audio is extracted server-side via FFmpeg.
  version: 1.0.0
  contact:
    name: H-ear World Support
    email: support@h-ear.world
    url: https://h-ear.world
  license:
    name: Proprietary
    url: https://h-ear.world/terms

externalDocs:
  description: Enterprise API Guide
  url: https://h-ear.world/docs/enterprise-api

servers:
  - url: https://ncm-apim.azure-api.net/v1
    description: Production (APIM)
  - url: https://api-dev.h-ear.world/api/v1
    description: Development
  - url: https://api-staging.h-ear.world/api/v1
    description: Staging
  - url: http://localhost:7071/api/v1
    description: Local

security:
  - NcmApiKey: []
  - ApimKey: []
  - BearerAuth: []

paths:
  /classify:
    post:
      operationId: classifyAudio
      summary: Classify audio
      description: |
        Send audio for noise classification. Accepts multipart/form-data (file upload) or JSON (URL).

        **Sync mode** (default): Omit callbackUrl. Blocks until results ready (up to 5 min).
        **Async mode**: Provide callbackUrl. Returns 202, POSTs results to webhook on completion.
      tags:
        - Classification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                  description: Public URL to audio file.
                fileName:
                  type: string
                  description: Filename for format detection.
                  example: recording.wav
                callbackUrl:
                  type: string
                  format: uri
                  description: HTTPS webhook URL. If set, API returns 202 and POSTs results here.
                  example: https://your-server.com/webhook/ncm
                callbackSecret:
                  type: string
                  description: HMAC-SHA256 secret for webhook X-NCM-Signature header.
                threshold:
                  type: number
                  minimum: 0
                  maximum: 1
                  default: 0.3
                  description: Confidence threshold (0.0-1.0). Classes below this are excluded.
                latitude:
                  type: number
                  format: double
                  description: GPS latitude for location tagging.
                  example: -35.2802
                longitude:
                  type: number
                  format: double
                  description: GPS longitude for location tagging.
                  example: 149.1310
            examples:
              syncUrl:
                summary: Classify from URL (sync)
                value:
                  url: https://example.com/audio/site-noise.wav
                  threshold: 0.3
              asyncWebhook:
                summary: Async with webhook
                value:
                  url: https://example.com/audio/long-recording.wav
                  callbackUrl: https://your-server.com/webhook/ncm
                  callbackSecret: whsec_your_secret
                  latitude: -35.2802
                  longitude: 149.1310
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: Audio or video file (MP3, WAV, FLAC, OGG, M4A, AAC, MP4, MOV, WEBM). Audio extracted from video server-side.
                callbackUrl:
                  type: string
                  format: uri
                callbackSecret:
                  type: string
                threshold:
                  type: number
                  minimum: 0
                  maximum: 1
                  default: 0.3
                latitude:
                  type: number
                longitude:
                  type: number
      responses:
        '200':
          description: Classification complete (sync mode).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClassificationResult'
              example:
                requestId: req_a1b2c3d4
                duration: 30.0
                processingTimeMs: 4200
                eventCount: 3
                classifications:
                  - class: Dog bark
                    confidence: 0.94
                    category: Animal
                  - class: Traffic noise
                    confidence: 0.67
                    category: Sounds of things
                  - class: Wind
                    confidence: 0.42
                    category: Natural sounds
                reportUrl: https://ncmstorage.blob.core.windows.net/reports/req_a1b2c3d4.xlsx?sv=...
        '202':
          description: Accepted for async processing (callbackUrl was provided).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AsyncAccepted'
              example:
                requestId: req_a1b2c3d4
                status: processing
                message: Job queued. Results will be POSTed to your callback URL.
                callbackUrl: https://your-server.com/webhook/ncm
                pollUrl: /api/jobs/req_a1b2c3d4
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '408':
          description: Sync timeout (file too large for sync — use async callbackUrl).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Processing timeout. For longer files, use the async flow.
                code: TIMEOUT
                requestId: req_a1b2c3d4
                hint: Poll GET /jobs/{requestId} for status
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'

  /classify/batch:
    post:
      operationId: classifyAudioBatch
      summary: Classify multiple audio files
      description: |
        Submit up to 50 audio files for batch classification.
        Always async. Provide callbackUrl for webhook, or poll individual job URLs.
      tags:
        - Classification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - files
              properties:
                files:
                  type: array
                  maxItems: 50
                  description: Audio files to classify (max 50). Each requires a URL.
                  items:
                    type: object
                    required: [url]
                    properties:
                      url:
                        type: string
                        format: uri
                        description: Public URL to audio file.
                      fileName:
                        type: string
                        description: Filename for format detection.
                      metadata:
                        type: object
                        description: Arbitrary key-value metadata returned in results.
                        additionalProperties:
                          type: string
                callbackUrl:
                  type: string
                  format: uri
                  description: HTTPS webhook URL. Results POSTed here when all files complete. Optional — poll jobIds if omitted.
                callbackSecret:
                  type: string
                  description: HMAC-SHA256 secret for X-NCM-Signature header.
                threshold:
                  type: number
                  default: 0.3
            example:
              files:
                - url: https://example.com/audio/sample1.wav
                - url: https://example.com/audio/sample2.wav
              callbackUrl: https://your-server.com/webhook/ncm-batch
              threshold: 0.3
      responses:
        '202':
          description: Batch accepted for processing.
          content:
            application/json:
              schema:
                type: object
                properties:
                  batchId:
                    type: string
                    example: batch_x9y8z7
                  fileCount:
                    type: integer
                    description: Number of files accepted (may be less than submitted if some failed validation).
                    example: 2
                  jobIds:
                    type: array
                    items:
                      type: string
                    description: Individual job IDs for polling.
                    example: [job_abc123, job_def456]
                  status:
                    type: string
                    enum: [processing]
                  estimatedCompletionMinutes:
                    type: integer
                    example: 3
                  message:
                    type: string
                    example: Batch queued. Results will be POSTed to your callback URL when all files complete.
                  pollUrls:
                    type: array
                    items:
                      type: string
                    description: URLs to poll for individual job status.
                    example: [/api/jobs/job_abc123, /api/jobs/job_def456]
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'

  /classes:
    get:
      operationId: listClasses
      summary: List audio classes
      description: |
        Returns audio classification classes for a taxonomy.
        Defaults to the active model's taxonomy (audioset-yamnet-521).
        Use ?taxonomy= to query a specific model's class set.
        No authentication required.

        Taxonomies:
        - **audioset-yamnet-521**: 521 environmental audio classes (YAMNet)
        - **audioset-panns-527**: 527 environmental audio classes (PANNs CNN14)
        - **species**: 6,522 wildlife species (BirdNET)
      tags:
        - Reference
      security: []
      parameters:
        - name: taxonomy
          in: query
          description: Taxonomy to list classes for. Defaults to active model taxonomy.
          schema:
            type: string
            enum: [audioset-yamnet-521, audioset-panns-527, species]
        - name: category
          in: query
          description: Filter by top-level category (e.g. Animal, Aves). Case-insensitive partial match.
          schema:
            type: string
            example: Animal
        - name: limit
          in: query
          description: Max classes to return (pagination). 0 or omit for all.
          schema:
            type: integer
            default: 0
        - name: offset
          in: query
          description: Skip N classes (pagination).
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of audio classes for the requested taxonomy.
          content:
            application/json:
              schema:
                type: object
                properties:
                  taxonomy:
                    type: string
                    description: Active taxonomy for this response.
                    example: audioset-yamnet-521
                  classes:
                    type: array
                    items:
                      $ref: '#/components/schemas/AudioClass'
                  total:
                    type: integer
                    description: Classes in this response (after pagination).
                    example: 521
                  totalFiltered:
                    type: integer
                    description: Total matching classes (before pagination).
                    example: 521
                  totalAvailable:
                    type: integer
                    description: Total classes in this taxonomy.
                    example: 521
                  categories:
                    type: array
                    items:
                      type: string
                    example: [Human sounds, Animal, Music, Natural sounds, Sounds of things, Source-ambiguous sounds, Channel environment and background]
                  availableTaxonomies:
                    type: array
                    items:
                      type: string
                    description: All taxonomies available from loaded models.
                    example: [audioset-yamnet-521, audioset-panns-527, species]
                  pagination:
                    type: object
                    description: Present when limit or offset is specified.
                    properties:
                      offset:
                        type: integer
                      limit:
                        type: integer
                      hasMore:
                        type: boolean
        '400':
          description: Unknown taxonomy requested.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "Unknown taxonomy: invalid"
                code: INVALID_TAXONOMY
                availableTaxonomies: [audioset-yamnet-521, audioset-panns-527, species]

  /health:
    get:
      operationId: healthCheck
      summary: Health check
      description: Liveness check. No authentication required.
      tags:
        - System
      security: []
      responses:
        '200':
          description: API is healthy.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [healthy]
                    example: healthy
                  version:
                    type: string
                    example: 1.0.0
                  deployedTimestamp:
                    type: string
                    format: date-time
                    description: When this API instance started.

  /openapi:
    get:
      operationId: getOpenApiSpec
      summary: This specification
      description: Returns the OpenAPI 3.0 spec (YAML default, JSON via Accept header or ?format=json).
      tags:
        - System
      security: []
      responses:
        '200':
          description: OpenAPI specification.
          content:
            application/x-yaml:
              schema:
                type: string
            application/json:
              schema:
                type: object

components:
  securitySchemes:
    NcmApiKey:
      type: apiKey
      in: header
      name: X-NCM-Api-Key
      description: Enterprise API key (format ncm_sk_...). Get one at https://h-ear.world/enterprise-api/dashboard
    ApimKey:
      type: apiKey
      in: header
      name: Ocp-Apim-Subscription-Key
      description: Azure APIM subscription key (alternative to X-NCM-Api-Key).
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth Bearer token (JWT validated by APIM). Requires active enterprise subscription.

  schemas:
    ClassificationResult:
      type: object
      description: Sync classification response with detected audio classes.
      properties:
        requestId:
          type: string
          description: Unique request ID for tracking and support.
          example: req_a1b2c3d4
        duration:
          type: number
          description: Audio duration in seconds.
          example: 30.0
        classifications:
          type: array
          description: Detected classes ordered by confidence.
          items:
            $ref: '#/components/schemas/Classification'
        eventCount:
          type: integer
          description: Total distinct noise events detected.
          example: 3
        processingTimeMs:
          type: integer
          description: Server-side processing time in milliseconds.
          example: 4200
        reportUrl:
          type: string
          format: uri
          description: Signed URL to Excel report (valid 7 days).
          example: https://ncmstorage.blob.core.windows.net/reports/req_a1b2c3d4.xlsx?sv=...
        noiseEvents:
          type: array
          description: Timestamped noise events with labels and confidence.
          items:
            type: object
            properties:
              label:
                type: string
                description: Noise event label.
                example: Dog bark
              startTime:
                type: number
                description: Event start time in seconds.
                example: 2.3
              confidence:
                type: number
                description: Event confidence (0.0 to 1.0).
                example: 0.94
        timestamps:
          type: array
          description: Per-second classification breakdown (when includeTimestamps=true).
          items:
            $ref: '#/components/schemas/TimestampedClassification'

    AsyncAccepted:
      type: object
      description: Async acceptance response (202). Results will be POSTed to callbackUrl.
      properties:
        requestId:
          type: string
          example: req_a1b2c3d4
        status:
          type: string
          enum: [processing]
        message:
          type: string
          example: Job queued. Results will be POSTed to your callback URL.
        callbackUrl:
          type: string
          format: uri
        pollUrl:
          type: string
          description: Poll this URL for job status.
          example: /api/jobs/req_a1b2c3d4

    Classification:
      type: object
      properties:
        class:
          type: string
          description: Audio class label from the 521-class taxonomy.
          example: Dog bark
        confidence:
          type: number
          minimum: 0
          maximum: 1
          description: Confidence score (0.0 to 1.0).
          example: 0.94
        category:
          type: string
          description: Parent category.
          example: Animal
          enum: [Human sounds, Animal, Music, Natural sounds, Sounds of things, Source-ambiguous sounds, Channel/environment]

    TimestampedClassification:
      type: object
      properties:
        startTime:
          type: number
          description: Segment start in seconds.
          example: 2.0
        endTime:
          type: number
          description: Segment end in seconds.
          example: 3.0
        classifications:
          type: array
          items:
            $ref: '#/components/schemas/Classification'

    AudioClass:
      type: object
      description: Audio class entry. Fields vary by taxonomy (AudioSet has tier fields, Species has species fields).
      properties:
        index:
          type: integer
          description: Model output index.
          example: 70
        name:
          type: string
          description: Human-readable class name.
          example: Bark
        category:
          type: string
          description: Top-level category (tier1 for AudioSet, taxonomicGroup for Species).
          example: Animal
        tier1:
          type: string
          description: AudioSet tier 1 category. Present for audioset-* taxonomies only.
          example: Animal
        tier2:
          type: string
          description: AudioSet tier 2 subcategory.
          example: Domestic animals, pets
        tierDepth:
          type: integer
          description: Number of populated tiers (1-6). AudioSet only.
          example: 4
        speciesScientific:
          type: string
          description: Scientific species name. Present for species taxonomy only.
          example: Corvus corax
        speciesCommon:
          type: string
          description: Common species name. Species taxonomy only.
          example: Common Raven
        taxonomicGroup:
          type: string
          description: Taxonomic group (Aves, Mammalia, Amphibia, Insecta). Species taxonomy only.
          example: Aves

    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
          example: Invalid audio format
        code:
          type: string
          description: Machine-readable error code.
          example: INVALID_FORMAT
          enum: [INVALID_FORMAT, MISSING_AUDIO, FILE_TOO_LARGE, INVALID_API_KEY, MISSING_API_KEY, NO_SUBSCRIPTION, AUTH_FAILED, QUOTA_EXCEEDED, RATE_LIMITED, TIMEOUT, PROCESSING_FAILED]
        requestId:
          type: string
          example: req_a1b2c3d4

  responses:
    BadRequest:
      description: Invalid request (missing audio, bad format, file too large).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: No audio provided. Use multipart/form-data file upload or JSON with url field.
            code: MISSING_AUDIO
            requestId: req_a1b2c3d4

    Unauthorized:
      description: Missing or invalid authentication. Supports API key (X-NCM-Api-Key) or OAuth Bearer token (Authorization header with X-User-Id injected by APIM).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missing_key:
              summary: No authentication provided
              value:
                error: Missing authentication. Provide Bearer token or X-NCM-Api-Key header.
                code: MISSING_API_KEY
                requestId: req_a1b2c3d4
            invalid_key:
              summary: Invalid API key
              value:
                error: API key not found or revoked
                code: INVALID_API_KEY
                requestId: req_a1b2c3d4
            no_subscription:
              summary: OAuth user without enterprise subscription
              value:
                error: No active subscription for this user
                code: NO_SUBSCRIPTION
                requestId: req_a1b2c3d4

    RateLimited:
      description: Rate limit or quota exceeded. Retry after the indicated period.
      headers:
        Retry-After:
          schema:
            type: integer
            example: 60
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Hourly rate limit exceeded
            code: RATE_LIMITED
            requestId: req_a1b2c3d4

    ServerError:
      description: Server error. Include requestId when contacting support.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Internal server error
            code: PROCESSING_FAILED
            requestId: req_a1b2c3d4

tags:
  - name: Classification
    description: Audio noise classification (single, batch, sync, async).
  - name: Reference
    description: Taxonomy and reference data (no auth required).
  - name: System
    description: Health, spec, and operational endpoints (no auth required).
