{"openapi":"3.0.3","info":{"title":"H-ear World Enterprise API","description":"Audio noise classification API. Classify audio into 521 sound categories\nwith confidence scores, timestamped events, and Excel reports.\n\n**Quick start:** POST audio (file upload or URL) to /classify\nwith your API key. Get back classifications with confidence scores.\n\n## Authentication\nTwo authentication methods are supported:\n\n**API Key** (server-to-server):\n- `X-NCM-Api-Key: ncm_sk_...` (recommended)\n- `Ocp-Apim-Subscription-Key` (APIM routing)\n- Get a key at https://h-ear.world/enterprise-api/dashboard\n\n**OAuth Bearer Token** (browser/app):\n- `Authorization: Bearer <token>` (JWT validated by APIM)\n- Requires active enterprise subscription for the authenticated user\n\n## Modes\n- **Sync** (default): Omit callbackUrl. API returns results when ready (up to 5 min).\n- **Async**: Include callbackUrl. API returns 202 and POSTs results to your webhook.\n\n## Rate Limits\n| Tier | Price | ML min/mo | Calls/day | Calls/hour |\n|------|-------|-----------|-----------|------------|\n| Starter | $25 AUD | 7,500 | 1,000 | 100 |\n| Enterprise | $125 AUD | 45,000 | 50,000 | 2,000 |\n\nResponse headers: `X-RateLimit-Remaining-Hour`, `X-RateLimit-Remaining-Day`\n\n## Supported Formats\nMP3, WAV, FLAC, OGG, M4A, AAC, MP4, MOV, WEBM | Max 25MB | Max 10 min\nVideo files (MP4, MOV, WebM) are accepted — audio is extracted server-side via FFmpeg.\n","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).\n\n**Sync mode** (default): Omit callbackUrl. Blocks until results ready (up to 5 min).\n**Async mode**: Provide callbackUrl. Returns 202, POSTs results to webhook on completion.\n","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.131}}},"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.131}}}},"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,"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.\nAlways async. Provide callbackUrl for webhook, or poll individual job URLs.\n","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.\nDefaults to the active model's taxonomy (audioset-yamnet-521).\nUse ?taxonomy= to query a specific model's class set.\nNo authentication required.\n\nTaxonomies:\n- **audioset-yamnet-521**: 521 environmental audio classes (YAMNet)\n- **audioset-panns-527**: 527 environmental audio classes (PANNs CNN14)\n- **species**: 6,522 wildlife species (BirdNET)\n","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},"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},"endTime":{"type":"number","description":"Segment end in seconds.","example":3},"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)."}]}