{
  "openapi": "3.1.0",
  "info": {
    "title": "Verifyd API",
    "version": "1.0.0",
    "description": "Authenticity verdicts for images, links and claims, each with a destination: the real article, the fact-check, or where a picture first appeared.\n\nSend your key in the `x-api-key` header. Without a key, requests are limited per visitor per minute. With a key, usage is metered per UTC day: one unit per image, page check or quick claim check; two for deep claim checks and original searches; three for deep page checks.\n\nSpeeches: POST /api/speech (key) checks and publishes a speech; POST /api/speech/match tells whether a video is a checked speech. Every verdict carries `word` (one word: REAL, AI, ALTERED, OLD, TRUTH, FALSE, DISPUTED, FAKE SITE, UNCHECKED) and `simple` (one plain sentence)."
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    },
    "schemas": {
      "Destination": {
        "type": "object",
        "nullable": true,
        "description": "Where clicking the badge should take the reader. `url` may be relative to the API base (for example /search.html?q=\u2026).",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "real-article",
              "real-site",
              "fact-check",
              "source",
              "first-seen",
              "credentials",
              "origin",
              "search"
            ]
          },
          "label": {
            "type": "string",
            "example": "Read it on the real bbc.com"
          },
          "url": {
            "type": "string"
          },
          "why": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "label",
          "url"
        ]
      },
      "Reason": {
        "type": "object",
        "properties": {
          "tone": {
            "type": "string",
            "enum": [
              "good",
              "bad",
              "warn",
              "info"
            ]
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "link": {
            "type": "string"
          }
        }
      },
      "Verdict": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "claim",
              "page"
            ]
          },
          "level": {
            "type": "string",
            "enum": [
              "verified",
              "unknown",
              "caution",
              "ai",
              "false"
            ]
          },
          "color": {
            "type": "string",
            "enum": [
              "green",
              "gray",
              "amber",
              "red"
            ]
          },
          "label": {
            "type": "string",
            "example": "AI-generated"
          },
          "headline": {
            "type": "string"
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Reason"
            }
          },
          "original": {
            "$ref": "#/components/schemas/Destination"
          },
          "canFindOriginal": {
            "type": "boolean",
            "description": "Images only: POST /api/verify/original can search the web for an earlier copy."
          },
          "evidence": {
            "type": "object",
            "description": "Raw signals: Content Credentials, metadata, detector scores, sightings, fact-checks, sources."
          },
          "word": {
            "type": "string",
            "example": "FALSE"
          },
          "simple": {
            "type": "string",
            "example": "Fact-checkers say this is not true."
          }
        },
        "required": [
          "kind",
          "level",
          "color",
          "label",
          "headline",
          "reasons"
        ]
      },
      "Image": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public image address. Fetched with private-network protection."
          },
          "data": {
            "type": "string",
            "format": "byte",
            "description": "Base64 image bytes, up to about 3 MB."
          },
          "mime": {
            "type": "string",
            "example": "image/jpeg"
          },
          "pageUrl": {
            "type": "string",
            "format": "uri",
            "description": "The page the image appeared on. Powers first-seen and recycled-photo checks."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Claim": {
        "type": "object",
        "properties": {
          "t": {
            "type": "number"
          },
          "text": {
            "type": "string"
          },
          "verdict": {
            "type": "string",
            "enum": [
              "true",
              "false",
              "mixed",
              "unchecked"
            ]
          },
          "word": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "simple": {
            "type": "string"
          },
          "by": {
            "type": "object",
            "nullable": true,
            "properties": {
              "publisher": {
                "type": "string"
              },
              "rating": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "date": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "Speech": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Verdict"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "speaker": {
                "type": "string",
                "nullable": true
              },
              "org": {
                "type": "string",
                "nullable": true
              },
              "eventDate": {
                "type": "string",
                "nullable": true
              },
              "sourceUrl": {
                "type": "string",
                "nullable": true
              },
              "mediaUrl": {
                "type": "string",
                "nullable": true
              },
              "links": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "claims": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Claim"
                }
              },
              "counts": {
                "type": "object"
              },
              "page": {
                "type": "string"
              }
            }
          }
        ]
      }
    }
  },
  "paths": {
    "/api/verify/media": {
      "post": {
        "summary": "Check images",
        "description": "Checks Content Credentials (C2PA), AI-generation metadata, an optional AI detector, and whether the same picture appeared earlier elsewhere. Send one image, or `items` with up to 12.",
        "security": [
          {
            "apiKey": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Image"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "items": {
                        "type": "array",
                        "maxItems": 12,
                        "items": {
                          "$ref": "#/components/schemas/Image"
                        }
                      }
                    },
                    "required": [
                      "items"
                    ]
                  }
                ]
              },
              "example": {
                "url": "https://example.com/photo.jpg",
                "pageUrl": "https://example.com/story"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Verdict"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "ok": {
                                "type": "boolean"
                              },
                              "result": {
                                "$ref": "#/components/schemas/Verdict"
                              },
                              "error": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily limit reached. See Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/verify/original": {
      "post": {
        "summary": "Find where a picture came from",
        "description": "Reverse image search for the earliest page carrying the same picture. Look-alike sites and the current page are never offered. Results are cached for a week.",
        "security": [
          {
            "apiKey": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Image"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "original": {
                      "$ref": "#/components/schemas/Destination"
                    },
                    "alternatives": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string"
                          },
                          "host": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "nullable": true
                          },
                          "exact": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "bestGuess": {
                      "type": "string",
                      "nullable": true
                    },
                    "searched": {
                      "type": "boolean"
                    },
                    "note": {
                      "type": "string"
                    },
                    "verdict": {
                      "$ref": "#/components/schemas/Verdict"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily limit reached. See Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/verify/page": {
      "post": {
        "summary": "Check a link",
        "description": "Detects look-alike sites impersonating news outlets (and links to the real article), checks the headline, and checks the lead image.",
        "security": [
          {
            "apiKey": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "deep": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://bbc-news.co/news/world-123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verdict"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily limit reached. See Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/verify/claim": {
      "post": {
        "summary": "Fact-check a claim",
        "description": "Searches published fact-checks, then (when `deep`) reads independent sources. Supported needs two independent sites.",
        "security": [
          {
            "apiKey": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "deep": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "text"
                ]
              },
              "example": {
                "text": "The moon landing was faked"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verdict"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily limit reached. See Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/verify/status": {
      "get": {
        "summary": "Which checks this server runs",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Service health",
        "responses": {
          "200": {
            "description": "Healthy"
          },
          "503": {
            "description": "Database unreachable"
          }
        }
      }
    },
    "/api/speech": {
      "get": {
        "summary": "List checked speeches",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "word",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "speeches": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Speech"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Check and publish a speech",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "speaker": {
                    "type": "string"
                  },
                  "org": {
                    "type": "string"
                  },
                  "eventDate": {
                    "type": "string",
                    "format": "date"
                  },
                  "sourceUrl": {
                    "type": "string"
                  },
                  "links": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "mediaUrl": {
                    "type": "string"
                  },
                  "data": {
                    "type": "string",
                    "format": "byte"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "captions": {
                    "type": "string",
                    "description": "WebVTT or SRT"
                  },
                  "transcript": {
                    "type": "string"
                  },
                  "published": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checked and published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Speech"
                }
              }
            }
          },
          "401": {
            "description": "API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Recording too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Not a recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/speech/item": {
      "get": {
        "summary": "One checked speech",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Speech"
                }
              }
            }
          },
          "404": {
            "description": "Unknown id"
          }
        }
      }
    },
    "/api/speech/match": {
      "post": {
        "summary": "Is this video a checked speech?",
        "security": [
          {
            "apiKey": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pageUrl": {
                    "type": "string"
                  },
                  "videoSrc": {
                    "type": "string"
                  },
                  "data": {
                    "type": "string",
                    "format": "byte"
                  },
                  "mime": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matched": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "link",
                        "file",
                        "frames",
                        null
                      ]
                    },
                    "speech": {
                      "$ref": "#/components/schemas/Speech"
                    },
                    "footage": {
                      "$ref": "#/components/schemas/Verdict"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/speech/upload-url": {
      "post": {
        "summary": "Signed upload address for a large recording",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "uploadUrl": {
                      "type": "string"
                    },
                    "token": {
                      "type": "string"
                    },
                    "mediaUrl": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Direct uploads not configured"
          }
        }
      }
    }
  }
}