FACE REGISTRY / DEVELOPERS

Tài liệu API

Tra cứu request, bộ lọc, cấu trúc dữ liệu và lỗi. Tài liệu và hợp đồng cho AI được tạo từ cùng một OpenAPI, cập nhật cùng mỗi lần deploy.

Bắt đầu tích hợp

Base URL: https://face-ai.luuanh.online

Request dữ liệu cần header Authorization: Bearer YOUR_API_TOKEN. Gửi JSON với Content-Type: application/json. Docs và health không yêu cầu token.

curl 'https://face-ai.luuanh.online/api/faces?used_status=false' \
  -H 'Authorization: Bearer YOUR_API_TOKEN'

Lưu ý khi lấy mẫu: GET /api/faces tăng count_found của các dòng trả về. API lấy 10 ID đầu tiên khớp bộ lọc theo ID tăng dần, rồi random tối đa 5 dòng; không tự đánh dấu used. Retry có thể tăng bộ đếm thêm lần nữa. Dùng /api/faces/manage hoặc /api/faces/exists khi cần chỉ đọc.

GET/

Serve dashboard HTML

Công khai · Không cần token

Responses

200 · Dashboard shell. Successful API tokens are remembered in origin-scoped localStorage.
{
  "description": "Dashboard shell. Successful API tokens are remembered in origin-scoped localStorage.",
  "content": {
    "text/html": {
      "schema": {
        "type": "string"
      }
    }
  }
}
Hợp đồng operation đầy đủ
{
  "operationId": "getDashboard",
  "tags": [
    "Dashboard"
  ],
  "security": [],
  "summary": "Serve dashboard HTML",
  "responses": {
    "200": {
      "description": "Dashboard shell. Successful API tokens are remembered in origin-scoped localStorage.",
      "content": {
        "text/html": {
          "schema": {
            "type": "string"
          }
        }
      }
    }
  }
}

GET/health

Run SELECT 1 on D1

Does not validate table migrations or the API token.

Công khai · Không cần token

Responses

200 · Database reachable
{
  "description": "Database reachable",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "status": {
            "const": "ok"
          },
          "database": {
            "const": "connected"
          }
        },
        "required": [
          "status",
          "database"
        ]
      }
    }
  }
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
Hợp đồng operation đầy đủ
{
  "operationId": "getHealth",
  "tags": [
    "Health"
  ],
  "security": [],
  "summary": "Run SELECT 1 on D1",
  "description": "Does not validate table migrations or the API token.",
  "responses": {
    "200": {
      "description": "Database reachable",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "status": {
                "const": "ok"
              },
              "database": {
                "const": "connected"
              }
            },
            "required": [
              "status",
              "database"
            ]
          }
        }
      }
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    }
  }
}

GET/api/faces

Sample up to five of the first ten matching IDs

All filters optional and combined using AND. Exact case-sensitive equality for trimmed username/provider. Takes the first ten matching IDs in ascending order, then randomly selects at most five of those candidates and increments count_found exactly once for each returned row. Response order is unspecified; next_cursor is always null. before_id is only an id < bound, not pagination. Use GET /api/faces/exists for reliable existence checks without incrementing count_found.

Xác thực: Bearer token

Tham số

username query · tùy chọn

Exact username; omit to disable this filter.

{
  "type": "string",
  "minLength": 1,
  "description": "Trimmed by the server; trimmed length must be 1-255."
}
provider query · tùy chọn

Exact provider; omit to disable this filter.

{
  "type": "string",
  "minLength": 1,
  "description": "Trimmed by the server; trimmed length must be 1-255."
}
used_status query · tùy chọn

Filter database column used. The key used is not supported in query.

{
  "type": "string",
  "enum": [
    "true",
    "false",
    "1",
    "0"
  ]
}
before_id query · tùy chọn

Optional id < before_id bound for the random sample, not a pagination cursor. Positive decimal without leading zeros.

{
  "type": "integer",
  "minimum": 1,
  "maximum": 9007199254740991
}

Responses

200 · Zero to five random matches from the first ten matching IDs in ascending order, each with count_found incremented. next_cursor is always null.
{
  "description": "Zero to five random matches from the first ten matching IDs in ascending order, each with count_found incremented. next_cursor is always null.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/FacePage"
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "searchFaces",
  "tags": [
    "Faces"
  ],
  "summary": "Sample up to five of the first ten matching IDs",
  "description": "All filters optional and combined using AND. Exact case-sensitive equality for trimmed username/provider. Takes the first ten matching IDs in ascending order, then randomly selects at most five of those candidates and increments count_found exactly once for each returned row. Response order is unspecified; next_cursor is always null. before_id is only an id < bound, not pagination. Use GET /api/faces/exists for reliable existence checks without incrementing count_found.",
  "parameters": [
    {
      "name": "username",
      "in": "query",
      "schema": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-255."
      },
      "description": "Exact username; omit to disable this filter."
    },
    {
      "name": "provider",
      "in": "query",
      "schema": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-255."
      },
      "description": "Exact provider; omit to disable this filter."
    },
    {
      "name": "used_status",
      "in": "query",
      "schema": {
        "type": "string",
        "enum": [
          "true",
          "false",
          "1",
          "0"
        ]
      },
      "description": "Filter database column used. The key used is not supported in query."
    },
    {
      "name": "before_id",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      },
      "description": "Optional id < before_id bound for the random sample, not a pagination cursor. Positive decimal without leading zeros."
    }
  ],
  "responses": {
    "200": {
      "description": "Zero to five random matches from the first ten matching IDs in ascending order, each with count_found incremented. next_cursor is always null.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FacePage"
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

POST/api/faces

Insert exactly one face record

Auto-incremented ID. No deduplication or idempotency key; repeating a successful POST can create duplicates. Unknown body fields rejected. used_status can be supplied instead of used; when both exist they must agree after normalization.

Xác thực: Bearer token

Request body
{
  "required": true,
  "description": "JSON object, at most 32768 bytes including JSON encoding. Content-Type application/json required.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/FaceCreate"
      }
    }
  }
}

Responses

201 · Face created
{
  "description": "Face created",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Face"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Location": {
      "description": "Relative URL of the new resource.",
      "schema": {
        "type": "string"
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
413 ·
{
  "$ref": "#/components/responses/Error413"
}
415 ·
{
  "$ref": "#/components/responses/Error415"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "createFace",
  "tags": [
    "Faces"
  ],
  "summary": "Insert exactly one face record",
  "description": "Auto-incremented ID. No deduplication or idempotency key; repeating a successful POST can create duplicates. Unknown body fields rejected. used_status can be supplied instead of used; when both exist they must agree after normalization.",
  "requestBody": {
    "required": true,
    "description": "JSON object, at most 32768 bytes including JSON encoding. Content-Type application/json required.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/FaceCreate"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Face created",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Face"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      },
      "headers": {
        "Location": {
          "description": "Relative URL of the new resource.",
          "schema": {
            "type": "string"
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "413": {
      "$ref": "#/components/responses/Error413"
    },
    "415": {
      "$ref": "#/components/responses/Error415"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

DELETE/api/faces

Delete all face records for one exact username/provider pair

Both query parameters are required, trimmed, case-sensitive, and combined with AND. Atomically deletes every matching face row, including used rows and duplicates, without sampling or pagination. Returns the actual deleted count; a pair with no matching records or retry returns 200 with deleted_count=0. Rejects missing, blank, overlong, duplicate or unknown query parameters and request bodies. Does not delete legacy items or remote files referenced by link_download. No migration required.

Xác thực: Bearer token

Tham số

provider query · bắt buộc

Required exact value after trimming; trimmed length must be 1-255. Case-sensitive.

{
  "type": "string",
  "minLength": 1
}
username query · bắt buộc

Required exact value after trimming; trimmed length must be 1-255. Case-sensitive.

{
  "type": "string",
  "minLength": 1
}

Responses

200 · All rows matching the exact pair deleted. Zero matches is a successful no-op.
{
  "description": "All rows matching the exact pair deleted. Zero matches is a successful no-op.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "provider",
              "username",
              "deleted_count"
            ],
            "properties": {
              "provider": {
                "type": "string"
              },
              "username": {
                "type": "string"
              },
              "deleted_count": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "example": {
        "data": {
          "provider": "TRUST",
          "username": "leson",
          "deleted_count": 12
        }
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
405 · Supported methods for /api/faces.
{
  "description": "Supported methods for /api/faces.",
  "headers": {
    "Allow": {
      "schema": {
        "type": "string",
        "const": "GET, POST, DELETE"
      }
    }
  }
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "deleteFacesByIdentity",
  "tags": [
    "Faces"
  ],
  "summary": "Delete all face records for one exact username/provider pair",
  "description": "Both query parameters are required, trimmed, case-sensitive, and combined with AND. Atomically deletes every matching face row, including used rows and duplicates, without sampling or pagination. Returns the actual deleted count; a pair with no matching records or retry returns 200 with deleted_count=0. Rejects missing, blank, overlong, duplicate or unknown query parameters and request bodies. Does not delete legacy items or remote files referenced by link_download. No migration required.",
  "parameters": [
    {
      "name": "provider",
      "in": "query",
      "required": true,
      "description": "Required exact value after trimming; trimmed length must be 1-255. Case-sensitive.",
      "schema": {
        "type": "string",
        "minLength": 1
      },
      "example": "TRUST"
    },
    {
      "name": "username",
      "in": "query",
      "required": true,
      "description": "Required exact value after trimming; trimmed length must be 1-255. Case-sensitive.",
      "schema": {
        "type": "string",
        "minLength": 1
      },
      "example": "leson"
    }
  ],
  "responses": {
    "200": {
      "description": "All rows matching the exact pair deleted. Zero matches is a successful no-op.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "provider",
                  "username",
                  "deleted_count"
                ],
                "properties": {
                  "provider": {
                    "type": "string"
                  },
                  "username": {
                    "type": "string"
                  },
                  "deleted_count": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          },
          "example": {
            "data": {
              "provider": "TRUST",
              "username": "leson",
              "deleted_count": 12
            }
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "405": {
      "description": "Supported methods for /api/faces.",
      "headers": {
        "Allow": {
          "schema": {
            "type": "string",
            "const": "GET, POST, DELETE"
          }
        }
      }
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/api/faces/{id}

Read one face by integer ID

Xác thực: Bearer token

Responses

200 · Face found
{
  "description": "Face found",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Face"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
404 ·
{
  "$ref": "#/components/responses/Error404"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "getFace",
  "tags": [
    "Faces"
  ],
  "summary": "Read one face by integer ID",
  "responses": {
    "200": {
      "description": "Face found",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Face"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "404": {
      "$ref": "#/components/responses/Error404"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

PATCH/api/faces/{id}

Mark this exact record as used

Sets used=true on the exact ID, including older duplicate records. Idempotent; does not change count_found or other fields. Rejects query parameters and unknown body fields. Only used=true is accepted.

Xác thực: Bearer token

Request body
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "used"
        ],
        "properties": {
          "used": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "example": {
        "used": true
      }
    }
  }
}

Responses

200 · Face found
{
  "description": "Face found",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Face"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
404 ·
{
  "$ref": "#/components/responses/Error404"
}
413 ·
{
  "$ref": "#/components/responses/Error413"
}
415 ·
{
  "$ref": "#/components/responses/Error415"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "markFaceUsedById",
  "tags": [
    "Faces"
  ],
  "summary": "Mark this exact record as used",
  "description": "Sets used=true on the exact ID, including older duplicate records. Idempotent; does not change count_found or other fields. Rejects query parameters and unknown body fields. Only used=true is accepted.",
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "used"
          ],
          "properties": {
            "used": {
              "type": "boolean",
              "const": true
            }
          }
        },
        "example": {
          "used": true
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Face found",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Face"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "404": {
      "$ref": "#/components/responses/Error404"
    },
    "413": {
      "$ref": "#/components/responses/Error413"
    },
    "415": {
      "$ref": "#/components/responses/Error415"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/api/stats

Global statistics over the face table

No filtering parameters. Seven UTC calendar days including today. Null created_at rows count in totals but not daily activity. providers contains at most eight rows, sorted by total descending then provider ascending. summary.providers is the total distinct count, not the truncated list length.

Xác thực: Bearer token

Responses

200 · Global statistics
{
  "description": "Global statistics",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Statistics"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "getFaceStatistics",
  "tags": [
    "Statistics"
  ],
  "summary": "Global statistics over the face table",
  "description": "No filtering parameters. Seven UTC calendar days including today. Null created_at rows count in totals but not daily activity. providers contains at most eight rows, sorted by total descending then provider ascending. summary.providers is the total distinct count, not the truncated list length.",
  "responses": {
    "200": {
      "description": "Global statistics",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Statistics"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      }
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/items

List legacy items with offset pagination

ORDER BY created_at DESC, id DESC. Unknown query keys are currently ignored. Query values must be decimal integers.

Xác thực: Bearer token

Tham số

limit query · tùy chọn

{
  "type": "integer",
  "minimum": 1,
  "maximum": 100,
  "default": 20
}
offset query · tùy chọn

{
  "type": "integer",
  "minimum": 0,
  "maximum": 1000000,
  "default": 0
}

Responses

200 · Items page
{
  "description": "Items page",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Item"
            },
            "maxItems": 100
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "data",
          "limit",
          "offset"
        ]
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "listItems",
  "tags": [
    "Legacy items"
  ],
  "summary": "List legacy items with offset pagination",
  "description": "ORDER BY created_at DESC, id DESC. Unknown query keys are currently ignored. Query values must be decimal integers.",
  "parameters": [
    {
      "name": "limit",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100,
        "default": 20
      }
    },
    {
      "name": "offset",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 0,
        "maximum": 1000000,
        "default": 0
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Items page",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Item"
                },
                "maxItems": 100
              },
              "limit": {
                "type": "integer"
              },
              "offset": {
                "type": "integer",
                "minimum": 0
              }
            },
            "required": [
              "data",
              "limit",
              "offset"
            ]
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

POST/items

Create one legacy item

Xác thực: Bearer token

Request body
{
  "required": true,
  "description": "JSON object, at most 32768 bytes including JSON encoding. Content-Type application/json required.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ItemInput"
      }
    }
  }
}

Responses

201 · Item created
{
  "description": "Item created",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Item"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Location": {
      "description": "Relative URL of the new resource.",
      "schema": {
        "type": "string"
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
413 ·
{
  "$ref": "#/components/responses/Error413"
}
415 ·
{
  "$ref": "#/components/responses/Error415"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "createItem",
  "tags": [
    "Legacy items"
  ],
  "summary": "Create one legacy item",
  "requestBody": {
    "required": true,
    "description": "JSON object, at most 32768 bytes including JSON encoding. Content-Type application/json required.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/ItemInput"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Item created",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Item"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      },
      "headers": {
        "Location": {
          "description": "Relative URL of the new resource.",
          "schema": {
            "type": "string"
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "413": {
      "$ref": "#/components/responses/Error413"
    },
    "415": {
      "$ref": "#/components/responses/Error415"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/items/{id}

Read legacy item

Xác thực: Bearer token

Responses

200 · Item found
{
  "description": "Item found",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Item"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
404 ·
{
  "$ref": "#/components/responses/Error404"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "getItem",
  "tags": [
    "Legacy items"
  ],
  "summary": "Read legacy item",
  "responses": {
    "200": {
      "description": "Item found",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Item"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      }
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "404": {
      "$ref": "#/components/responses/Error404"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

PUT/items/{id}

Replace legacy item name and description

Not PATCH: omitting description clears it to empty string. Updates updated_at and preserves created_at. Extra input fields ignored.

Xác thực: Bearer token

Request body
{
  "required": true,
  "description": "JSON object, at most 32768 bytes including JSON encoding. Content-Type application/json required.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ItemInput"
      }
    }
  }
}

Responses

200 · Item updated
{
  "description": "Item updated",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Item"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
404 ·
{
  "$ref": "#/components/responses/Error404"
}
413 ·
{
  "$ref": "#/components/responses/Error413"
}
415 ·
{
  "$ref": "#/components/responses/Error415"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "replaceItem",
  "tags": [
    "Legacy items"
  ],
  "summary": "Replace legacy item name and description",
  "description": "Not PATCH: omitting description clears it to empty string. Updates updated_at and preserves created_at. Extra input fields ignored.",
  "requestBody": {
    "required": true,
    "description": "JSON object, at most 32768 bytes including JSON encoding. Content-Type application/json required.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/ItemInput"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Item updated",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Item"
              }
            },
            "required": [
              "data"
            ]
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "404": {
      "$ref": "#/components/responses/Error404"
    },
    "413": {
      "$ref": "#/components/responses/Error413"
    },
    "415": {
      "$ref": "#/components/responses/Error415"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

DELETE/items/{id}

Delete legacy item

Xác thực: Bearer token

Responses

204 · Deleted; no response body.
{
  "description": "Deleted; no response body."
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
404 ·
{
  "$ref": "#/components/responses/Error404"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "deleteItem",
  "tags": [
    "Legacy items"
  ],
  "summary": "Delete legacy item",
  "responses": {
    "204": {
      "description": "Deleted; no response body."
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "404": {
      "$ref": "#/components/responses/Error404"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/api/faces/exists

Check an exact provider, username and background hash

Queries the complete matching set without random sampling, count_found increments or used updates. Both used statuses are included. If duplicates exist, returns the newest matching id. Missing, unknown or duplicate query parameters return 400. No pagination and no migration required.

Xác thực: Bearer token

Tham số

provider query · bắt buộc

Required exact value after trimming. Provider/username are case-sensitive.

{
  "type": "string",
  "minLength": 1,
  "maxLength": 255
}
username query · bắt buộc

Required exact value after trimming. Provider/username are case-sensitive.

{
  "type": "string",
  "minLength": 1,
  "maxLength": 255
}
sha256_background query · bắt buộc

Required 64-character hexadecimal SHA-256 after trimming; normalized to lowercase.

{
  "type": "string",
  "minLength": 1,
  "maxLength": 64
}

Responses

200 · Exact existence result; data is a Face when exists=true and null otherwise.
{
  "description": "Exact existence result; data is a Face when exists=true and null otherwise.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/FaceExists"
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "checkFaceExists",
  "tags": [
    "Faces"
  ],
  "summary": "Check an exact provider, username and background hash",
  "description": "Queries the complete matching set without random sampling, count_found increments or used updates. Both used statuses are included. If duplicates exist, returns the newest matching id. Missing, unknown or duplicate query parameters return 400. No pagination and no migration required.",
  "parameters": [
    {
      "name": "provider",
      "in": "query",
      "required": true,
      "schema": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255
      },
      "description": "Required exact value after trimming. Provider/username are case-sensitive."
    },
    {
      "name": "username",
      "in": "query",
      "required": true,
      "schema": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255
      },
      "description": "Required exact value after trimming. Provider/username are case-sensitive."
    },
    {
      "name": "sha256_background",
      "in": "query",
      "required": true,
      "schema": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      },
      "description": "Required 64-character hexadecimal SHA-256 after trimming; normalized to lowercase."
    }
  ],
  "responses": {
    "200": {
      "description": "Exact existence result; data is a Face when exists=true and null otherwise.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FaceExists"
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/api/faces/manage

Browse all face records in read-only pages

Read-only management list ordered by id DESC. Does not increment count_found or change used. Exact username/provider/used_status filters are combined with AND. All parameters are optional; unknown and duplicate parameters are rejected. Offset pagination can shift if records are inserted between requests. filename is a literal substring of the last URL path segment, excluding directories, query strings and fragments. Matches raw or percent-encoded text, ignoring ASCII case; trimmed length 1-255.

Xác thực: Bearer token

Tham số

username query · tùy chọn

Exact username; omit to disable this filter.

{
  "type": "string",
  "minLength": 1,
  "description": "Trimmed by the server; trimmed length must be 1-255."
}
provider query · tùy chọn

Exact provider; omit to disable this filter.

{
  "type": "string",
  "minLength": 1,
  "description": "Trimmed by the server; trimmed length must be 1-255."
}
filename query · tùy chọn

Partial filename, literal substring; raw and percent-encoded forms supported. ASCII case-insensitive. Combined with other filters using AND.

{
  "type": "string",
  "minLength": 1,
  "maxLength": 255
}
used_status query · tùy chọn

Filter database column used. The key used is not supported in query.

{
  "type": "string",
  "enum": [
    "true",
    "false",
    "1",
    "0"
  ]
}
limit query · tùy chọn

Page size; decimal integer.

{
  "type": "integer",
  "minimum": 1,
  "maximum": 100,
  "default": 25
}
offset query · tùy chọn

Number of matching rows to skip; decimal integer.

{
  "type": "integer",
  "minimum": 0,
  "maximum": 9007199254740991,
  "default": 0
}

Responses

200 · A page of matching records and the total matching count. Out-of-range offsets return an empty data array.
{
  "description": "A page of matching records and the total matching count. Out-of-range offsets return an empty data array.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ManagedFacePage"
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "listManagedFaces",
  "tags": [
    "Faces"
  ],
  "summary": "Browse all face records in read-only pages",
  "description": "Read-only management list ordered by id DESC. Does not increment count_found or change used. Exact username/provider/used_status filters are combined with AND. All parameters are optional; unknown and duplicate parameters are rejected. Offset pagination can shift if records are inserted between requests. filename is a literal substring of the last URL path segment, excluding directories, query strings and fragments. Matches raw or percent-encoded text, ignoring ASCII case; trimmed length 1-255.",
  "parameters": [
    {
      "name": "username",
      "in": "query",
      "schema": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-255."
      },
      "description": "Exact username; omit to disable this filter."
    },
    {
      "name": "provider",
      "in": "query",
      "schema": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-255."
      },
      "description": "Exact provider; omit to disable this filter."
    },
    {
      "name": "filename",
      "in": "query",
      "schema": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255
      },
      "description": "Partial filename, literal substring; raw and percent-encoded forms supported. ASCII case-insensitive. Combined with other filters using AND."
    },
    {
      "name": "used_status",
      "in": "query",
      "schema": {
        "type": "string",
        "enum": [
          "true",
          "false",
          "1",
          "0"
        ]
      },
      "description": "Filter database column used. The key used is not supported in query."
    },
    {
      "name": "limit",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100,
        "default": 25
      },
      "description": "Page size; decimal integer."
    },
    {
      "name": "offset",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9007199254740991,
        "default": 0
      },
      "description": "Number of matching rows to skip; decimal integer."
    }
  ],
  "responses": {
    "200": {
      "description": "A page of matching records and the total matching count. Out-of-range offsets return an empty data array.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/ManagedFacePage"
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

GET/docs

Read the API documentation page

Công khai · Không cần token

Responses

200 · Read the API documentation page
{
  "description": "Read the API documentation page",
  "content": {
    "text/html": {
      "schema": {
        "type": "string"
      }
    }
  }
}
Hợp đồng operation đầy đủ
{
  "operationId": "getDocumentation",
  "tags": [
    "Documentation"
  ],
  "summary": "Read the API documentation page",
  "security": [],
  "responses": {
    "200": {
      "description": "Read the API documentation page",
      "content": {
        "text/html": {
          "schema": {
            "type": "string"
          }
        }
      }
    }
  }
}

GET/openapi.json

Read the full OpenAPI contract

Công khai · Không cần token

Responses

200 · Read the full OpenAPI contract
{
  "description": "Read the full OpenAPI contract",
  "content": {
    "application/json": {
      "schema": {
        "type": "object"
      }
    }
  }
}
Hợp đồng operation đầy đủ
{
  "operationId": "getOpenApi",
  "tags": [
    "Documentation"
  ],
  "summary": "Read the full OpenAPI contract",
  "security": [],
  "responses": {
    "200": {
      "description": "Read the full OpenAPI contract",
      "content": {
        "application/json": {
          "schema": {
            "type": "object"
          }
        }
      }
    }
  }
}

GET/llms.txt

Discover API documentation and side effects for AI agents

Công khai · Không cần token

Responses

200 · Discover API documentation and side effects for AI agents
{
  "description": "Discover API documentation and side effects for AI agents",
  "content": {
    "text/plain": {
      "schema": {
        "type": "string"
      }
    }
  }
}
Hợp đồng operation đầy đủ
{
  "operationId": "getAiGuide",
  "tags": [
    "Documentation"
  ],
  "summary": "Discover API documentation and side effects for AI agents",
  "security": [],
  "responses": {
    "200": {
      "description": "Discover API documentation and side effects for AI agents",
      "content": {
        "text/plain": {
          "schema": {
            "type": "string"
          }
        }
      }
    }
  }
}

GET/llms-full.txt

Read the complete OpenAPI contract as text

Công khai · Không cần token

Responses

200 · Read the complete OpenAPI contract as text
{
  "description": "Read the complete OpenAPI contract as text",
  "content": {
    "text/plain": {
      "schema": {
        "type": "string"
      }
    }
  }
}
Hợp đồng operation đầy đủ
{
  "operationId": "getFullAiContract",
  "tags": [
    "Documentation"
  ],
  "summary": "Read the complete OpenAPI contract as text",
  "security": [],
  "responses": {
    "200": {
      "description": "Read the complete OpenAPI contract as text",
      "content": {
        "text/plain": {
          "schema": {
            "type": "string"
          }
        }
      }
    }
  }
}

PATCH/api/faces/toggle-used

Mark one exact matching face as used

All three JSON fields are combined with AND. Atomically sets used=true on the matching row with the largest id, even if already used. Never toggles back to false or advances to another duplicate on retry. Changes at most one row, preserving count_found and every other field. Returns 404 when no row matches. Rejects unknown body fields and query parameters. No migration required.

Xác thực: Bearer token

Request body
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "provider",
          "username",
          "sha256_background"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Required exact value after trimming. Provider/username are case-sensitive."
          },
          "username": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Required exact value after trimming. Provider/username are case-sensitive."
          },
          "sha256_background": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Required 64-character hexadecimal SHA-256 after trimming; normalized to lowercase."
          }
        }
      },
      "example": {
        "provider": "TRUST",
        "username": "leson",
        "sha256_background": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
      }
    }
  }
}

Responses

200 · Newest matching face marked used=true; repeated calls keep the same row used.
{
  "description": "Newest matching face marked used=true; repeated calls keep the same row used.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Face"
          }
        }
      }
    }
  }
}
400 ·
{
  "$ref": "#/components/responses/Error400"
}
401 ·
{
  "$ref": "#/components/responses/Error401"
}
404 ·
{
  "$ref": "#/components/responses/Error404"
}
405 · Only PATCH is allowed.
{
  "description": "Only PATCH is allowed.",
  "headers": {
    "Allow": {
      "schema": {
        "type": "string",
        "const": "PATCH"
      }
    }
  }
}
413 ·
{
  "$ref": "#/components/responses/Error413"
}
415 ·
{
  "$ref": "#/components/responses/Error415"
}
500 ·
{
  "$ref": "#/components/responses/Error500"
}
503 ·
{
  "$ref": "#/components/responses/Error503"
}
Hợp đồng operation đầy đủ
{
  "operationId": "markFaceUsed",
  "tags": [
    "Faces"
  ],
  "summary": "Mark one exact matching face as used",
  "description": "All three JSON fields are combined with AND. Atomically sets used=true on the matching row with the largest id, even if already used. Never toggles back to false or advances to another duplicate on retry. Changes at most one row, preserving count_found and every other field. Returns 404 when no row matches. Rejects unknown body fields and query parameters. No migration required.",
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "provider",
            "username",
            "sha256_background"
          ],
          "properties": {
            "provider": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required exact value after trimming. Provider/username are case-sensitive."
            },
            "username": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required exact value after trimming. Provider/username are case-sensitive."
            },
            "sha256_background": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "Required 64-character hexadecimal SHA-256 after trimming; normalized to lowercase."
            }
          }
        },
        "example": {
          "provider": "TRUST",
          "username": "leson",
          "sha256_background": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Newest matching face marked used=true; repeated calls keep the same row used.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Face"
              }
            }
          }
        }
      }
    },
    "400": {
      "$ref": "#/components/responses/Error400"
    },
    "401": {
      "$ref": "#/components/responses/Error401"
    },
    "404": {
      "$ref": "#/components/responses/Error404"
    },
    "405": {
      "description": "Only PATCH is allowed.",
      "headers": {
        "Allow": {
          "schema": {
            "type": "string",
            "const": "PATCH"
          }
        }
      }
    },
    "413": {
      "$ref": "#/components/responses/Error413"
    },
    "415": {
      "$ref": "#/components/responses/Error415"
    },
    "500": {
      "$ref": "#/components/responses/Error500"
    },
    "503": {
      "$ref": "#/components/responses/Error503"
    }
  }
}

Schemas & xác thực

Các tham chiếu #/components/schemas/... trong request và response được định nghĩa dưới đây.

securitySchemes
{
  "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "Exact header Authorization: Bearer <API_TOKEN>. Distinct secrets for dev and production. No token-issuing endpoint."
  }
}
schemas
{
  "Error": {
    "type": "object",
    "properties": {
      "error": {
        "type": "string"
      }
    },
    "required": [
      "error"
    ]
  },
  "StatusInput": {
    "description": "Accepted normalized status values. Strings are case-sensitive and are not trimmed.",
    "oneOf": [
      {
        "type": "boolean"
      },
      {
        "type": "integer",
        "enum": [
          0,
          1
        ]
      },
      {
        "type": "string",
        "enum": [
          "true",
          "false",
          "1",
          "0"
        ]
      }
    ]
  },
  "Face": {
    "type": "object",
    "properties": {
      "id": {
        "type": "integer",
        "minimum": 1
      },
      "provider": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255
      },
      "username": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255
      },
      "sha256_background": {
        "type": "string",
        "pattern": "^[0-9a-f]{64}$"
      },
      "created_at": {
        "type": [
          "integer",
          "null"
        ],
        "minimum": 0,
        "maximum": 253402300799,
        "description": "Unix seconds, not milliseconds."
      },
      "used": {
        "type": "boolean"
      },
      "link_download": {
        "type": "string",
        "minLength": 1,
        "maxLength": 4096,
        "description": "HTTP or HTTPS download URL; server does not fetch or check availability."
      },
      "count_found": {
        "type": "integer",
        "minimum": 0,
        "description": "Number of times selected by GET /api/faces. Existence/detail checks do not increment it."
      }
    },
    "required": [
      "id",
      "provider",
      "username",
      "sha256_background",
      "created_at",
      "used",
      "link_download",
      "count_found"
    ]
  },
  "FaceCreate": {
    "type": "object",
    "properties": {
      "provider": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-255."
      },
      "username": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-255."
      },
      "sha256_background": {
        "type": "string",
        "description": "Trimmed, then must contain exactly 64 hexadecimal characters. Normalized to lowercase."
      },
      "created_at": {
        "type": [
          "integer",
          "null"
        ],
        "minimum": 0,
        "maximum": 253402300799,
        "description": "Unix seconds. Omit to use server time; explicit null is preserved."
      },
      "used": {
        "$ref": "#/components/schemas/StatusInput"
      },
      "used_status": {
        "$ref": "#/components/schemas/StatusInput"
      },
      "link_download": {
        "type": "string",
        "description": "Trimmed, length 1-4096; parsed URL must have protocol http: or https:."
      }
    },
    "required": [
      "provider",
      "username",
      "sha256_background",
      "link_download"
    ],
    "additionalProperties": false,
    "example": {
      "provider": "example",
      "username": "leson",
      "sha256_background": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "link_download": "https://example.com/face.jpg",
      "used": false
    }
  },
  "FacePage": {
    "type": "object",
    "properties": {
      "data": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Face"
        },
        "maxItems": 5
      },
      "limit": {
        "type": "integer",
        "const": 5
      },
      "next_cursor": {
        "type": "null",
        "description": "Always null: random samples have no next page."
      }
    },
    "required": [
      "data",
      "limit",
      "next_cursor"
    ]
  },
  "Statistics": {
    "type": "object",
    "properties": {
      "summary": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "used": {
            "type": "integer",
            "minimum": 0
          },
          "unused": {
            "type": "integer",
            "minimum": 0
          },
          "providers": {
            "type": "integer",
            "minimum": 0
          },
          "added_today": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "total",
          "used",
          "unused",
          "providers",
          "added_today"
        ]
      },
      "providers": {
        "type": "array",
        "maxItems": 8,
        "items": {
          "type": "object",
          "properties": {
            "provider": {
              "type": "string"
            },
            "total": {
              "type": "integer",
              "minimum": 0
            },
            "used": {
              "type": "integer",
              "minimum": 0
            },
            "unused": {
              "type": "integer",
              "minimum": 0
            }
          },
          "required": [
            "provider",
            "total",
            "used",
            "unused"
          ]
        }
      },
      "daily": {
        "type": "array",
        "minItems": 7,
        "maxItems": 7,
        "items": {
          "type": "object",
          "properties": {
            "date": {
              "type": "string",
              "format": "date"
            },
            "total": {
              "type": "integer",
              "minimum": 0
            }
          },
          "required": [
            "date",
            "total"
          ]
        }
      },
      "timezone": {
        "type": "string",
        "const": "UTC"
      }
    },
    "required": [
      "summary",
      "providers",
      "daily",
      "timezone"
    ]
  },
  "Item": {
    "type": "object",
    "properties": {
      "id": {
        "type": "string",
        "format": "uuid"
      },
      "name": {
        "type": "string",
        "minLength": 1,
        "maxLength": 200
      },
      "description": {
        "type": "string",
        "maxLength": 5000
      },
      "created_at": {
        "type": "string",
        "format": "date-time"
      },
      "updated_at": {
        "type": "string",
        "format": "date-time"
      }
    },
    "required": [
      "id",
      "name",
      "description",
      "created_at",
      "updated_at"
    ]
  },
  "ItemInput": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "minLength": 1,
        "description": "Trimmed by the server; trimmed length must be 1-200."
      },
      "description": {
        "type": "string",
        "maxLength": 5000,
        "default": ""
      }
    },
    "required": [
      "name"
    ],
    "additionalProperties": true,
    "description": "Extra fields are ignored. Description is not trimmed."
  },
  "FaceExists": {
    "oneOf": [
      {
        "type": "object",
        "properties": {
          "exists": {
            "const": true
          },
          "data": {
            "$ref": "#/components/schemas/Face"
          }
        },
        "required": [
          "exists",
          "data"
        ]
      },
      {
        "type": "object",
        "properties": {
          "exists": {
            "const": false
          },
          "data": {
            "type": "null"
          }
        },
        "required": [
          "exists",
          "data"
        ]
      }
    ]
  },
  "ManagedFacePage": {
    "type": "object",
    "required": [
      "data",
      "total",
      "limit",
      "offset"
    ],
    "properties": {
      "data": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Face"
        }
      },
      "total": {
        "type": "integer",
        "minimum": 0
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100
      },
      "offset": {
        "type": "integer",
        "minimum": 0
      }
    }
  }
}
responses
{
  "Error400": {
    "description": "Invalid JSON object, field, or query parameter. Returns the first validation error.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "Error401": {
    "description": "Missing or incorrect API token.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    },
    "headers": {
      "WWW-Authenticate": {
        "schema": {
          "type": "string",
          "const": "Bearer"
        }
      }
    }
  },
  "Error404": {
    "description": "Route or resource not found.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "Error413": {
    "description": "JSON request body exceeds 32768 bytes.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "Error415": {
    "description": "Content-Type must be application/json, optionally with charset.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "Error500": {
    "description": "Unexpected Worker or D1 error; internal details are not returned.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "Error503": {
    "description": "Worker API_TOKEN is not configured.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}