{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://marketingengineer.jobs/schemas/jobs.schema.json",
  "title": "Marketing Engineer Job Board Dataset",
  "description": "Canonical schema for the Marketing Engineer Job Board mock dataset. The root document is an array of curated job listings. Use this schema to validate and shape any data sourced or generated by an agent before persisting it to jobs.json.",
  "type": "array",
  "uniqueItems": true,
  "items": { "$ref": "#/$defs/JobListing" },
  "$defs": {
    "JobListing": {
      "type": "object",
      "title": "JobListing",
      "description": "A single curated job posting rendered on the board.",
      "additionalProperties": false,
      "required": [
        "id",
        "title",
        "role",
        "company",
        "location",
        "description",
        "tags",
        "employmentType",
        "workplaceType",
        "isFeatured",
        "applyUrl",
        "createdAt"
      ],
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "Stable unique identifier for the listing. Use a UUID v4 (e.g. generated with crypto.randomUUID()). Used in URLs at /role/{id}."
        },
        "title": {
          "type": "string",
          "minLength": 3,
          "maxLength": 200,
          "description": "Human-facing job title exactly as it should appear on the card and detail page (e.g. \"Marketing Engineering Lead, Research & Intelligence\")."
        },
        "role": {
          "$ref": "#/$defs/JobRoleId"
        },
        "company": {
          "$ref": "#/$defs/JobCompany"
        },
        "location": {
          "$ref": "#/$defs/JobLocation"
        },
        "description": {
          "type": "string",
          "minLength": 20,
          "maxLength": 600,
          "description": "Short 1\u20132 sentence summary shown on the detail page hero. Avoid duplicating the title. Keep it human-readable and concrete."
        },
        "responsibilities": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 600
          },
          "minItems": 1,
          "description": "Optional list of bullet-point responsibilities lifted from the job description. Each item is rendered as a list bullet on the detail page."
        },
        "requirements": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 600
          },
          "minItems": 1,
          "description": "Optional list of requirements / qualifications. Rendered as bullets on the detail page."
        },
        "tags": {
          "type": "array",
          "description": "Short keywords used for visual chips and search (e.g. \"AEO\", \"GEO\", \"LLMs\", \"Brand\"). Should be 0\u20136 entries, uppercase or TitleCase.",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32
          },
          "uniqueItems": true,
          "maxItems": 12
        },
        "employmentType": {
          "$ref": "#/$defs/EmploymentTypeId"
        },
        "workplaceType": {
          "$ref": "#/$defs/WorkplaceTypeId"
        },
        "salary": {
          "$ref": "#/$defs/JobSalary"
        },
        "isFeatured": {
          "type": "boolean",
          "description": "When true, the listing is eligible for the featured rail (hero/sidebar)."
        },
        "isTrending": {
          "type": "boolean",
          "description": "When true, a small \"Trending\" flame badge is shown next to the title."
        },
        "applyUrl": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://",
          "description": "Direct link to the company's application page. UTM parameters are appended at render time."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 datetime in UTC for when the role was posted. Used to compute relative \"posted N days ago\" labels and for sort=newest."
        }
      }
    },

    "JobCompany": {
      "type": "object",
      "title": "JobCompany",
      "description": "Company that owns the listing.",
      "additionalProperties": false,
      "required": ["name", "iconUrl", "sizeRange", "industry"],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "Company display name (e.g. \"Spotlight\", \"Ramp\")."
        },
        "iconUrl": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://",
          "description": "Absolute URL to a square company icon/logo. Any host is accepted; the client renders it via <img> without optimization. Prefer transparent SVG or square PNG \u2265 64px. Falls back to the first letter of the company name if the image fails to load."
        },
        "sizeRange": {
          "$ref": "#/$defs/CompanySizeRangeId"
        },
        "industry": {
          "$ref": "#/$defs/CompanyIndustryId"
        },
        "tagline": {
          "type": "string",
          "minLength": 1,
          "maxLength": 300,
          "description": "Optional short company pitch displayed on the detail page."
        },
        "websiteUrl": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://",
          "description": "Optional company homepage URL."
        }
      }
    },

    "JobSalary": {
      "type": "object",
      "title": "JobSalary",
      "description": "Optional compensation range. Omit the field entirely when comp is not disclosed.",
      "additionalProperties": false,
      "required": ["min", "max", "currency", "period"],
      "properties": {
        "min": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000000,
          "description": "Minimum compensation amount in the smallest indivisible unit of `currency` per `period` (e.g. 100000 = 100,000 USD/year). Must be \u2264 `max`."
        },
        "max": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000000,
          "description": "Maximum compensation amount, in the same currency and period as `min`. Must be \u2265 `min`."
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$",
          "description": "ISO 4217 three-letter uppercase currency code (e.g. \"USD\", \"EUR\", \"GBP\", \"CAD\")."
        },
        "period": {
          "$ref": "#/$defs/SalaryPeriodId"
        }
      }
    },

    "JobLocation": {
      "type": "object",
      "title": "JobLocation",
      "description": "Structured location for a role using ISO codes. At least one property must be set. For a fully remote role with no geographic anchor, pass `{ \"remote\": true }`. For a remote role limited to a specific country, combine `country` and `remote: true`. For on-site / hybrid roles, set `city` plus `region` plus `country`.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "city": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120,
          "description": "Human-readable city name as it should be displayed (e.g. \"New York\", \"Kansas City\", \"London\"). Do not include the region or country here."
        },
        "region": {
          "type": "string",
          "pattern": "^[A-Z]{2}-[A-Z0-9]{1,3}$",
          "description": "ISO 3166-2 subdivision code in the form `<country>-<subdivision>` (e.g. \"US-NY\", \"US-CA\", \"GB-LND\"). When provided, the prefix must match `country` if `country` is also set."
        },
        "country": {
          "type": "string",
          "pattern": "^[A-Z]{2}$",
          "description": "ISO 3166-1 alpha-2 country code in uppercase (e.g. \"US\", \"GB\", \"DE\"). Country display names are resolved at render time via `Intl.DisplayNames`, so do not store the full name here."
        },
        "remote": {
          "type": "boolean",
          "description": "True when the role can be performed fully remotely. Combine with `country` to scope the remote role to a specific country (e.g. \"Remote, US\"). Leave the geographic fields unset for globally remote roles."
        }
      }
    },

    "JobRoleId": {
      "title": "JobRoleId",
      "description": "Internal taxonomy of curated roles. MARKETING_ENGINEER covers full-stack Marketing Engineering roles (full-stack marketers with the skills of a builder, who build agents, automation, and AI search systems for marketing teams); AEO covers Answer Engine Optimization / generative search roles.",
      "type": "string",
      "oneOf": [
        { "const": "MARKETING_ENGINEER", "title": "Marketing Engineer", "description": "Full-stack marketer with the skills of a builder. Builds at the system level to automate at the campaign level: shipping agents and automated systems that absorb repetitive marketing work and unlock campaigns the team could not have executed manually." },
        { "const": "AEO", "title": "AEO", "description": "Answer Engine Optimization / generative search role." }
      ]
    },

    "EmploymentTypeId": {
      "title": "EmploymentTypeId",
      "type": "string",
      "oneOf": [
        { "const": "FULL_TIME", "title": "Full-time" },
        { "const": "PART_TIME", "title": "Part-time" },
        { "const": "CONTRACT", "title": "Contract" },
        { "const": "TEMPORARY", "title": "Temporary" },
        { "const": "INTERNSHIP", "title": "Internship" }
      ]
    },

    "WorkplaceTypeId": {
      "title": "WorkplaceTypeId",
      "type": "string",
      "oneOf": [
        { "const": "ON_SITE", "title": "On-site", "description": "Employee is expected on-site at a company office." },
        { "const": "HYBRID", "title": "Hybrid", "description": "Mix of on-site and remote work." },
        { "const": "REMOTE", "title": "Remote", "description": "Fully remote role." }
      ]
    },

    "CompanyIndustryId": {
      "title": "CompanyIndustryId",
      "description": "Coarse industry bucket used for filtering and badges.",
      "type": "string",
      "oneOf": [
        { "const": "SAAS", "title": "SaaS" },
        { "const": "ECOMMERCE", "title": "E-commerce" },
        { "const": "FINTECH", "title": "Fintech" },
        { "const": "HEALTHCARE", "title": "Healthcare" },
        { "const": "MEDIA", "title": "Media" },
        { "const": "TRAVEL", "title": "Travel & Hospitality" },
        { "const": "CPG", "title": "Consumer Goods" },
        { "const": "B2B_SERVICES", "title": "B2B Services" },
        { "const": "MARKETING_ADVERTISING", "title": "Marketing & Advertising" },
        { "const": "EDUCATION", "title": "Education" },
        { "const": "AI_ML", "title": "AI & ML" },
        { "const": "GAMING", "title": "Gaming" },
        { "const": "REAL_ESTATE", "title": "Real Estate" },
        { "const": "TELECOM", "title": "Telecom" },
        { "const": "PRODUCTIVITY", "title": "Productivity" },
        { "const": "OTHER", "title": "Other" }
      ]
    },

    "CompanySizeRangeId": {
      "title": "CompanySizeRangeId",
      "description": "Employee headcount bucket. The numbers before the underscore are the inclusive lower bound; the second number is the inclusive upper bound (or PLUS for open-ended).",
      "type": "string",
      "oneOf": [
        { "const": "1_10", "title": "1\u201310 employees" },
        { "const": "11_50", "title": "11\u201350 employees" },
        { "const": "51_200", "title": "51\u2013200 employees" },
        { "const": "201_500", "title": "201\u2013500 employees" },
        { "const": "501_1000", "title": "501\u20131,000 employees" },
        { "const": "1001_5000", "title": "1,001\u20135,000 employees" },
        { "const": "5001_10000", "title": "5,001\u201310,000 employees" },
        { "const": "10000_PLUS", "title": "10,000+ employees" }
      ]
    },

    "SalaryPeriodId": {
      "title": "SalaryPeriodId",
      "type": "string",
      "oneOf": [
        { "const": "YEAR", "title": "per year" },
        { "const": "MONTH", "title": "per month" },
        { "const": "HOUR", "title": "per hour" }
      ]
    }
  }
}
