{
  "openapi": "3.1.0",
  "info": {
    "title": "PixelDepot Face API",
    "version": "1.1.0-beta",
    "summary": "Face geometry and attribute APIs.",
    "description": "Face Detect returns threshold-clearing geometry only and retains no image or response geometry. Face Analyze preserves its broader experimental contract and seven-day request-history payload window. Both report exact model digests.",
    "contact": {
      "url": "https://pixeldepot.ai"
    },
    "license": {
      "name": "Terms of use",
      "url": "https://pixeldepot.ai/legal/terms"
    }
  },
  "servers": [
    {
      "url": "https://pixeldepot.ai/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/face/analyze": {
      "post": {
        "operationId": "analyzeFace",
        "summary": "Locate faces and estimate attributes",
        "description": "Send one JPEG, PNG or WebP as the multipart `image` part. JSON, `image_url`, crops and per-request thresholds are not accepted.\n\nA single best candidate may be returned with `meets_threshold: false` when no candidate clears the Analyze profile.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "JPEG, PNG or WebP. Up to 10 MB and 50 decoded megapixels."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Face Analyze result.",
            "headers": {
              "X-Request-ID": {
                "description": "The request ID also returned in the body.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Inference-Duration-Ms": {
                "description": "Milliseconds the gateway spent calling inference.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyzeResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing, malformed or not recognized. `invalid_image`: The bytes are not a decodable JPEG, PNG or WebP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "request_id": "req_..."
                    }
                  },
                  "invalid_image": {
                    "value": {
                      "error": "invalid_image",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`missing_authorization`: No Authorization header was sent. `invalid_key`: The key is unknown, revoked, or not a PixelDepot key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "request_id": "req_..."
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": "invalid_key",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_quota`: The monthly allowance for your plan is spent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_quota": {
                    "value": {
                      "error": "insufficient_quota",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`image_too_large`: Over 10 MB, or over 50 megapixels once decoded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "image_too_large": {
                    "value": {
                      "error": "image_too_large",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded`: Over the per-minute burst for your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limit_exceeded": {
                    "value": {
                      "error": "rate_limit_exceeded",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. The request ID identifies it in our logs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "`inference_unreachable`: The inference service did not answer. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inference_unreachable": {
                    "value": {
                      "error": "inference_unreachable",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "504": {
            "description": "`inference_timeout`: The request could not enter or finish inference in time.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inference_timeout": {
                    "value": {
                      "error": "inference_timeout",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/face/detect": {
      "post": {
        "operationId": "detectFaces",
        "summary": "Locate and count faces without attributes",
        "description": "Send one JPEG, PNG or WebP as the multipart `image` part. JSON, `image_url`, crops and per-request thresholds are not accepted.\n\nOnly candidates at or above the fitted 0.90-precision operating point are returned, ordered by confidence. No qualifying face is a successful response with `faces: []`. The service retains operational metadata, but no image or response geometry.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "JPEG, PNG or WebP. Up to 10 MB and 50 decoded megapixels."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Geometry-only detection result.",
            "headers": {
              "X-Request-ID": {
                "description": "The request ID also returned in the body.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Inference-Duration-Ms": {
                "description": "Milliseconds the gateway spent calling inference.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DetectResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing, malformed or not recognized. `invalid_image`: The bytes are not a decodable JPEG, PNG or WebP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "request_id": "req_..."
                    }
                  },
                  "invalid_image": {
                    "value": {
                      "error": "invalid_image",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`missing_authorization`: No Authorization header was sent. `invalid_key`: The key is unknown, revoked, or not a PixelDepot key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "request_id": "req_..."
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": "invalid_key",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_quota`: The monthly allowance for your plan is spent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_quota": {
                    "value": {
                      "error": "insufficient_quota",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`image_too_large`: Over 10 MB, or over 50 megapixels once decoded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "image_too_large": {
                    "value": {
                      "error": "image_too_large",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded`: Over the per-minute burst for your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limit_exceeded": {
                    "value": {
                      "error": "rate_limit_exceeded",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. The request ID identifies it in our logs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "`inference_unreachable`: The inference service did not answer. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inference_unreachable": {
                    "value": {
                      "error": "inference_unreachable",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "`feature_unavailable`: No face detector is available on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "feature_unavailable": {
                    "value": {
                      "error": "feature_unavailable",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          },
          "504": {
            "description": "`inference_timeout`: The request could not enter or finish inference in time.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inference_timeout": {
                    "value": {
                      "error": "inference_timeout",
                      "request_id": "req_..."
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A `pd_live_...` or `pd_test_...` API key from the console."
      }
    },
    "schemas": {
      "AgeAttribute": {
        "additionalProperties": false,
        "description": "Age as both a bucket and a representative integer. See AGE_MIDPOINTS.",
        "properties": {
          "range": {
            "title": "Range",
            "type": "string"
          },
          "estimate": {
            "title": "Estimate",
            "type": "integer"
          },
          "confidence": {
            "maximum": 1,
            "minimum": 0,
            "title": "Confidence",
            "type": "number"
          },
          "meets_threshold": {
            "title": "Meets Threshold",
            "type": "boolean"
          },
          "threshold": {
            "maximum": 1,
            "minimum": 0,
            "title": "Threshold",
            "type": "number"
          },
          "probabilities": {
            "items": {
              "$ref": "#/components/schemas/ClassProbability"
            },
            "title": "Probabilities",
            "type": "array"
          }
        },
        "required": [
          "range",
          "estimate",
          "confidence",
          "meets_threshold",
          "threshold",
          "probabilities"
        ],
        "title": "AgeAttribute",
        "type": "object"
      },
      "AnalyzedFace": {
        "additionalProperties": false,
        "description": "One analyzed face region.",
        "properties": {
          "id": {
            "title": "Id",
            "type": "string"
          },
          "region": {
            "enum": [
              "whole_image",
              "bounding_box"
            ],
            "title": "Region",
            "type": "string"
          },
          "bounding_box": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BoundingBox"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "confidence": {
            "anyOf": [
              {
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Confidence"
          },
          "meets_threshold": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Meets Threshold"
          },
          "threshold": {
            "anyOf": [
              {
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Threshold"
          },
          "attributes": {
            "$ref": "#/components/schemas/FaceAttributes"
          }
        },
        "required": [
          "id",
          "region",
          "attributes"
        ],
        "title": "AnalyzedFace",
        "type": "object"
      },
      "BoundingBox": {
        "additionalProperties": false,
        "description": "Face region in **original submitted-image pixels**, ``(0, 0)`` top-left.\n\nNot detector-input pixels. The detector sees a letterboxed square tensor and\nthe caller never does, so every coordinate on the wire is in the coordinate\nsystem of the image the caller uploaded — the only one they can draw on.\n\nIntegers, matching PRD §10. A pixel index is an integer, and a float here\nwould imply a precision the detector does not have. The convention is\ncontainment: the integer box always encloses the model's float box, so a\ndrawn rectangle never cuts a chin off by a pixel.",
        "properties": {
          "x": {
            "minimum": 0,
            "title": "X",
            "type": "integer"
          },
          "y": {
            "minimum": 0,
            "title": "Y",
            "type": "integer"
          },
          "width": {
            "exclusiveMinimum": 0,
            "title": "Width",
            "type": "integer"
          },
          "height": {
            "exclusiveMinimum": 0,
            "title": "Height",
            "type": "integer"
          }
        },
        "required": [
          "x",
          "y",
          "width",
          "height"
        ],
        "title": "BoundingBox",
        "type": "object"
      },
      "CategoricalAttribute": {
        "additionalProperties": false,
        "description": "A categorical prediction with its full distribution.\n\n**`label` always carries a real class.** It is never the string\n``\"uncertain\"``.\n\nOwner decision, 2026-08-01: the API must always decide. That is also the\nmodel the customer base already knows — Kairos returns a distribution and no\n\"I don't know\", and the integrator thresholds it themselves.\n\nRefusing to answer and answering badly are still different things, so the\ndistinction moved out of ``label`` and into its own field. ``meets_threshold``\nsays whether this prediction cleared the validated bar for its head; when it\nis ``false`` the label is the model's best guess and nothing more. That is\nstrictly more information than the old magic string carried, and it is\nmachine-readable rather than requiring a string comparison.",
        "properties": {
          "label": {
            "title": "Label",
            "type": "string"
          },
          "confidence": {
            "maximum": 1,
            "minimum": 0,
            "title": "Confidence",
            "type": "number"
          },
          "meets_threshold": {
            "title": "Meets Threshold",
            "type": "boolean"
          },
          "threshold": {
            "maximum": 1,
            "minimum": 0,
            "title": "Threshold",
            "type": "number"
          },
          "probabilities": {
            "items": {
              "$ref": "#/components/schemas/ClassProbability"
            },
            "title": "Probabilities",
            "type": "array"
          }
        },
        "required": [
          "label",
          "confidence",
          "meets_threshold",
          "threshold",
          "probabilities"
        ],
        "title": "CategoricalAttribute",
        "type": "object"
      },
      "ClassProbability": {
        "additionalProperties": false,
        "description": "One class and its calibrated probability.",
        "properties": {
          "label": {
            "title": "Label",
            "type": "string"
          },
          "probability": {
            "maximum": 1,
            "minimum": 0,
            "title": "Probability",
            "type": "number"
          }
        },
        "required": [
          "label",
          "probability"
        ],
        "title": "ClassProbability",
        "type": "object"
      },
      "FaceAttributes": {
        "additionalProperties": false,
        "description": "Attributes for one face.\n\n``apparent_ancestry`` is absent unless the caller is entitled to it. Absence\nis the default, not an error (ADR-0007, ADR-0008).\n\n``has_glasses`` is absent on artifacts that predate the head — 0.1.0 through\n0.3.0 — rather than defaulted, because a default would be a claim the model\nnever made.",
        "properties": {
          "estimated_age": {
            "$ref": "#/components/schemas/AgeAttribute"
          },
          "perceived_gender_presentation": {
            "$ref": "#/components/schemas/CategoricalAttribute"
          },
          "apparent_ancestry": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CategoricalAttribute"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "has_glasses": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CategoricalAttribute"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "`yes` means lenses in front of the eyes. Sunglasses pushed up onto the head, hanging from a collar or held in the hand are `no`: this describes what is in front of the eyes in this frame, not what the person owns. Absent on artifacts older than face-attributes 0.4.0."
          }
        },
        "required": [
          "estimated_age",
          "perceived_gender_presentation"
        ],
        "title": "FaceAttributes",
        "type": "object"
      },
      "ImageInfo": {
        "additionalProperties": false,
        "description": "Dimensions of the submitted image, as decoded.",
        "properties": {
          "width": {
            "title": "Width",
            "type": "integer"
          },
          "height": {
            "title": "Height",
            "type": "integer"
          }
        },
        "required": [
          "width",
          "height"
        ],
        "title": "ImageInfo",
        "type": "object"
      },
      "ModelIdentity": {
        "additionalProperties": false,
        "description": "Identity of a model that contributed to this response (PRD §12).",
        "properties": {
          "name": {
            "title": "Name",
            "type": "string"
          },
          "version": {
            "title": "Version",
            "type": "string"
          },
          "weight_sha256": {
            "title": "Weight Sha256",
            "type": "string"
          },
          "card": {
            "title": "Card",
            "type": "string"
          }
        },
        "required": [
          "name",
          "version",
          "weight_sha256",
          "card"
        ],
        "title": "ModelIdentity",
        "type": "object"
      },
      "ResponseMeta": {
        "additionalProperties": false,
        "description": "Everything needed to audit or reproduce the result.",
        "properties": {
          "models": {
            "items": {
              "$ref": "#/components/schemas/ModelIdentity"
            },
            "title": "Models",
            "type": "array"
          },
          "disclosures": {
            "items": {
              "type": "string"
            },
            "title": "Disclosures",
            "type": "array"
          },
          "release_status": {
            "default": "unknown",
            "title": "Release Status",
            "type": "string"
          }
        },
        "required": [
          "models",
          "disclosures"
        ],
        "title": "ResponseMeta",
        "type": "object"
      },
      "AnalyzeResponse": {
        "additionalProperties": false,
        "description": "Face Analyze result.",
        "properties": {
          "request_id": {
            "title": "Request Id",
            "type": "string"
          },
          "image": {
            "$ref": "#/components/schemas/ImageInfo"
          },
          "faces": {
            "items": {
              "$ref": "#/components/schemas/AnalyzedFace"
            },
            "title": "Faces",
            "type": "array"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "required": [
          "request_id",
          "image",
          "faces",
          "meta"
        ],
        "title": "AnalyzeResponse",
        "type": "object"
      },
      "DetectMeta": {
        "additionalProperties": false,
        "description": "Audit identity and completeness limits for Face Detect.",
        "properties": {
          "models": {
            "items": {
              "$ref": "#/components/schemas/ModelIdentity"
            },
            "title": "Models",
            "type": "array"
          },
          "disclosures": {
            "items": {
              "type": "string"
            },
            "title": "Disclosures",
            "type": "array"
          },
          "release_status": {
            "default": "unknown",
            "title": "Release Status",
            "type": "string"
          },
          "max_faces": {
            "minimum": 1,
            "title": "Max Faces",
            "type": "integer"
          },
          "truncated": {
            "title": "Truncated",
            "type": "boolean"
          }
        },
        "required": [
          "models",
          "disclosures",
          "max_faces",
          "truncated"
        ],
        "title": "DetectMeta",
        "type": "object"
      },
      "DetectedFace": {
        "additionalProperties": false,
        "description": "One threshold-clearing face returned by Face Detect.",
        "properties": {
          "id": {
            "title": "Id",
            "type": "string"
          },
          "bounding_box": {
            "$ref": "#/components/schemas/BoundingBox"
          },
          "confidence": {
            "maximum": 1,
            "minimum": 0,
            "title": "Confidence",
            "type": "number"
          },
          "threshold": {
            "maximum": 1,
            "minimum": 0,
            "title": "Threshold",
            "type": "number"
          },
          "meets_threshold": {
            "const": true,
            "default": true,
            "title": "Meets Threshold",
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "bounding_box",
          "confidence",
          "threshold"
        ],
        "title": "DetectedFace",
        "type": "object"
      },
      "DetectResponse": {
        "additionalProperties": false,
        "description": "Geometry-only Face Detect result.",
        "properties": {
          "request_id": {
            "title": "Request Id",
            "type": "string"
          },
          "image": {
            "$ref": "#/components/schemas/ImageInfo"
          },
          "faces": {
            "items": {
              "$ref": "#/components/schemas/DetectedFace"
            },
            "title": "Faces",
            "type": "array"
          },
          "meta": {
            "$ref": "#/components/schemas/DetectMeta"
          }
        },
        "required": [
          "request_id",
          "image",
          "faces",
          "meta"
        ],
        "title": "DetectResponse",
        "type": "object"
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "feature_unavailable",
              "image_too_large",
              "inference_timeout",
              "inference_unreachable",
              "insufficient_quota",
              "internal_error",
              "invalid_image",
              "invalid_key",
              "invalid_request",
              "missing_authorization",
              "rate_limit_exceeded"
            ]
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          }
        }
      }
    }
  }
}