{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.retailmedia.verve.com/schemas/verve-metadata.schema.json",
  "title": "verve-metadata",
  "description": "verve-metadata is the JSON every Ad in a GET /v1alpha2/ads response carries in its Extension of that type, inside CDATA. Keys are the snake_case field names below. A key with no value is left out. Ignore keys you do not recognise: new ones can appear, and so can enum values.",
  "type": "object",
  "required": [
    "request_id"
  ],
  "properties": {
    "audience": {
      "description": "audience is the flight's dynamic targeting, for offline matching against the player's detected shoppers, cart and weather. Absent for untargeted ads; an ad with no audience matches everyone.",
      "$ref": "#/$defs/Audience"
    },
    "campaign_id": {
      "description": "campaign_id is the campaign of that flight, for attribution and debugging. Absent when the ad has none.",
      "type": "string"
    },
    "expires_at": {
      "description": "expires_at is when the ad stops being valid, as an RFC 3339 timestamp in UTC, to the whole second. Drop the ad at slot time once past it. An online response carries a much shorter horizon than an offline one.",
      "$ref": "#/$defs/Timestamp"
    },
    "flight_id": {
      "description": "flight_id is the Verve flight behind the ad, for attribution. Absent when the ad comes from no Verve flight.",
      "type": "string"
    },
    "frequency_caps": {
      "description": "frequency_caps are the flight's per-player play limits, for the player to enforce offline. Every entry applies at once. Count plays per flight_id, never per ad. Absent for uncapped flights and on online responses.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/FrequencyCap"
      }
    },
    "request_id": {
      "description": "request_id groups the ads of one /ads response, for correlation and debugging. Always present.",
      "type": "string"
    },
    "source": {
      "description": "source is the kind of source that served the ad: Verve's own ad server, an SSP or a CMS.",
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "SOURCE_TYPE_AD_SERVER",
            "SOURCE_TYPE_SSP",
            "SOURCE_TYPE_CMS"
          ]
        },
        {
          "type": "string"
        }
      ]
    },
    "source_id": {
      "description": "source_id is the specific source the ad came from, such as one CMS, SSP or ad server. Offline, group stored ads by it to keep the player's source mix.",
      "type": "string"
    }
  },
  "$defs": {
    "Timestamp": {
      "type": "string",
      "format": "date-time"
    },
    "Audience": {
      "description": "Audience is the targeting of an ad. An ad matches when any one group matches. A group matches when all its conditions match.",
      "type": "object",
      "properties": {
        "targeting_groups": {
          "description": "targeting_groups are OR'd: the ad matches when any one group matches.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/TargetingGroup"
          }
        }
      }
    },
    "DeviceCondition": {
      "description": "DeviceCondition matches on the shopper's cart.",
      "type": "object",
      "properties": {
        "basket": {
          "description": "basket matches when the cart holds any listed sku or item.",
          "$ref": "#/$defs/BasketFilter"
        }
      }
    },
    "FrequencyCap": {
      "description": "FrequencyCap limits how often one player may show the flight in one window.",
      "type": "object",
      "properties": {
        "limit": {
          "description": "limit is the most plays allowed within the window.",
          "type": "integer",
          "minimum": 0,
          "maximum": 4294967295
        },
        "window": {
          "description": "window is the period the limit applies to, in UTC. FREQUENCY_CAP_WINDOW_MINUTE and FREQUENCY_CAP_WINDOW_THIRTY_MINUTES look back 60 seconds and 30 minutes from now. FREQUENCY_CAP_WINDOW_HOUR is the current clock hour, FREQUENCY_CAP_WINDOW_DAY the current calendar day, and FREQUENCY_CAP_WINDOW_WEEK the current ISO week, which starts on Monday. FREQUENCY_CAP_WINDOW_FLIGHT is the flight's whole lifespan.",
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "FREQUENCY_CAP_WINDOW_MINUTE",
                "FREQUENCY_CAP_WINDOW_THIRTY_MINUTES",
                "FREQUENCY_CAP_WINDOW_HOUR",
                "FREQUENCY_CAP_WINDOW_DAY",
                "FREQUENCY_CAP_WINDOW_WEEK",
                "FREQUENCY_CAP_WINDOW_FLIGHT"
              ]
            },
            {
              "type": "string"
            }
          ]
        }
      }
    },
    "StoreCondition": {
      "description": "StoreCondition matches on the store's current weather.",
      "type": "object",
      "properties": {
        "weather_types": {
          "description": "weather_types match when the current weather is any one of them.",
          "type": "array",
          "items": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "WEATHER_TYPE_SUNNY",
                  "WEATHER_TYPE_CLOUDY",
                  "WEATHER_TYPE_RAINY",
                  "WEATHER_TYPE_SNOW",
                  "WEATHER_TYPE_ABOVE_20_CELSIUS",
                  "WEATHER_TYPE_BELOW_10_CELSIUS"
                ]
              },
              {
                "type": "string"
              }
            ]
          }
        }
      }
    },
    "TargetingCondition": {
      "description": "TargetingCondition is one criterion. Exactly one member is set.",
      "type": "object",
      "properties": {
        "demographic": {
          "description": "demographic matches when a detected shopper fits any listed profile.",
          "$ref": "#/$defs/DemographicCondition"
        },
        "device": {
          "description": "device matches on the shopper's cart.",
          "$ref": "#/$defs/DeviceCondition"
        },
        "store": {
          "description": "store matches on the store's current weather.",
          "$ref": "#/$defs/StoreCondition"
        }
      }
    },
    "TargetingGroup": {
      "description": "TargetingGroup matches when all of its conditions match.",
      "type": "object",
      "properties": {
        "conditions": {
          "description": "conditions are AND'd.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/TargetingCondition"
          }
        }
      }
    },
    "BasketFilter": {
      "description": "BasketFilter matches items currently in the shopper's active cart. skus and items are OR'd: a match on either satisfies the filter.",
      "type": "object",
      "properties": {
        "items": {
          "description": "items is an open list of product names or categories in the shopper's cart.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "skus": {
          "description": "skus are the product SKUs to match.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "CountComparison": {
      "description": "CountComparison expresses a comparison against a numeric count value. An absent operator matches any count.",
      "type": "object",
      "properties": {
        "operator": {
          "description": "operator is the comparison to apply.",
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "COUNT_COMPARISON_OPERATOR_EQUAL",
                "COUNT_COMPARISON_OPERATOR_LESS_THAN",
                "COUNT_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL",
                "COUNT_COMPARISON_OPERATOR_GREATER_THAN",
                "COUNT_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL"
              ]
            },
            {
              "type": "string"
            }
          ]
        },
        "value": {
          "description": "value is the number to compare against.",
          "type": "integer",
          "minimum": 0,
          "maximum": 4294967295
        }
      }
    },
    "DemographicCondition": {
      "description": "DemographicCondition targets shoppers by demographics detected via vision sensors. Only applies to devices equipped with vision sensors. Profiles are OR'd: the condition matches if any single profile matches the detected shopper.",
      "type": "object",
      "properties": {
        "profiles": {
          "description": "profiles are the slices to match; any one is enough.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/DemographicProfile"
          }
        }
      }
    },
    "DemographicProfile": {
      "description": "DemographicProfile is a single demographic slice. Unset fields match any value.",
      "type": "object",
      "properties": {
        "age_range": {
          "description": "age_range is the age bracket to match.",
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "AGE_RANGE_18_20",
                "AGE_RANGE_21_24",
                "AGE_RANGE_25_29",
                "AGE_RANGE_30_34",
                "AGE_RANGE_35_39",
                "AGE_RANGE_40_44",
                "AGE_RANGE_45_49",
                "AGE_RANGE_50_54",
                "AGE_RANGE_55_59",
                "AGE_RANGE_60_64",
                "AGE_RANGE_65_69",
                "AGE_RANGE_70_74",
                "AGE_RANGE_75_PLUS"
              ]
            },
            {
              "type": "string"
            }
          ]
        },
        "gender": {
          "description": "gender is the gender to match.",
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "GENDER_FEMALE",
                "GENDER_MALE",
                "GENDER_OTHER",
                "GENDER_UNKNOWN"
              ]
            },
            {
              "type": "string"
            }
          ]
        },
        "group_size": {
          "description": "group_size compares the number of people the vision sensor detects.",
          "$ref": "#/$defs/CountComparison"
        }
      }
    }
  }
}
