{
  "openapi": "3.1.0",
  "info": {
    "title": "API Reference",
    "version": "1.0.0",
    "description": "ProofAge is an identity verification and age estimation API. Use this API to create verification sessions, upload media (selfies and documents), and receive decisions via webhooks."
  },
  "servers": [
    {
      "url": "https://api.proofage.xyz/v1",
      "description": "API"
    }
  ],
  "security": [
    {
      "apiKey": [],
      "hmacSignature": []
    }
  ],
  "paths": {
    "/verifications/{verification}/media": {
      "post": {
        "operationId": "uploadMedia",
        "description": "Uploads a selfie or document image to the verification session.\nConsent must be accepted before uploading. The file is validated\nfor quality (face detection, blur, brightness for selfies;\ndocument detection, readability for documents).\n\nAnswers `200` with an empty body; a failed quality check answers `422`.",
        "summary": "Upload media",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UploadMediaRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The media was stored. The body is empty.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "examples": [
                    ""
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "description": "The image failed a quality check; the code says which.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "code": "FACE_NOT_FOUND",
                      "message": "Face validation failed. Please upload a clear, well-lit selfie with your face fully visible."
                    }
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/verifications/{verification}/media/{media}": {
      "get": {
        "operationId": "downloadVerificationMedia",
        "description": "Streams the bytes of a selfie or document image belonging to the\nverification, served by ProofAge under the same API key and HMAC\nsignature as every other endpoint.\n\nThe response body is the image itself; `Content-Type` carries its MIME\ntype. Media that has been purged, has passed its retention window, or\ndoes not belong to this verification answers 404.",
        "summary": "Download media",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "media",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The media file itself. Content-Type carries its MIME type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Media does not belong to this verification, or is no longer available.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "error": {
                        "code": "MEDIA_NOT_FOUND",
                        "message": "Media not found."
                      }
                    }
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationException"
          }
        }
      }
    },
    "/verifications": {
      "post": {
        "operationId": "createVerification",
        "description": "Creates a new verification session and returns a URL for the end-user to complete verification.",
        "summary": "Create a verification session",
        "tags": [
          "Verification"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StoreVerificationRequest"
              }
            }
          }
        },
        "responses": {
          "422": {
            "$ref": "#/components/responses/ValidationException"
          },
          "201": {
            "description": "The verification session, with the url the person opens to complete it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "id": "550e8400-e29b-41d4-a716-446655440000",
                      "external_id": "user-123",
                      "external_metadata": {
                        "plan": "premium"
                      },
                      "redirect_url": "https://example.com/callback",
                      "status": "created",
                      "reason": null,
                      "duplicate_check": {
                        "checked": false,
                        "duplicate_count": 0,
                        "duplicates": []
                      },
                      "erasure": null,
                      "consent_accepted_at": null,
                      "created_at": "2026-03-19T12:00:00+00:00",
                      "updated_at": "2026-03-19T12:00:00+00:00",
                      "url": "https://idv.proofage.xyz/v/eyJ..."
                    }
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "external_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "external_metadata": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": {}
                    },
                    "redirect_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": "string"
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "duplicate_check": {
                      "type": "object",
                      "properties": {
                        "checked": {
                          "type": "boolean"
                        },
                        "duplicate_count": {
                          "type": "integer"
                        },
                        "duplicates": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "verification_id": {
                                "type": "string"
                              },
                              "external_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "similarity_score": {
                                "type": "number"
                              },
                              "verified_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "verification_id",
                              "external_id",
                              "similarity_score",
                              "verified_at"
                            ]
                          }
                        }
                      },
                      "required": [
                        "checked",
                        "duplicate_count",
                        "duplicates"
                      ]
                    },
                    "erasure": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "erased_at": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "string"
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "requested_via": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "erased_at",
                        "scope",
                        "reason",
                        "requested_via"
                      ]
                    },
                    "consent_accepted_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "external_id",
                    "external_metadata",
                    "redirect_url",
                    "status",
                    "reason",
                    "duplicate_check",
                    "erasure",
                    "consent_accepted_at",
                    "created_at",
                    "updated_at",
                    "url"
                  ]
                }
              }
            }
          },
          "402": {
            "description": "A live workspace has no payment method.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "code": "PAYMENT_METHOD_REQUIRED",
                      "message": "A payment method is required to create verifications.",
                      "free_verifications_remaining": 0,
                      "trial_ends_at": "2026-04-03T12:00:00+00:00",
                      "trial_active": false
                    }
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "free_verifications_remaining": {
                      "type": "integer"
                    },
                    "trial_ends_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "trial_active": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "code",
                    "message",
                    "free_verifications_remaining",
                    "trial_ends_at",
                    "trial_active"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/verifications/{verification}/consent": {
      "post": {
        "operationId": "acceptConsent",
        "description": "Records the end-user's acceptance of the data processing consent.\nMust be called before uploading media or submitting the verification.",
        "summary": "Accept consent",
        "tags": [
          "Verification"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StoreVerificationConsentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent is recorded; media can now be uploaded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "consent_version_id": 1,
                      "consent_accepted_at": "2026-03-19T12:01:00+00:00"
                    }
                  ],
                  "properties": {
                    "consent_version_id": {
                      "type": "integer"
                    },
                    "consent_accepted_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "consent_version_id",
                    "consent_accepted_at"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "$ref": "#/components/responses/ValidationException"
          }
        }
      }
    },
    "/verifications/{verification}": {
      "get": {
        "operationId": "getVerification",
        "description": "Retrieves the current state of a verification session including its status and decision reason.",
        "summary": "Get verification status",
        "tags": [
          "Verification"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verification session state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "id": "550e8400-e29b-41d4-a716-446655440000",
                      "external_id": "user-123",
                      "external_metadata": {
                        "plan": "premium"
                      },
                      "redirect_url": "https://example.com/callback",
                      "status": "approved",
                      "reason": null,
                      "duplicate_check": {
                        "checked": true,
                        "duplicate_count": 2,
                        "duplicates": [
                          {
                            "verification_id": "771a9200-c3df-4e88-b201-112233445566",
                            "external_id": "user-456",
                            "similarity_score": 0.93,
                            "verified_at": "2026-03-11T09:22:41+00:00"
                          }
                        ]
                      },
                      "erasure": null,
                      "consent_accepted_at": "2026-03-19T12:01:00+00:00",
                      "created_at": "2026-03-19T12:00:00+00:00",
                      "updated_at": "2026-03-19T12:05:00+00:00"
                    }
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "external_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "external_metadata": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": {}
                    },
                    "redirect_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": "string"
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "duplicate_check": {
                      "type": "object",
                      "properties": {
                        "checked": {
                          "type": "boolean"
                        },
                        "duplicate_count": {
                          "type": "integer"
                        },
                        "duplicates": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "verification_id": {
                                "type": "string"
                              },
                              "external_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "similarity_score": {
                                "type": "number"
                              },
                              "verified_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "verification_id",
                              "external_id",
                              "similarity_score",
                              "verified_at"
                            ]
                          }
                        }
                      },
                      "required": [
                        "checked",
                        "duplicate_count",
                        "duplicates"
                      ]
                    },
                    "erasure": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "erased_at": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "string"
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "requested_via": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "erased_at",
                        "scope",
                        "reason",
                        "requested_via"
                      ]
                    },
                    "consent_accepted_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "external_id",
                    "external_metadata",
                    "redirect_url",
                    "status",
                    "reason",
                    "duplicate_check",
                    "erasure",
                    "consent_accepted_at",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "$ref": "#/components/responses/ValidationException"
          }
        }
      }
    },
    "/verifications/{verification}/estimation": {
      "get": {
        "operationId": "getVerificationEstimation",
        "description": "Retrieves sanitized age-threshold and gender estimates for age estimation workspaces.\nThe gender value uses 0 for female and 1 for male.",
        "summary": "Get verification age estimation result",
        "tags": [
          "Verification"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verification age estimation result. Gender value mapping: 0 = female, 1 = male.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "verification_id": "019de7a0-c11b-7373-9289-b0aa1ba08d13",
                      "attempt_id": "019de7a2-0305-7269-9cff-1715c71b19ae",
                      "age_threshold": {
                        "minimum": 18,
                        "passed": true,
                        "confidence": 0.98
                      },
                      "gender": {
                        "value": 0,
                        "confidence": 0.93
                      }
                    }
                  ],
                  "properties": {
                    "verification_id": {
                      "type": "string"
                    },
                    "attempt_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "age_threshold": {
                      "type": "object",
                      "properties": {
                        "minimum": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "passed": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "confidence": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "minimum",
                        "passed",
                        "confidence"
                      ]
                    },
                    "gender": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "value": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "confidence": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "value",
                        "confidence"
                      ]
                    }
                  },
                  "required": [
                    "verification_id",
                    "attempt_id",
                    "age_threshold",
                    "gender"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "$ref": "#/components/responses/ValidationException"
          }
        }
      }
    },
    "/verifications/{verification}/document": {
      "get": {
        "operationId": "getVerificationDocument",
        "description": "Returns what was read from the identity document of the most relevant attempt,\nnormalised: dates as YYYY-MM-DD, countries as ISO 3166-1 alpha-2 (XK for Kosovo), and null for\nanything the document did not yield or does not print. On identity (KYC)\nworkspaces `fields` carries eleven keys; on age verification workspaces only\n`first_name`, `last_name`, `date_of_birth` and `document_number` are returned.\nEach media item carries a `url` for the download endpoint, or null when the\nmedia has been purged or has passed its retention window.",
        "summary": "Get verification document result",
        "tags": [
          "Verification"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verification document result. `type` and `gender` are open enums: handle values not listed here. `gender` is F, M or X, where X means the document states that the sex is unspecified. `other` in `type` is a readable document that is not an identity card, passport, driving licence or residence permit, such as a health insurance card. Countries are ISO 3166-1 alpha-2 (XK for Kosovo). A document that prints only the month or year of expiry reports the last day of that period; a partially printed birth or issue date is null. On a Mexican voter card or driving licence created before 20 August 2026 17:00 UTC, `gender` is null. The German identity card prints no sex, so `gender` is null there. A permanent document (for example `PERMANENTE` or `INDEFINIDA`) reads as a null `expiry_date`. `type` `id` is the same value the upload endpoint takes. `issuing_country` and `nationality` can differ, for example on a residence permit. `issuing_subdivision` is the state or province that issued the document, as a bare code beside `issuing_country` (for example `FL` with `US`), or null; today it is filled for US driving licences and ID cards. `place_of_birth` is the printed text, not normalised. `address` is the printed text as read, not parsed and not normalised, and may contain line breaks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "document": {
                        "type": "passport",
                        "issuing_country": "DE",
                        "issuing_subdivision": null,
                        "fields": {
                          "first_name": "JANE",
                          "middle_name": null,
                          "last_name": "DOE",
                          "date_of_birth": "1990-04-12",
                          "gender": "F",
                          "nationality": "DE",
                          "place_of_birth": "BERLIN",
                          "address": null,
                          "document_number": "C01X00T47",
                          "issue_date": "2020-04-14",
                          "expiry_date": "2030-04-13"
                        }
                      },
                      "media": [
                        {
                          "id": "019de7a0-c11b-7373-9289-b0aa1ba08d13",
                          "type": "selfie",
                          "url": "https://api.proofage.xyz/v1/verifications/019de7a0-c11b-7373-9289-b0aa1ba08d13/media/019de7a0-c11b-7373-9289-b0aa1ba08d13"
                        },
                        {
                          "id": "019de7a0-c129-7259-8a08-bc10390ecbdb",
                          "type": "document_front",
                          "url": "https://api.proofage.xyz/v1/verifications/019de7a0-c11b-7373-9289-b0aa1ba08d13/media/019de7a0-c129-7259-8a08-bc10390ecbdb"
                        }
                      ],
                      "meta": {
                        "attempt_id": "019de7a2-0305-7269-9cff-1715c71b19ae"
                      }
                    },
                    {
                      "document": {
                        "type": "id",
                        "issuing_country": "FR",
                        "issuing_subdivision": null,
                        "fields": {
                          "first_name": "JEAN",
                          "last_name": "MARTIN",
                          "date_of_birth": "1988-11-02",
                          "document_number": "X4RTBPFW4"
                        }
                      },
                      "media": [
                        {
                          "id": "019de7a0-c129-7259-8a08-bc10390ecbdb",
                          "type": "document_front",
                          "url": "https://api.proofage.xyz/v1/verifications/019de7a0-c11b-7373-9289-b0aa1ba08d13/media/019de7a0-c129-7259-8a08-bc10390ecbdb"
                        }
                      ],
                      "meta": {
                        "attempt_id": "019de7a2-0305-7269-9cff-1715c71b19ae"
                      }
                    }
                  ],
                  "properties": {
                    "document": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "passport",
                            "id",
                            "driver_license",
                            "residence_permit",
                            "other",
                            null
                          ]
                        },
                        "issuing_country": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "issuing_subdivision": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "fields": {
                          "type": "object",
                          "properties": {
                            "first_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "middle_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "last_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "date_of_birth": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date"
                            },
                            "gender": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "F",
                                "M",
                                "X",
                                null
                              ]
                            },
                            "nationality": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "place_of_birth": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "address": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "document_number": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "issue_date": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date"
                            },
                            "expiry_date": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date"
                            }
                          },
                          "required": [
                            "first_name",
                            "last_name",
                            "date_of_birth",
                            "document_number"
                          ]
                        }
                      },
                      "required": [
                        "type",
                        "issuing_country",
                        "issuing_subdivision",
                        "fields"
                      ]
                    },
                    "media": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "url": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "type",
                          "url"
                        ]
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "attempt_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "attempt_id"
                      ]
                    }
                  },
                  "required": [
                    "document",
                    "media",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "$ref": "#/components/responses/ValidationException"
          }
        }
      }
    },
    "/verifications/{verification}/submit": {
      "post": {
        "operationId": "submitVerification",
        "description": "Submits the verification for automated processing once the current attempt's media is uploaded.\nAn upload moves the verification to `started`. A facial age estimation that asked for a document\nis submitted the same way, after the document is uploaded.\n\nAnswers `200` with an empty body; the decision arrives by webhook.",
        "summary": "Submit verification for processing",
        "tags": [
          "Verification"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The verification was submitted. The body is empty.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "examples": [
                    ""
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "description": "The verification is not waiting to be submitted (`INVALID_STATUS`), or required media is missing (`MISSING_REQUIRED_MEDIA`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "error": {
                        "code": "MISSING_REQUIRED_MEDIA",
                        "message": "All required media must be uploaded before submitting."
                      }
                    }
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/verifications/{verification}/blocked-face": {
      "post": {
        "operationId": "blockVerificationFace",
        "description": "Adds the face from this verification's selfie to your blocklist, shared by all your workspaces.\nLater verifications with a matching face are declined.\nOptional `reason` text is stored with the block and truncated to 1000 characters.\n\nOptional `reason_code` classifies the block, and is what reporting counts: one of\n`presentation_attack`, `fraudulent_document`, `scam_or_abuse`, `underage`, `other`.\nSend it whenever a person made the decision \u2014 the admin consoles require it, so a\nblock that arrives without one cannot be told apart from an automated one.",
        "summary": "Block a verification face",
        "tags": [
          "Verification"
        ],
        "parameters": [
          {
            "name": "verification",
            "in": "path",
            "required": true,
            "description": "The verification ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockVerificationFaceRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "No content"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFoundException"
          },
          "422": {
            "$ref": "#/components/responses/ValidationException"
          }
        }
      }
    },
    "/workspace": {
      "get": {
        "operationId": "getWorkspace",
        "description": "Returns the workspace settings including flow type, verification mode, and age thresholds.",
        "summary": "Get workspace configuration",
        "tags": [
          "Workspace"
        ],
        "responses": {
          "200": {
            "description": "`WorkspaceResource`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceResource"
                }
              }
            }
          }
        }
      }
    },
    "/consent": {
      "get": {
        "operationId": "getConsent",
        "description": "Returns the currently active consent version that must be presented to and accepted by the end-user.",
        "summary": "Get active consent",
        "tags": [
          "Workspace"
        ],
        "responses": {
          "200": {
            "description": "The active consent version. Show the text at url, then send id and text_sha256 back when accepting.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "examples": [
                    {
                      "id": 1,
                      "version": 2,
                      "text_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                      "url": "https://app.proofage.xyz/consent"
                    }
                  ],
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "version": {
                      "type": "integer"
                    },
                    "text_sha256": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "version",
                    "text_sha256",
                    "url"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "description": "Your workspace public key (pk_test_\u2026 or pk_live_\u2026).",
        "in": "header",
        "name": "X-API-Key"
      },
      "hmacSignature": {
        "type": "apiKey",
        "description": "HMAC-SHA256 of the request, hex-encoded, keyed with the workspace secret key. See API authentication.",
        "in": "header",
        "name": "X-HMAC-Signature"
      }
    },
    "schemas": {
      "BlockVerificationFaceRequest": {
        "type": "object",
        "properties": {
          "reason_code": {
            "$ref": "#/components/schemas/BlockedFaceReasonCode"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free text stored with the block; longer text is cut at 1000 characters."
          }
        },
        "title": "BlockVerificationFaceRequest"
      },
      "BlockedFaceReasonCode": {
        "type": "string",
        "description": "Why a person put this face on the blocklist. The admin consoles require one; the public API accepts it optionally, and propagated device matches carry none.\n",
        "enum": [
          "presentation_attack",
          "fraudulent_document",
          "scam_or_abuse",
          "underage",
          "other"
        ],
        "title": "BlockedFaceReasonCode"
      },
      "StoreVerificationConsentRequest": {
        "type": "object",
        "properties": {
          "consent_version_id": {
            "type": "integer"
          },
          "text_sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "minLength": 64,
            "maxLength": 64
          }
        },
        "required": [
          "consent_version_id",
          "text_sha256"
        ],
        "title": "StoreVerificationConsentRequest"
      },
      "StoreVerificationRequest": {
        "type": "object",
        "properties": {
          "callback_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Where the person's browser is sent after the final screen. Ignored unless the request is HMAC-signed.",
            "maxLength": 2048
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your identifier for the person. Ignored unless the request is HMAC-signed: the public key alone cannot tie a session to your user.",
            "maxLength": 255
          },
          "external_metadata": {
            "type": [
              "object",
              "null"
            ],
            "description": "Free-form data returned in the API and webhooks. Accepted without a signature, so treat it as client-supplied.",
            "additionalProperties": {}
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "description": "Free-form data kept on the session and not returned to integrations.",
            "additionalProperties": {}
          }
        },
        "title": "StoreVerificationRequest"
      },
      "UploadMediaRequest": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "contentMediaType": "application/octet-stream"
          },
          "type": {
            "type": "string",
            "description": "What the file is: the person's selfie, or one side of their document.",
            "enum": [
              "selfie",
              "document"
            ]
          },
          "side": {
            "type": "string",
            "description": "Which side of the document. Required when `type` is `document`.",
            "enum": [
              "front",
              "back"
            ]
          },
          "document": {
            "type": "string",
            "description": "The kind of document. Required when `type` is `document`.",
            "enum": [
              "id",
              "driver_license",
              "passport",
              "residence_permit"
            ]
          }
        },
        "required": [
          "file",
          "type"
        ],
        "title": "UploadMediaRequest"
      },
      "VerificationDocumentResource": {
        "type": "object",
        "properties": {
          "document": {
            "type": "object",
            "properties": {
              "type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issuing_country": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issuing_subdivision": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "fields": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "first_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "middle_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "last_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "date_of_birth": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "gender": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "nationality": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "place_of_birth": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "address": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "document_number": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "issue_date": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "expiry_date": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "first_name",
                      "middle_name",
                      "last_name",
                      "date_of_birth",
                      "gender",
                      "nationality",
                      "place_of_birth",
                      "address",
                      "document_number",
                      "issue_date",
                      "expiry_date"
                    ]
                  },
                  {
                    "type": "array",
                    "items": {}
                  }
                ]
              }
            },
            "required": [
              "type",
              "issuing_country",
              "issuing_subdivision",
              "fields"
            ]
          },
          "media": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "type",
                "url"
              ]
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "attempt_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "attempt_id"
            ]
          }
        },
        "required": [
          "document",
          "media",
          "meta"
        ],
        "title": "VerificationDocumentResource"
      },
      "VerificationEstimationResource": {
        "type": "object",
        "properties": {
          "verification_id": {
            "type": "string"
          },
          "attempt_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "age_threshold": {
            "type": "object",
            "properties": {
              "minimum": {
                "type": "integer"
              },
              "passed": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "confidence": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "minimum",
              "passed",
              "confidence"
            ]
          },
          "gender": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "value": {
                "type": [
                  "integer",
                  "null"
                ],
                "enum": [
                  0,
                  1,
                  null
                ]
              },
              "confidence": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "value",
              "confidence"
            ]
          }
        },
        "required": [
          "verification_id",
          "attempt_id",
          "age_threshold",
          "gender"
        ],
        "title": "VerificationEstimationResource"
      },
      "WorkspaceResource": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "flow_type": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "age_mode": {
            "type": [
              "string",
              "null"
            ]
          },
          "age_threshold": {
            "type": [
              "integer",
              "null"
            ]
          },
          "verification_type": {
            "type": "string"
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhook_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "allow_expired_documents": {
            "type": "boolean"
          },
          "allow_duplicate_accounts": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name",
          "flow_type",
          "mode",
          "age_mode",
          "age_threshold",
          "verification_type",
          "redirect_url",
          "webhook_url",
          "allow_expired_documents",
          "allow_duplicate_accounts"
        ],
        "title": "WorkspaceResource"
      }
    },
    "responses": {
      "ValidationException": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "message": {
                  "type": "string",
                  "description": "Errors overview."
                },
                "errors": {
                  "type": "object",
                  "description": "A detailed description of each field that failed validation.",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "required": [
                "message",
                "errors"
              ]
            }
          }
        }
      },
      "ModelNotFoundException": {
        "description": "Not found",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "message": {
                  "type": "string",
                  "description": "Error overview."
                }
              },
              "required": [
                "message"
              ]
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "verificationDecision": {
      "post": {
        "summary": "Verification status changed",
        "description": "ProofAge sends this request to the workspace's webhook URL each time a verification moves to `approved`, `declined`, `resubmission_requested`, `review`, `abandoned` or `expired`. One verification can therefore produce several events. Verify `X-HMAC-Signature` before trusting the body: it is the hex HMAC-SHA256 of `{X-Timestamp}.{raw body}`, keyed with the workspace's active secret key. Respond with any 2xx. A 408, 429 or 5xx response, or no response within 30 seconds, is retried after 5, 15 and 60 minutes. See [Webhooks](/integration/webhooks).",
        "parameters": [
          {
            "name": "X-HMAC-Signature",
            "in": "header",
            "required": true,
            "description": "Hex HMAC-SHA256 of `{X-Timestamp}.{raw body}` with the workspace's active secret key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time, in seconds, when the request was signed.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "X-Auth-Client",
            "in": "header",
            "required": true,
            "description": "The workspace's public key (`pk_test_\u2026` or `pk_live_\u2026`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-ProofAge-Webhook-Delivery-Id",
            "in": "header",
            "required": true,
            "description": "ID of this delivery. A retry of the same delivery keeps it; use it to ignore duplicates.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "verification_id",
                  "status",
                  "external_id",
                  "external_metadata",
                  "reason",
                  "timestamp",
                  "document"
                ],
                "properties": {
                  "verification_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The verification this event is about."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "declined",
                      "resubmission_requested",
                      "review",
                      "abandoned",
                      "expired"
                    ],
                    "description": "The status the verification moved to."
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Your identifier for the person, as sent when the verification was created."
                  },
                  "external_metadata": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "description": "The metadata you attached when creating the verification, returned as sent.",
                    "additionalProperties": {}
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The decision reason code when the status is `declined` or `resubmission_requested`; `null` otherwise. Also `null` on the `resubmission_requested` a facial age estimation sends when it asks the person for an ID (`GET` reports that verification as `documents_required`). When attempts run out, the `declined` event carries the last attempt's reason. See Decision reasons."
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the status changed, ISO 8601."
                  },
                  "document": {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "passport",
                          "id",
                          "driver_license",
                          "residence_permit",
                          "other",
                          null
                        ]
                      },
                      "issuing_country": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "issuing_subdivision": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The state or province that issued the document, as a bare code beside `issuing_country` (for example `FL` with `US`), or null. Today it is filled for US driving licences and ID cards. Present on every workspace and kept after erasure."
                      },
                      "fields": {
                        "type": "object",
                        "properties": {
                          "first_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "middle_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "last_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "date_of_birth": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date"
                          },
                          "gender": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "F",
                              "M",
                              "X",
                              null
                            ],
                            "description": "`X` means the document states that the sex is unspecified."
                          },
                          "nationality": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Published only when the document itself states it."
                          },
                          "place_of_birth": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "address": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "KYC workspaces only. The printed text as read: not parsed, may contain line breaks."
                          },
                          "document_number": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "issue_date": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date"
                          },
                          "expiry_date": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date"
                          }
                        },
                        "required": [
                          "first_name",
                          "last_name",
                          "date_of_birth",
                          "document_number"
                        ]
                      }
                    },
                    "required": [
                      "type",
                      "issuing_country",
                      "issuing_subdivision",
                      "fields"
                    ],
                    "description": "The same object `GET /v1/verifications/{id}/document` returns, without `media` and `meta`, read from the attempt that decided the verification. Identity (KYC) workspaces receive eleven `fields`; age verification workspaces receive `first_name`, `last_name`, `date_of_birth` and `document_number` only, and the other seven keys are absent. `null` means the field was not read, is not printed on that document, or no document was read at all (a facial age estimation that passed without an ID, a test workspace, a wallet check); `document` itself is never null. Dates are `YYYY-MM-DD` and countries ISO 3166-1 alpha-2 (`XK` for Kosovo); `type` and `gender` are open enums. A resend or a manual retry carries the document as it is now; an automatic retry carries the body as first sent. After the person's data is erased, only `type` and `issuing_country` are kept."
                  },
                  "duplicate_detected": {
                    "type": "boolean",
                    "description": "Present and `true` only when the same face was found behind another verification in the workspace."
                  },
                  "duplicate_count": {
                    "type": "integer",
                    "description": "Present with `duplicate_detected`: how many earlier verifications matched."
                  },
                  "duplicate_of": {
                    "type": "object",
                    "description": "Present with `duplicate_detected`: the first matching verification.",
                    "properties": {
                      "verification_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "external_id": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  },
                  "fingerprint_signals": {
                    "type": "object",
                    "description": "Optional device and network signals collected during the session. Treat it as informational; its fields may change.",
                    "additionalProperties": {}
                  },
                  "manual_moderation": {
                    "type": "object",
                    "description": "Present when a person in the console approved or declined the verification by hand.",
                    "properties": {
                      "action": {
                        "type": "string",
                        "enum": [
                          "approve",
                          "decline"
                        ]
                      },
                      "reason": {
                        "type": "string",
                        "description": "The reason the moderator gave."
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "tenant_admin",
                          "landlord_admin"
                        ],
                        "description": "`tenant_admin` is someone on your team; `landlord_admin` is ProofAge staff."
                      },
                      "performed_by": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "role": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      },
                      "source_status": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "On an approval: the status the verification had before it."
                      },
                      "source_reason": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "On an approval: the reason code it had before it."
                      }
                    }
                  }
                }
              },
              "example": {
                "verification_id": "550e8400-e29b-41d4-a716-446655440000",
                "status": "declined",
                "external_id": "user_12345",
                "external_metadata": {
                  "plan": "premium"
                },
                "reason": "document.face.mismatch",
                "timestamp": "2026-09-29T12:05:00+00:00",
                "document": {
                  "type": "passport",
                  "issuing_country": "DE",
                  "issuing_subdivision": null,
                  "fields": {
                    "first_name": "JANE",
                    "middle_name": null,
                    "last_name": "DOE",
                    "date_of_birth": "1990-04-12",
                    "gender": "F",
                    "nationality": "DE",
                    "place_of_birth": "BERLIN",
                    "address": null,
                    "document_number": "C01X00T47",
                    "issue_date": "2020-04-14",
                    "expiry_date": "2030-04-13"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx acknowledges the delivery."
          }
        },
        "security": []
      }
    }
  }
}
