{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/craftingeventfinder-com/json-schema/craftingeventfinder-com-source-schema.json",
  "title": "Source",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/craftingeventfinder-com-openapi.yml#/components/schemas/Source",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "url": {
      "type": "string"
    },
    "craftTypes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "state": {
      "type": [
        "string",
        "null"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "candidate",
        "active",
        "paused",
        "rejected"
      ]
    },
    "trust": {
      "type": "string",
      "enum": [
        "untrusted",
        "suggested",
        "trusted",
        "blocked"
      ]
    },
    "approvedCleanCount": {
      "type": "integer"
    },
    "lastFetchedAt": {
      "type": [
        "string",
        "null"
      ]
    },
    "lastContentHash": {
      "type": [
        "string",
        "null"
      ]
    },
    "lastError": {
      "type": [
        "string",
        "null"
      ]
    },
    "consecutiveFailures": {
      "type": "integer"
    },
    "eventsFoundCount": {
      "type": "integer"
    },
    "notes": {
      "type": [
        "string",
        "null"
      ]
    },
    "unchangedStreak": {
      "type": "integer"
    },
    "consecutiveEmptyRuns": {
      "type": "integer"
    },
    "nextFetchAt": {
      "type": [
        "string",
        "null"
      ]
    },
    "trustedAt": {
      "type": [
        "string",
        "null"
      ],
      "description": "When the source last became trusted; null unless trusted"
    },
    "distrustedReason": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why the source last lost trust automatically, e.g. \"1 event rejected: wrong date\""
    },
    "rejectReason": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "not_event_source",
        "not_crafting",
        "wrong_craft",
        "out_of_area",
        "duplicate",
        "date_passed",
        "site_dead",
        "wrong_dates",
        "other",
        null
      ],
      "description": "Why an admin rejected the source; null unless rejected with a reason"
    },
    "rejectNote": {
      "type": [
        "string",
        "null"
      ],
      "description": "The admin's own words on the rejection, if any"
    },
    "probation": {
      "type": "string",
      "enum": [
        "restart"
      ],
      "description": "Keep on probation (WI-336): start a fresh probation, so its events publish with a spot check on each and rejects from before today no longer count. Not with trust"
    },
    "home": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "venue": {
          "type": [
            "string",
            "null"
          ]
        },
        "street": {
          "type": [
            "string",
            "null"
          ]
        },
        "city": {
          "type": "string"
        },
        "state": {
          "type": [
            "string",
            "null"
          ]
        },
        "postal": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "venue",
        "street",
        "city",
        "state",
        "postal"
      ],
      "description": "The source's own place (WI-292): a class, club night, guild meeting or crop from it that names no town takes this town, and its venue, street and ZIP where the listing gives none"
    },
    "outreachEmail": {
      "type": [
        "string",
        "null"
      ],
      "description": "WI-512: the source's public contact email, used only to draft organizer invites and never shown on the site (migration 052); null when unknown"
    },
    "preview": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "found": {
          "type": [
            "integer",
            "null"
          ]
        },
        "upcoming": {
          "type": [
            "integer",
            "null"
          ]
        },
        "at": {
          "type": "string"
        }
      },
      "required": [
        "found",
        "upcoming",
        "at"
      ],
      "description": "WI-545: how many distinct event dates the source's page showed and how many are today or later (an estimate), and when it was counted; found and upcoming are null when the page couldn't be read that day; null until tried (migration 053)"
    },
    "createdAt": {
      "type": "string"
    },
    "updatedAt": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "name",
    "url",
    "craftTypes",
    "state",
    "status",
    "trust",
    "approvedCleanCount",
    "lastFetchedAt",
    "lastContentHash",
    "lastError",
    "consecutiveFailures",
    "eventsFoundCount",
    "notes",
    "unchangedStreak",
    "consecutiveEmptyRuns",
    "nextFetchAt",
    "trustedAt",
    "distrustedReason",
    "rejectReason",
    "rejectNote",
    "home",
    "outreachEmail",
    "preview",
    "createdAt",
    "updatedAt"
  ]
}