{"openapi":"3.1.0","info":{"title":"Muzzle Biometric Verification API","description":"Cattle muzzle biometric verification API. Send 3 reference photos of a known animal and 1 probe photo to verify identity. Uses a triplet loss siamese ensemble of 3 models for robust verification with out-of-domain detection.\n\n## Authentication\nAll `/predict`, `/domain-check`, and `/encode` requests require an `X-API-Key` header. Use the Authorize button below to set your API key.\n\n## Rate Limiting\nRequests to `/predict`, `/domain-check`, and `/encode` are limited to 30 per 60s per IP. Exceeded requests return HTTP 429 with a `Retry-After` header.","contact":{"name":"Muzzle Biometric Support","email":""},"license":{"name":"Proprietary"},"version":"2.1.0"},"paths":{"/health":{"get":{"tags":["Health"],"summary":"Health","description":"Check API health and model loading status.\n\nReturns whether the service is operational and which ML models are loaded.\nUse this endpoint as a readiness probe before calling any inference endpoint.\n\n## Response Fields\n| Field | Type | Description |\n|---|---|---|\n| `status` | string | `ok` when the API is ready for inference, `degraded` when the API responds but models are not ready |\n| `models_loaded` | array[int] | List of loaded model IDs. Expected steady state: `[60, 62, 64]` |\n\n## Operational Guidance\n- `status = ok` and `models_loaded = [60, 62, 64]`: normal operating state\n- `status = degraded` and `models_loaded = []`: inference endpoints will return **503 Models not loaded**\n- Partial model lists indicate an unexpected startup state and should be investigated\n\n## Authentication\nThis endpoint does not require `X-API-Key`.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/predict":{"post":{"tags":["Verification"],"summary":"Predict Endpoint","operationId":"predict_endpoint_predict_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictResponse"}}}},"401":{"description":"API Key invalid or missing"},"422":{"description":"Invalid payload (missing images, oversized, wrong format)"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal processing error"},"503":{"description":"Models not loaded"}},"security":[{"ApiKey":[]}]}},"/predict/file":{"post":{"tags":["Verification"],"summary":"Predict File Endpoint","operationId":"predict_file_endpoint_predict_file_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_predict_file_endpoint_predict_file_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictResponse"}}}},"401":{"description":"API Key invalid or missing"},"422":{"description":"Invalid file (wrong format, oversized, missing)"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal processing error"},"503":{"description":"Models not loaded"}},"security":[{"ApiKey":[]}]}},"/encode":{"post":{"tags":["Encode"],"summary":"Encode Endpoint","description":"Generate ML embedding codes for 1 to 12 images (JSON base64 input).\n\nUses all 3 siamese models (60, 62, 64) to produce per-model 128-dimensional codes\nand one 384-dimensional concatenated ensemble code for each image.\n\n## When To Use This Endpoint\n- Backends that already store or transport images as base64 strings\n- Batch enrollment flows that want to encode multiple images in one request\n- Integrations that need deterministic output order through `index`\n\n## Input\n- `photos[]`: list of base64 images (minimum 1, maximum 12)\n- Input order is preserved in the response through `images[].index`\n\n## Output Per Image\n- `index`: original input order (0-based)\n- `filename`: generated identifier (`photo_{index}`)\n- `decision`: domain-check verdict for this image\n- `domain_summary`: criterion-level domain-check explanation\n- `model_codes[]`: individual per-model embedding code (128 dims each)\n- `ensemble_code`: concatenated code (384 dims)\n\n## Response Semantics\n- `decision.verdict = VALID_MUZZLE`: image passed domain validation before encoding\n- `decision.verdict = INVALID_INPUT`: image failed domain validation, but its codes are still returned for traceability/debugging\n- `model_codes[]`: always 3 entries, one for each model ID (60, 62, 64)\n- `ensemble_code`: concatenation of the 3 model codes, not an average\n\n## Errors\n- **401**: Invalid or missing API key\n- **422**: Invalid payload, too many images, oversized image string, or malformed input\n- **429**: Rate limit exceeded\n- **500**: Internal processing error\n- **503**: Models not loaded\n\n## Authentication\nRequires `X-API-Key` header when API_KEY is configured.","operationId":"encode_endpoint_encode_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncodeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncodeResponse"}}}},"401":{"description":"API Key invalid or missing"},"422":{"description":"Invalid payload (missing images, oversized, wrong format)"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal processing error"},"503":{"description":"Models not loaded"}},"security":[{"ApiKey":[]}]}},"/encode/file":{"post":{"tags":["Encode"],"summary":"Encode File Endpoint","operationId":"encode_file_endpoint_encode_file_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_encode_file_endpoint_encode_file_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncodeResponse"}}}},"401":{"description":"API Key invalid or missing"},"422":{"description":"Invalid file (wrong format, oversized, missing)"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal processing error"},"503":{"description":"Models not loaded"}},"security":[{"ApiKey":[]}]}},"/domain-check":{"post":{"tags":["Domain Check"],"summary":"Domain Check Endpoint","operationId":"domain_check_endpoint_domain_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCheckResponse"}}}},"401":{"description":"API Key invalid or missing"},"422":{"description":"Invalid payload (missing image, oversized, wrong format)"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal processing error"},"503":{"description":"Models not loaded"}},"security":[{"ApiKey":[]}]}},"/domain-check/file":{"post":{"tags":["Domain Check"],"summary":"Domain Check File Endpoint","operationId":"domain_check_file_endpoint_domain_check_file_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_domain_check_file_endpoint_domain_check_file_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCheckResponse"}}}},"401":{"description":"API Key invalid or missing"},"422":{"description":"Invalid file (wrong format, oversized, missing)"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal processing error"},"503":{"description":"Models not loaded"}},"security":[{"ApiKey":[]}]}}},"components":{"schemas":{"Body_domain_check_file_endpoint_domain_check_file_post":{"properties":{"photo":{"type":"string","contentMediaType":"application/octet-stream","title":"Photo","description":"Muzzle image to validate (JPEG or PNG)"}},"type":"object","required":["photo"],"title":"Body_domain_check_file_endpoint_domain_check_file_post"},"Body_encode_file_endpoint_encode_file_post":{"properties":{"photos":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Photos","description":"One to twelve muzzle images (JPEG or PNG)"}},"type":"object","required":["photos"],"title":"Body_encode_file_endpoint_encode_file_post"},"Body_predict_file_endpoint_predict_file_post":{"properties":{"reference_photo_1":{"type":"string","contentMediaType":"application/octet-stream","title":"Reference Photo 1","description":"First reference muzzle image (JPEG or PNG)"},"reference_photo_2":{"type":"string","contentMediaType":"application/octet-stream","title":"Reference Photo 2","description":"Second reference muzzle image (JPEG or PNG)"},"reference_photo_3":{"type":"string","contentMediaType":"application/octet-stream","title":"Reference Photo 3","description":"Third reference muzzle image (JPEG or PNG)"},"probe_photo":{"type":"string","contentMediaType":"application/octet-stream","title":"Probe Photo","description":"Probe muzzle image to verify (JPEG or PNG)"}},"type":"object","required":["reference_photo_1","reference_photo_2","reference_photo_3","probe_photo"],"title":"Body_predict_file_endpoint_predict_file_post"},"CriterionResult":{"properties":{"metric":{"type":"string","title":"Metric","description":"Name of the evaluated metric. Common values: 'cosine_similarity', 'euclidean_distance', 'mutual_similarity', 'domain_check', 'embedding_variance', 'cross_model_consistency'. Use this to programmatically identify which criterion a result refers to."},"value":{"type":"number","title":"Value","description":"Observed value of the metric for this specific prediction."},"threshold":{"type":"number","title":"Threshold","description":"Minimum (for cosine similarity, mutual_similarity, embedding_variance) or maximum (for euclidean_distance, cross_model_consistency) threshold for this criterion to pass. Compare 'value' against 'threshold' using the direction indicated by 'metric' name."},"passed":{"type":"boolean","title":"Passed","description":"True if the criterion was satisfied (value meets threshold). False if the criterion failed. ALL criteria must pass for a MATCH verdict. Use this field to programmatically determine the outcome without parsing 'description'."},"description":{"type":"string","title":"Description","description":"Human-readable explanation of what was checked, the observed value, the threshold, and whether it passed. Use this for display to end users (e.g., 'The cosine similarity (0.88) meets the required threshold (0.62)')."}},"type":"object","required":["metric","value","threshold","passed","description"],"title":"CriterionResult","examples":[{"description":"Cosine similarity between probe and reference template is 0.15, below the required 0.62","metric":"cosine_similarity","passed":false,"threshold":0.62,"value":0.15}]},"Decision":{"properties":{"verdict":{"type":"string","title":"Verdict","description":"Final decision on whether the probe photo matches the reference animal. One of: 'MATCH' (probe is likely the same animal as the references), 'NO_MATCH' (probe is a different animal), 'INVALID_INPUT' (photos are out-of-domain or references are inconsistent)."},"score":{"type":"number","title":"Score","description":"Ensemble cosine similarity between the probe embedding and the averaged reference template embedding. Range: [0.0, 1.0]. This is the PRIMARY match metric -- show this number to the end user. Values closer to 1.0 indicate higher similarity. The verdict (MATCH/NO_MATCH) is determined by comparing this against PREDICT_COSINE_THRESHOLD."},"confidence":{"type":"number","title":"Confidence","description":"Confidence score derived from the euclidean distance between probe and reference template. Range: [0.0, 1.0]. Formula: max(0, min(1, (threshold - distance) / threshold)). Higher confidence = more reliable the verdict. confidence = 0.0 when euclidean distance >= PREDICT_EUCLIDEAN_MAX (completely different embeddings)."},"confidence_label":{"type":"string","title":"Confidence Label","description":"Human-readable confidence classification. One of: 'NOT_THE_ANIMAL' (confidence = 0.0, or euclidean distance exceeded threshold, definitely different animal), 'FAIR' (0 < confidence < 0.55, possible match, review recommended), 'GOOD' (0.55 <= confidence < 0.85, high probability of match), 'EXCELLENT' (confidence >= 0.85, near-certain match)."},"explanation":{"type":"string","title":"Explanation","description":"Plain-language explanation of the result and why it was reached. Includes which criteria passed or failed and why. Use this for display to end users without requiring them to interpret numeric values."}},"type":"object","required":["verdict","score","confidence","confidence_label","explanation"],"title":"Decision","examples":[{"confidence":0.54,"confidence_label":"FAIR","explanation":"The probe photo matches the reference animal with FAIR confidence. All decision criteria passed.","score":0.88,"verdict":"MATCH"},{"confidence":0.0,"confidence_label":"NOT_THE_ANIMAL","explanation":"The probe photo does not match the reference animal. Cosine similarity (0.15) is far below the required threshold (0.62).","score":0.15,"verdict":"NO_MATCH"}]},"DomainCheckDecision":{"properties":{"verdict":{"type":"string","title":"Verdict","description":"Validation verdict for the submitted photo. 'VALID_MUZZLE' -- the photo appears to be a valid muzzle image with meaningful content. 'INVALID_INPUT' -- the photo failed at least one validation criterion. Failed photos may be blank, extremely low-quality, random noise, or completely outside the types of images the models were trained on."},"explanation":{"type":"string","title":"Explanation","description":"Plain-language explanation of the validation result. For VALID_MUZZLE: describes what passed. For INVALID_INPUT: names which criterion/criteria failed and why. Use this for display to end users."}},"type":"object","required":["verdict","explanation"],"title":"DomainCheckDecision","examples":[{"explanation":"The image appears to be a valid muzzle photo. All quality criteria passed.","verdict":"VALID_MUZZLE"},{"explanation":"The image does not appear to be a valid muzzle photo. Failed criteria: embedding_variance.","verdict":"INVALID_INPUT"}]},"DomainCheckRequest":{"properties":{"photo":{"type":"string","title":"Photo","description":"A single base64-encoded JPEG or PNG image to validate as a potential muzzle photo. The image will be analyzed to determine if it appears to be a bovine muzzle (as opposed to random noise, blank images, or completely out-of-domain content). No reference photos are needed -- this check uses embedding statistics only."}},"type":"object","required":["photo"],"title":"DomainCheckRequest","examples":[{"photo":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="}]},"DomainCheckResponse":{"properties":{"decision":{"$ref":"#/components/schemas/DomainCheckDecision","description":"CONSUMER-FACING: The validation verdict (VALID_MUZZLE or INVALID_INPUT) and a plain-language explanation. Start here -- most integrations only need 'decision' to determine whether to accept the image."},"embedding_variance":{"type":"number","title":"Embedding Variance","description":"Variance (spread) of the 384-dimensional ensemble embedding vector values. Range: [0.0, ~0.25]. Meaningful muzzle images produce embeddings with moderate variance (typically >0.005). Blank images, solid colors, or completely random noise produce near-zero variance. This is one of the two criteria used for validation."},"cross_model_consistency":{"type":"number","title":"Cross Model Consistency","description":"Mean pairwise cosine similarity between the 3 individual model embeddings (model 60, 62, 64). Range: [-1.0, 1.0], but typically near 0.0 for normal images. VERY HIGH values (near 1.0) indicate the 3 models collapsed to producing near-identical embeddings, which strongly suggests the input image is out-of-domain (e.g., random noise, solid color, or garbage). This is one of the two criteria used for validation."},"summary":{"$ref":"#/components/schemas/DomainCheckSummary","description":"CONSUMER-FACING: The detailed breakdown of every validation criterion evaluated. Use this to explain to users WHY the verdict was reached. Each criterion includes a human-readable description ready for display."}},"type":"object","required":["decision","embedding_variance","cross_model_consistency","summary"],"title":"DomainCheckResponse","examples":[{"cross_model_consistency":0.05,"decision":{"explanation":"The image appears to be a valid muzzle photo. All quality criteria passed.","verdict":"VALID_MUZZLE"},"embedding_variance":0.031,"summary":{"criteria":[{"description":"Embedding variance (0.031) indicates meaningful content (>0.005)","metric":"embedding_variance","passed":true,"threshold":0.005,"value":0.031},{"description":"Cross-model pairwise similarity (0.05) is within expected range (<=0.20)","metric":"cross_model_consistency","passed":true,"threshold":0.2,"value":0.05}]}}]},"DomainCheckSummary":{"properties":{"criteria":{"items":{"$ref":"#/components/schemas/CriterionResult"},"type":"array","title":"Criteria","description":"Detailed breakdown of every validation criterion evaluated for this image. Each entry shows the metric name, observed value, threshold, pass/fail, and a human-readable description. Use this to explain to users WHY the verdict was reached, which criteria passed/failed, and what the observed values were."}},"type":"object","required":["criteria"],"title":"DomainCheckSummary","examples":[{"criteria":[{"description":"Embedding variance (0.031) indicates meaningful content (>0.005)","metric":"embedding_variance","passed":true,"threshold":0.005,"value":0.031},{"description":"Cross-model pairwise similarity (0.05) is within expected range (<=0.20)","metric":"cross_model_consistency","passed":true,"threshold":0.2,"value":0.05}]}]},"EncodeRequest":{"properties":{"photos":{"items":{"type":"string"},"type":"array","maxItems":12,"minItems":1,"title":"Photos","description":"List of 1 to 12 base64-encoded JPEG or PNG images. Each image is transformed into ML embedding codes using model IDs 60, 62, and 64."}},"type":"object","required":["photos"],"title":"EncodeRequest","examples":[{"photos":["iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="]}]},"EncodeResponse":{"properties":{"images":{"items":{"$ref":"#/components/schemas/EncodedImageResult"},"type":"array","title":"Images","description":"Encoded results for each input image in the same order as received. Use `index` and `filename` to identify each image."}},"type":"object","required":["images"],"title":"EncodeResponse","examples":[{"images":[{"decision":{"explanation":"The image appears to be a valid muzzle photo. All quality criteria passed.","verdict":"VALID_MUZZLE"},"domain_summary":{"criteria":[{"description":"Embedding variance (0.031) indicates meaningful content (>0.005)","metric":"embedding_variance","passed":true,"threshold":0.005,"value":0.031},{"description":"Cross-model pairwise similarity (0.05) is within expected range (<=0.20)","metric":"cross_model_consistency","passed":true,"threshold":0.2,"value":0.05}]},"ensemble_code":[0.12,-0.34,0.22,0.08,-0.11,0.45,-0.09,0.27,0.14],"filename":"photo_0","index":0,"model_codes":[{"code":[0.12,-0.34,0.22],"model_id":60},{"code":[0.08,-0.11,0.45],"model_id":62},{"code":[-0.09,0.27,0.14],"model_id":64}]}]}]},"EncodedImageResult":{"properties":{"index":{"type":"integer","title":"Index","description":"Input order index of the image in the request. Use this to map each output code back to the original image."},"filename":{"type":"string","title":"Filename","description":"Image identifier returned by the API. For JSON input, generated as photo_{index}. For multipart input, preserves the uploaded filename."},"decision":{"$ref":"#/components/schemas/DomainCheckDecision","description":"Domain-check verdict for this image before encoding. VALID_MUZZLE means the image passed quality/domain checks."},"domain_summary":{"$ref":"#/components/schemas/DomainCheckSummary","description":"Detailed domain-check criteria for this specific image, including pass/fail and thresholds."},"model_codes":{"items":{"$ref":"#/components/schemas/ModelEmbeddingCode"},"type":"array","title":"Model Codes","description":"Individual per-model embedding codes. Always contains exactly 3 entries: model IDs 60, 62, and 64."},"ensemble_code":{"items":{"type":"number"},"type":"array","title":"Ensemble Code","description":"Concatenated ensemble embedding code with 384 dimensions (3 models x 128 dimensions)."}},"type":"object","required":["index","filename","decision","domain_summary","model_codes","ensemble_code"],"title":"EncodedImageResult"},"EnsembleSummary":{"properties":{"template_vs_probe_cosine":{"type":"number","title":"Template Vs Probe Cosine","description":"Cosine similarity between the averaged embedding of the 3 reference photos and the probe photo embedding, computed using the concatenated ensemble of all 3 models. Range: [0.0, 1.0]. This is the ensemble-level primary match metric. Higher = more similar. This is the value used to compute 'decision.score'."},"template_vs_probe_distance":{"type":"number","title":"Template Vs Probe Distance","description":"Euclidean distance between the averaged embedding of the 3 reference photos and the probe photo embedding, computed using the concatenated ensemble of all 3 models. Range: [0.0, ~2.0]. Lower = more similar. Used to compute 'decision.confidence'."},"mean_cosine":{"type":"number","title":"Mean Cosine","description":"Mean of the 3 per-reference cosine similarity values, computed at the ensemble level. Range: [0.0, 1.0]. Average agreement between each reference photo and the probe photo."},"min_cosine":{"type":"number","title":"Min Cosine","description":"Minimum cosine similarity across the 3 per-reference comparisons, computed at the ensemble level. Range: [0.0, 1.0]. The most conservative similarity score at the ensemble level."},"max_cosine":{"type":"number","title":"Max Cosine","description":"Maximum cosine similarity across the 3 per-reference comparisons, computed at the ensemble level. Range: [0.0, 1.0]. The most optimistic similarity score at the ensemble level."}},"type":"object","required":["template_vs_probe_cosine","template_vs_probe_distance","mean_cosine","min_cosine","max_cosine"],"title":"EnsembleSummary","examples":[{"max_cosine":0.91,"mean_cosine":0.88,"min_cosine":0.85,"template_vs_probe_cosine":0.88,"template_vs_probe_distance":0.46}]},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Overall API health status. 'ok' -- service is fully operational with all models loaded and ready to process requests. 'degraded' -- service is responding but models failed to load; verification requests will return 503."},"models_loaded":{"items":{"type":"integer"},"type":"array","title":"Models Loaded","description":"List of TensorFlow model IDs that successfully loaded. Expected: [60, 62, 64]. Empty list [] means no models loaded -- requests will return 503. Partial list (e.g., [60, 62]) means some models failed -- verification still works but with reduced accuracy."}},"type":"object","required":["status","models_loaded"],"title":"HealthResponse","examples":[{"models_loaded":[60,62,64],"status":"ok"},{"models_loaded":[],"status":"degraded"}]},"ModelEmbeddingCode":{"properties":{"model_id":{"type":"integer","title":"Model Id","description":"Model identifier that generated this embedding code (60, 62, or 64)."},"code":{"items":{"type":"number"},"type":"array","title":"Code","description":"Normalized 128-dimensional embedding code generated by this model for the input image. This is the per-model biometric representation."}},"type":"object","required":["model_id","code"],"title":"ModelEmbeddingCode"},"ModelVerification":{"properties":{"model_id":{"type":"integer","title":"Model Id","description":"TensorFlow model identifier. This API uses 3 independently-trained siamese models (IDs: 60, 62, 64). Each model produces its own 128-dimensional embedding. Use this field to identify which model generated this verification result."},"template_vs_probe_cosine":{"type":"number","title":"Template Vs Probe Cosine","description":"Cosine similarity between the averaged embedding of all 3 reference photos (reference template) and the probe photo embedding, computed within this specific model. Range: [0.0, 1.0]. This is the primary similarity metric used for the MATCH/NO_MATCH decision. Higher values = more similar."},"template_vs_probe_distance":{"type":"number","title":"Template Vs Probe Distance","description":"Euclidean distance between the averaged embedding of all 3 reference photos and the probe photo embedding, computed within this specific model. Range: [0.0, ~2.0]. Lower values = more similar. Used alongside cosine similarity to confirm the match decision."},"reference_vs_probe":{"items":{"$ref":"#/components/schemas/ReferenceVsProbe"},"type":"array","title":"Reference Vs Probe","description":"Per-reference comparison results. Shows cosine similarity and euclidean distance between each individual reference photo and the probe photo, computed within this specific model. Use this to identify if one specific reference photo is causing a low match score."},"mean_cosine":{"type":"number","title":"Mean Cosine","description":"Mean of the 3 per-reference cosine similarity values. Range: [0.0, 1.0]. Summarizes overall agreement between the 3 references and the probe."},"min_cosine":{"type":"number","title":"Min Cosine","description":"Minimum cosine similarity across the 3 per-reference comparisons. Range: [0.0, 1.0]. The most conservative (lowest) similarity score. A low min_cosine with high mean_cosine suggests one reference is an outlier."},"max_cosine":{"type":"number","title":"Max Cosine","description":"Maximum cosine similarity across the 3 per-reference comparisons. Range: [0.0, 1.0]. The most optimistic (highest) similarity score. If max_cosine is much higher than min_cosine, the references may be inconsistent."}},"type":"object","required":["model_id","template_vs_probe_cosine","template_vs_probe_distance","reference_vs_probe","mean_cosine","min_cosine","max_cosine"],"title":"ModelVerification","examples":[{"max_cosine":0.9,"mean_cosine":0.87,"min_cosine":0.83,"model_id":60,"reference_vs_probe":[{"cosine_similarity":0.89,"euclidean_distance":0.42,"reference_index":0},{"cosine_similarity":0.83,"euclidean_distance":0.55,"reference_index":1},{"cosine_similarity":0.9,"euclidean_distance":0.4,"reference_index":2}],"template_vs_probe_cosine":0.87,"template_vs_probe_distance":0.49}]},"PredictDetails":{"properties":{"models":{"items":{"$ref":"#/components/schemas/ModelVerification"},"type":"array","title":"Models","description":"Per-model verification results for all 3 TensorFlow siamese models (IDs: 60, 62, 64). Each entry contains the full verification output for one model, including per-reference comparisons. Use this for deep debugging, model-level auditing, or comparing how each model scored the verification. For the consumer-facing result, use 'decision' and 'summary' instead."},"ensemble":{"$ref":"#/components/schemas/EnsembleSummary","description":"Ensemble-level verification summary using the concatenated embedding from all 3 models. Contains the same metrics as ModelVerification but computed on the 384-dimensional ensemble embedding (3 models x 128 dimensions each). This is the authoritative set of metrics used for the final MATCH/NO_MATCH verdict."},"reference_mutual_similarity":{"type":"number","title":"Reference Mutual Similarity","description":"Mean cosine similarity between all pairs of the 3 reference photos. Range: [0.0, 1.0]. Higher values indicate the 3 reference photos are from the same animal. Low values (below PREDICT_MUTUAL_MIN) indicate the references may be from different animals, which invalidates the comparison."}},"type":"object","required":["models","ensemble","reference_mutual_similarity"],"title":"PredictDetails","examples":[{"ensemble":{"max_cosine":0.91,"mean_cosine":0.88,"min_cosine":0.85,"template_vs_probe_cosine":0.88,"template_vs_probe_distance":0.46},"models":[{"max_cosine":0.9,"mean_cosine":0.87,"min_cosine":0.83,"model_id":60,"reference_vs_probe":[{"cosine_similarity":0.89,"euclidean_distance":0.42,"reference_index":0},{"cosine_similarity":0.83,"euclidean_distance":0.55,"reference_index":1},{"cosine_similarity":0.9,"euclidean_distance":0.4,"reference_index":2}],"template_vs_probe_cosine":0.87,"template_vs_probe_distance":0.49}],"reference_mutual_similarity":0.8}]},"PredictRequest":{"properties":{"reference_photos":{"items":{"type":"string"},"type":"array","maxItems":3,"minItems":3,"title":"Reference Photos","description":"Exactly 3 base64-encoded JPEG or PNG reference muzzle images. These are enrollment photos of a known, verified animal. All 3 photos must be from the same animal. Use 3 different angles (left, center, right) for best results."},"probe_photo":{"type":"string","title":"Probe Photo","description":"A single base64-encoded JPEG or PNG probe muzzle image. This is the photo of the animal you want to verify. The API will determine whether it matches the reference animal."}},"type":"object","required":["reference_photos","probe_photo"],"title":"PredictRequest","examples":[{"probe_photo":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","reference_photos":["iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="]}]},"PredictResponse":{"properties":{"decision":{"$ref":"#/components/schemas/Decision","description":"CONSUMER-FACING: The final verdict and the key numbers to show the end user. Contains the MATCH/NO_MATCH verdict, the primary match score (cosine similarity), confidence score and label, and a plain-language explanation. Start here for integration -- most use cases only need 'decision' and 'summary'."},"summary":{"$ref":"#/components/schemas/PredictSummary","description":"CONSUMER-FACING: The detailed breakdown of every criterion evaluated. Use this to explain to users WHY the verdict was reached, which criteria passed/failed, and what the observed values were. Each criterion includes a human-readable description ready for display."},"details":{"$ref":"#/components/schemas/PredictDetails","description":"TECHNICAL / DEBUG: Per-model verification results and ensemble-level metrics. Contains the full verification data including per-model breakdowns and per-reference comparisons. Use this for model-level auditing, debugging unexpected verdicts, or building model-selection logic in advanced integrations. Most consumer integrations do not need this section."}},"type":"object","required":["decision","summary","details"],"title":"PredictResponse","examples":[{"decision":{"confidence":0.54,"confidence_label":"FAIR","explanation":"The probe photo matches the reference animal with FAIR confidence. All decision criteria passed.","score":0.88,"verdict":"MATCH"},"details":{"ensemble":{"max_cosine":0.91,"mean_cosine":0.88,"min_cosine":0.85,"template_vs_probe_cosine":0.88,"template_vs_probe_distance":0.46},"models":[{"max_cosine":0.9,"mean_cosine":0.87,"min_cosine":0.83,"model_id":60,"reference_vs_probe":[{"cosine_similarity":0.89,"euclidean_distance":0.42,"reference_index":0},{"cosine_similarity":0.83,"euclidean_distance":0.55,"reference_index":1},{"cosine_similarity":0.9,"euclidean_distance":0.4,"reference_index":2}],"template_vs_probe_cosine":0.87,"template_vs_probe_distance":0.49}],"reference_mutual_similarity":0.8},"summary":{"criteria":[{"description":"Cosine similarity between probe and reference template (0.88) meets the required threshold (0.62)","metric":"cosine_similarity","passed":true,"threshold":0.62,"value":0.88},{"description":"Euclidean distance between probe and reference template (0.46) is within the allowed limit (0.85)","metric":"euclidean_distance","passed":true,"threshold":0.85,"value":0.46},{"description":"Reference photos mutual similarity (0.80) exceeds the minimum required (0.40)","metric":"mutual_similarity","passed":true,"threshold":0.4,"value":0.8},{"description":"Photos appear to be valid muzzle images","metric":"domain_check","passed":true,"threshold":1,"value":1}]}}]},"PredictSummary":{"properties":{"criteria":{"items":{"$ref":"#/components/schemas/CriterionResult"},"type":"array","title":"Criteria","description":"Detailed breakdown of every decision criterion evaluated for this prediction. Each entry shows the metric name, observed value, threshold, pass/fail, and a human-readable description. Use this for user-facing explanations and for auditing why a decision was made. The 4 criteria evaluated are: cosine_similarity, euclidean_distance, mutual_similarity, and domain_check."}},"type":"object","required":["criteria"],"title":"PredictSummary","examples":[{"criteria":[{"description":"Cosine similarity between probe and reference template is 0.15, below the required 0.62","metric":"cosine_similarity","passed":false,"threshold":0.62,"value":0.15},{"description":"Euclidean distance between probe and reference template is 1.25, above the allowed 0.85","metric":"euclidean_distance","passed":false,"threshold":0.85,"value":1.25}]}]},"ReferenceVsProbe":{"properties":{"reference_index":{"type":"integer","title":"Reference Index","description":"Index of the reference photo compared against the probe: 0 (first), 1 (second), or 2 (third)."},"cosine_similarity":{"type":"number","title":"Cosine Similarity","description":"Cosine similarity between the normalized embedding of this reference photo and the normalized embedding of the probe photo. Range: [0.0, 1.0]. Higher values indicate greater similarity. Values above ~0.60 typically indicate the same animal."},"euclidean_distance":{"type":"number","title":"Euclidean Distance","description":"Euclidean distance between the normalized embedding of this reference photo and the normalized embedding of the probe photo. Range: [0.0, ~2.0]. Lower values indicate greater similarity. Values below ~0.85 typically indicate the same animal."}},"type":"object","required":["reference_index","cosine_similarity","euclidean_distance"],"title":"ReferenceVsProbe","examples":[{"cosine_similarity":0.89,"euclidean_distance":0.42,"reference_index":0},{"cosine_similarity":0.91,"euclidean_distance":0.38,"reference_index":1}]}},"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication. Obtain from your account settings."}}},"tags":[{"name":"Health","description":"Service health and model status checks."},{"name":"Verification","description":"Muzzle biometric verification endpoints."},{"name":"Domain Check","description":"Single-image muzzle validation endpoints. Check if a photo is a valid muzzle image."},{"name":"Encode","description":"Image embedding code generation endpoints."}]}