diff --git a/.vscode/launch.json b/.vscode/launch.json
index fb87b664..859ce6df 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -18,7 +18,8 @@
         "--nolazy"
       ],
       "env": {
-        "NODE_ENV": "development"
+        "NODE_ENV": "development",
+        "NODE_TLS_REJECT_UNAUTHORIZED": "0"
       },
       "console": "internalConsole",
       "sourceMaps": false,
diff --git a/README.md b/README.md
index ed639468..bdd9a11f 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,10 @@ Features
 - Tested on **[over 1,500 real-world APIs](https://apis.guru/browse-apis/)** from Google, Microsoft, Facebook, Spotify, etc.
 - Supports [circular references](https://apitools.dev/swagger-parser/docs/#circular-refs), nested references, back-references, and cross-references
 - Maintains object reference equality — `$ref` pointers to the same value always resolve to the same object instance
-
+- Checks for inconsistencies in Swagger v2.0 and OpenAPI v3.0 specs:
+  - path parameter mis-matches
+  - required field mis-matches
+  - arrays without item definition
 
 
 Related Projects
diff --git a/lib/index.d.ts b/lib/index.d.ts
index fc9c618b..6dd459c0 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -206,9 +206,10 @@ declare class SwaggerParser {
 
 // eslint-disable-next-line no-redeclare
 declare namespace SwaggerParser {
-
+  /* eslint-disable @typescript-eslint/no-explicit-any */
   export type ApiCallback = (err: Error | null, api?: OpenAPI.Document) => any;
   export type $RefsCallback = (err: Error | null, $refs?: $Refs) => any;
+  /* eslint-enable */
 
   /**
    * See https://apitools.dev/swagger-parser/docs/options.html
@@ -323,6 +324,7 @@ declare namespace SwaggerParser {
      */
     read(
       file: FileInfo,
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
       callback?: (error: Error | null, data: string | null) => any
     ): string | Buffer | Promise<string | Buffer>;
   }
@@ -407,6 +409,7 @@ declare namespace SwaggerParser {
      *
      * @param types (optional) Optionally only return values from certain locations ("file", "http", etc.)
      */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     public values(...types: string[]): { [url: string]: any }
 
     /**
@@ -425,6 +428,7 @@ declare namespace SwaggerParser {
      *
      * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash
      */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     public get($ref: string): any
 
     /**
@@ -433,6 +437,7 @@ declare namespace SwaggerParser {
      * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash
      * @param value The value to assign. Can be anything (object, string, number, etc.)
      */
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     public set($ref: string, value: any): void
   }
 
diff --git a/lib/index.js b/lib/index.js
index 22514465..5367df76 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -5,7 +5,7 @@ const validateSchema = require("./validators/schema");
 const validateSpec = require("./validators/spec");
 const normalizeArgs = require("@apidevtools/json-schema-ref-parser/lib/normalize-args");
 const util = require("./util");
-const Options = require("./options");
+const ParserOptions = require("./options");
 const maybe = require("call-me-maybe");
 const { ono } = require("@jsdevtools/ono");
 const $RefParser = require("@apidevtools/json-schema-ref-parser");
@@ -54,7 +54,7 @@ Object.defineProperty(SwaggerParser.prototype, "api", {
  */
 SwaggerParser.prototype.parse = async function (path, api, options, callback) {
   let args = normalizeArgs(arguments);
-  args.options = new Options(args.options);
+  args.options = new ParserOptions(args.options);
 
   try {
     let schema = await $RefParser.prototype.parse.call(this, args.path, args.schema, args.options);
@@ -150,7 +150,7 @@ SwaggerParser.validate = function (path, api, options, callback) {
 SwaggerParser.prototype.validate = async function (path, api, options, callback) {
   let me = this;
   let args = normalizeArgs(arguments);
-  args.options = new Options(args.options);
+  args.options = new ParserOptions(args.options);
 
   // ZSchema doesn't support circular objects, so don't dereference circular $refs yet
   // (see https://github.com/zaggino/z-schema/issues/137)
@@ -182,8 +182,8 @@ SwaggerParser.prototype.validate = async function (path, api, options, callback)
     }
 
     if (args.options.validate.spec) {
-      // Validate the API against the Swagger spec
-      validateSpec(me.api);
+      // Validate the API against the Swagger spec; hand in $refs.circular meta-data from the SwaggerParser
+      validateSpec(me.api, me.$refs.circular);
     }
 
     return maybe(args.callback, Promise.resolve(me.schema));
@@ -193,9 +193,5 @@ SwaggerParser.prototype.validate = async function (path, api, options, callback)
   }
 };
 
-/**
- * The Swagger object
- * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
- *
- * @typedef {{swagger: string, info: {}, paths: {}}} SwaggerObject
- */
+// Only the one export; don't try to export the @typedef of SwaggerOrOpenAPIObject
+module.exports = SwaggerParser;
diff --git a/lib/validators/schema.js b/lib/validators/schema.js
index d22ea681..f88be694 100644
--- a/lib/validators/schema.js
+++ b/lib/validators/schema.js
@@ -6,12 +6,23 @@ const AjvDraft4 = require("ajv-draft-04");
 const Ajv = require("ajv/dist/2020");
 const { openapi } = require("@apidevtools/openapi-schemas");
 
+/**
+ * The Swagger v2.0 or OpenAPI v3.0.x object - it could be either (but not both)
+ *
+ * cf.
+ *    - https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
+ *    - https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#oasObject
+ *
+ * @typedef {{swagger: string, info: {}, paths: {},
+ *           openapi:string, }} SwaggerOrOpenAPIObject
+ */
+
 module.exports = validateSchema;
 
 /**
  * Validates the given Swagger API against the Swagger 2.0 or OpenAPI 3.0 and 3.1 schemas.
  *
- * @param {SwaggerObject} api
+ * @param {SwaggerOrOpenAPIObject} api  Either a Swagger or OpenAPI object - determined by presence of swagger, or openapi fields
  */
 function validateSchema (api) {
   let ajv;
@@ -59,8 +70,8 @@ function validateSchema (api) {
 /**
  * Determines which version of Ajv to load and prepares it for use.
  *
- * @param {bool} draft04
- * @returns {Ajv}
+ * @param {boolean} draft04  Are we initialising for JsonSchemaDraft04?
+ * @returns {Ajv}  The initialized Ajv environment
  */
 function initializeAjv (draft04 = true) {
   const opts = {
@@ -81,7 +92,7 @@ function initializeAjv (draft04 = true) {
  *
  * @param {object[]}  errors     - The Ajv errors
  * @param {string}    [indent]   - The whitespace used to indent the error message
- * @returns {string}
+ * @returns {string}             - Formatted error message string
  */
 function formatAjvError (errors, indent) {
   indent = indent || "  ";
diff --git a/lib/validators/spec.js b/lib/validators/spec.js
index 21e965d3..3e330349 100644
--- a/lib/validators/spec.js
+++ b/lib/validators/spec.js
@@ -9,44 +9,153 @@ const schemaTypes = ["array", "boolean", "integer", "number", "string", "object"
 module.exports = validateSpec;
 
 /**
- * Validates parts of the Swagger 2.0 spec that aren't covered by the Swagger 2.0 JSON Schema.
+ * Validates parts of the Swagger 2.0 spec that aren't covered by the Swagger 2.0 JSON Schema;
+ *   and parts of the OpenAPI v3.0.2 spec that aren't covered by the OpenAPI 3.0 JSON SChema
  *
- * @param {SwaggerObject} api
+ * @param {object} api         - the entire Swagger API object  (plus some isCircular meta data from the parser)
+ * @param {object} isCircular  - meta data from the SwaggerParser; does the API have circular $refs ??
  */
-function validateSpec (api) {
-  if (api.openapi) {
-    // We don't (yet) support validating against the OpenAPI spec
-    return;
-  }
+function validateSpec (api, isCircular) {
 
   let paths = Object.keys(api.paths || {});
   let operationIds = [];
+
+  // accumulate errors
+  let message = "Specification check failed.\n";
+  let isValid = true;
+
+  // Check all the paths
   for (let pathName of paths) {
     let path = api.paths[pathName];
     let pathId = "/paths" + pathName;
 
-    if (path && pathName.indexOf("/") === 0) {
-      validatePath(api, path, pathId, operationIds);
+    try {
+
+      // ...and off we go ...
+      if (path && pathName.indexOf("/") === 0) {
+        validatePath(api, isCircular, path, pathId, operationIds);
+      }
+    }
+    catch (err) {
+      message += err.message;
+      isValid = false;
+    }
+
+    // in OpenAPI v3.0 #/components holds lots of different definitions...
+    if (api.openapi) {
+      // #/components/schemas   (don't have contentType)
+      let schemaNames = Object.keys(api.components?.schemas || {});
+      for (let schemaName of schemaNames) {
+
+        let schema = api.components.schemas[schemaName];
+        let schemaId = "/components/schemas/" + schemaName;
+
+        try {
+          validateSchema(schema, schemaId, schemaTypes, isCircular);
+        }
+        catch (err) {
+          message += err.message;
+          isValid = false;
+        }
+      }
+
+      // #/components/parameters    (don't have contentTypes)
+      //     NOTE: the #/components/parameters that have been used, have already been cheked.
+      let paramNames = Object.keys(api.components?.parameters || {});
+      for (let paramName of paramNames) {
+
+        let schema = api.components.parameters[paramName].schema;
+        let schemaId = "/components/parameters/" + paramName + "/schema";
+
+        try {
+          validateSchema(schema, schemaId, schemaTypes, isCircular);
+        }
+        catch (err) {
+          message += err.message;
+          isValid = false;
+        }
+      }
+
+      // #/components/requestBodies
+      let reqBodyNames = Object.keys(api.components?.requestBodies || {});
+      for (let reqBodyName of reqBodyNames) {
+
+        // Loop through the contentTypes
+        let contentTypes = Object.keys(api.components.requestBodies[reqBodyName].content || {});
+        for (let contentType of contentTypes) {
+
+          let schema = api.components.requestBodies[reqBodyName].content[contentType].schema;
+          let schemaId = "/components/requestBodies/" + reqBodyName + "/content/" + contentType + "/schema";
+
+          try {
+            validateSchema(schema, schemaId, schemaTypes, isCircular);
+          }
+          catch (err) {
+            message += err.message;
+            isValid = false;
+          }
+        }
+      }
+
+      // #/components/responses
+      let rspNames = Object.keys(api.components?.responses || {});
+      for (let rspName of rspNames) {
+
+        // Loop through the contentTypes
+        let contentTypes = Object.keys(api.components.responses[rspName].content || {});
+        for (let contentType of contentTypes) {
+
+          let schema = api.components.responses[rspName].content[contentType].schema;
+          let schemaId = "/components/responses/" + rspName + "/content/" + contentType + "/schema";
+
+          try {
+            validateSchema(schema, schemaId, schemaTypes, isCircular);
+          }
+          catch (err) {
+            message += err.message;
+            isValid = false;
+          }
+        }
+      }
+    }
+    else {
+      // ... in Swagger v2.0 they were all slapped in definitions
+      let definitions = Object.keys(api.definitions || {});
+      for (let definitionName of definitions) {
+        let definition = api.definitions[definitionName];
+        let definitionId = "/definitions/" + definitionName;
+
+        try {
+          validateSchema(definition, definitionId, schemaTypes, isCircular);
+        }
+        catch (err) {
+          message += err.message;
+          isValid = false;
+        }
+      }
     }
   }
 
-  let definitions = Object.keys(api.definitions || {});
-  for (let definitionName of definitions) {
-    let definition = api.definitions[definitionName];
-    let definitionId = "/definitions/" + definitionName;
-    validateRequiredPropertiesExist(definition, definitionId);
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
   }
 }
 
 /**
  * Validates the given path.
  *
- * @param {SwaggerObject} api           - The entire Swagger API object
- * @param {object}        path          - A Path object, from the Swagger API
+ * @param {object}        api           - The entire Swagger/OpenAPI API object
+ * @param {object}        isCircular    - meta data from the SwaggerParser; does the API have circular $refs ??
+ * @param {object}        path          - A Path object, from the Swagger/OpenAPI API
  * @param {string}        pathId        - A value that uniquely identifies the path
  * @param {string}        operationIds  - An array of collected operationIds found in other paths
  */
-function validatePath (api, path, pathId, operationIds) {
+function validatePath (api, isCircular, path, pathId, operationIds) {
+  // accumulate errors
+  let message = "";
+  let isValid = true;
+
   for (let operationName of swaggerMethods) {
     let operation = path[operationName];
     let operationId = pathId + "/" + operationName;
@@ -58,40 +167,67 @@ function validatePath (api, path, pathId, operationIds) {
           operationIds.push(declaredOperationId);
         }
         else {
-          throw ono.syntax(`Validation failed. Duplicate operation id '${declaredOperationId}'`);
+          message += `Validation failed. Duplicate operation id '${declaredOperationId}'\n`;
+          isValid = false;
         }
       }
-      validateParameters(api, path, pathId, operation, operationId);
+      try {
+        validateParameters(api, isCircular, path, pathId, operation, operationId);
+      }
+      catch (err) {
+        message += err.message;
+        isValid = false;
+      }
+
+      // Don't forget to validate the Request
 
+      // Zoop through all the responses
       let responses = Object.keys(operation.responses || {});
       for (let responseName of responses) {
         let response = operation.responses[responseName];
         let responseId = operationId + "/responses/" + responseName;
-        validateResponse(responseName, (response || {}), responseId);
+        try {
+          validateResponse(api, isCircular, responseName, (response || {}), responseId);
+        }
+        catch (err) {
+          message += err.message;
+          isValid = false;
+        }
       }
     }
   }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
+  }
 }
 
 /**
  * Validates the parameters for the given operation.
  *
- * @param {SwaggerObject} api           - The entire Swagger API object
+ * @param {object}        api           - The entire Swagger/OpenAPI API object
+ * @param {object}        isCircular    - meta data from the SwaggerParser; does the API have circular $refs ??
  * @param {object}        path          - A Path object, from the Swagger API
  * @param {string}        pathId        - A value that uniquely identifies the path
  * @param {object}        operation     - An Operation object, from the Swagger API
  * @param {string}        operationId   - A value that uniquely identifies the operation
  */
-function validateParameters (api, path, pathId, operation, operationId) {
+function validateParameters (api, isCircular, path, pathId, operation, operationId) {
   let pathParams = path.parameters || [];
   let operationParams = operation.parameters || [];
 
+  // accumulate errors
+  let message = "";
+  let isValid = true;
+
   // Check for duplicate path parameters
   try {
     checkForDuplicates(pathParams);
   }
   catch (e) {
-    throw ono.syntax(e, `Validation failed. ${pathId} has duplicate parameters`);
+    message += `Validation failed. ${pathId} has duplicate parameters\n` + e.message;
+    isValid = false;
   }
 
   // Check for duplicate operation parameters
@@ -99,7 +235,8 @@ function validateParameters (api, path, pathId, operation, operationId) {
     checkForDuplicates(operationParams);
   }
   catch (e) {
-    throw ono.syntax(e, `Validation failed. ${operationId} has duplicate parameters`);
+    message += `Validation failed. ${operationId} has duplicate parameters\n` + e.message;
+    isValid = false;
   }
 
   // Combine the path and operation parameters,
@@ -114,9 +251,32 @@ function validateParameters (api, path, pathId, operation, operationId) {
     return combinedParams;
   }, operationParams.slice());
 
-  validateBodyParameters(params, operationId);
-  validatePathParameters(params, pathId, operationId);
-  validateParameterTypes(params, api, operation, operationId);
+  try {
+    validateBodyParameters(params, operationId);
+  }
+  catch (err) {
+    message += err.message;
+    isValid = false;
+  }
+  try {
+    validatePathParameters(params, pathId, operationId);
+  }
+  catch (err) {
+    message += err.message;
+    isValid = false;
+  }
+  try {
+    validateParameterTypes(params, api, isCircular, operation, operationId);
+  }
+  catch (err) {
+    message += err.message;
+    isValid = false;
+  }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
+  }
 }
 
 /**
@@ -129,17 +289,26 @@ function validateBodyParameters (params, operationId) {
   let bodyParams = params.filter((param) => { return param.in === "body"; });
   let formParams = params.filter((param) => { return param.in === "formData"; });
 
+  // accumulate errors
+  let message = "";
+  let isValid = true;
+
   // There can only be one "body" parameter
   if (bodyParams.length > 1) {
-    throw ono.syntax(
-      `Validation failed. ${operationId} has ${bodyParams.length} body parameters. Only one is allowed.`,
-    );
+    message +=
+      `Validation failed. ${operationId} has ${bodyParams.length} body parameters. Only one is allowed.\n`;
+    isValid = false;
   }
   else if (bodyParams.length > 0 && formParams.length > 0) {
     // "body" params and "formData" params are mutually exclusive
-    throw ono.syntax(
-      `Validation failed. ${operationId} has body parameters and formData parameters. Only one or the other is allowed.`,
-    );
+    message +=
+      `Validation failed. ${operationId} has body parameters and formData parameters. Only one or the other is allowed.\n`;
+    isValid = false;
+  }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
   }
 }
 
@@ -154,12 +323,17 @@ function validatePathParameters (params, pathId, operationId) {
   // Find all {placeholders} in the path string
   let placeholders = pathId.match(util.swaggerParamRegExp) || [];
 
+  // accumulate errors
+  let message = "";
+  let isValid = true;
+
   // Check for duplicates
   for (let i = 0; i < placeholders.length; i++) {
     for (let j = i + 1; j < placeholders.length; j++) {
       if (placeholders[i] === placeholders[j]) {
-        throw ono.syntax(
-          `Validation failed. ${operationId} has multiple path placeholders named ${placeholders[i]}`);
+        message +=
+          `Validation failed. ${operationId} has multiple path placeholders named ${placeholders[i]}\n`;
+        isValid = false;
       }
     }
   }
@@ -168,23 +342,29 @@ function validatePathParameters (params, pathId, operationId) {
 
   for (let param of params) {
     if (param.required !== true) {
-      throw ono.syntax(
+      message +=
         "Validation failed. Path parameters cannot be optional. " +
-        `Set required=true for the "${param.name}" parameter at ${operationId}`,
-      );
+        `Set required=true for the "${param.name}" parameter at ${operationId}\n`;
+      isValid = false;
     }
     let match = placeholders.indexOf("{" + param.name + "}");
     if (match === -1) {
-      throw ono.syntax(
+      message +=
         `Validation failed. ${operationId} has a path parameter named "${param.name}", ` +
-        `but there is no corresponding {${param.name}} in the path string`
-      );
+        `but there is no corresponding {${param.name}} in the path string\n`;
+      isValid = false;
     }
     placeholders.splice(match, 1);
   }
 
   if (placeholders.length > 0) {
-    throw ono.syntax(`Validation failed. ${operationId} is missing path parameter(s) for ${placeholders}`);
+    message += `Validation failed. ${operationId} is missing path parameter(s) for ${placeholders}\n`;
+    isValid = false;
+  }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
   }
 }
 
@@ -193,32 +373,51 @@ function validatePathParameters (params, pathId, operationId) {
  *
  * @param   {object[]}  params       -  An array of Parameter objects
  * @param   {object}    api          -  The entire Swagger API object
+ * @param   {object}    isCircular   - meta data from the SwaggerParser; does the API have circular $refs ??
  * @param   {object}    operation    -  An Operation object, from the Swagger API
  * @param   {string}    operationId  -  A value that uniquely identifies the operation
  */
-function validateParameterTypes (params, api, operation, operationId) {
+function validateParameterTypes (params, api, isCircular, operation, operationId) {
+
+  // accumulate errors
+  let message = "";
+  let isValid = true;
+
   for (let param of params) {
     let parameterId = operationId + "/parameters/" + param.name;
     let schema, validTypes;
 
-    switch (param.in) {
-      case "body":
-        schema = param.schema;
-        validTypes = schemaTypes;
-        break;
-      case "formData":
-        schema = param;
-        validTypes = primitiveTypes.concat("file");
-        break;
-      default:
-        schema = param;
-        validTypes = primitiveTypes;
+    // schema is always inside 'schema' tag in openapi specs...
+    if (api.openapi) {
+      schema = param.schema;
+      validTypes = schemaTypes;
+    }
+    else {
+      //  Swagger 2.0 was different...
+      switch (param.in) {
+        case "body":
+          schema = param.schema;
+          validTypes = schemaTypes;
+          break;
+        case "formData":
+          schema = param;
+          validTypes = primitiveTypes.concat("file");
+          break;
+        default:
+          schema = param;
+          validTypes = primitiveTypes;
+      }
     }
 
-    validateSchema(schema, parameterId, validTypes);
-    validateRequiredPropertiesExist(schema, parameterId);
+    try {
+      validateSchema(schema, parameterId, validTypes, isCircular);
+    }
+    catch (err) {
+      message += err.message;
+      isValid = false;
+    }
 
-    if (schema.type === "file") {
+    if (schema?.type === "file") {
       // "file" params must consume at least one of these MIME types
       let formData = /multipart\/(.*\+)?form-data/;
       let urlEncoded = /application\/(.*\+)?x-www-form-urlencoded/;
@@ -230,13 +429,18 @@ function validateParameterTypes (params, api, operation, operationId) {
       });
 
       if (!hasValidMimeType) {
-        throw ono.syntax(
+        message +=
           `Validation failed. ${operationId} has a file parameter, so it must consume multipart/form-data ` +
-          "or application/x-www-form-urlencoded",
-        );
+          "or application/x-www-form-urlencoded\n";
+        isValid = false;
       }
     }
   }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
+  }
 }
 
 /**
@@ -245,46 +449,123 @@ function validateParameterTypes (params, api, operation, operationId) {
  * @param   {object[]}  params  - An array of Parameter objects
  */
 function checkForDuplicates (params) {
+  let message = "";
+  let isValid = true;
+
   for (let i = 0; i < params.length - 1; i++) {
     let outer = params[i];
     for (let j = i + 1; j < params.length; j++) {
       let inner = params[j];
       if (outer.name === inner.name && outer.in === inner.in) {
-        throw ono.syntax(`Validation failed. Found multiple ${outer.in} parameters named "${outer.name}"`);
+        message += `Validation failed. Found multiple ${outer.in} parameters named "${outer.name}"\n`;
+        isValid = false;
       }
     }
   }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
+  }
 }
 
 /**
  * Validates the given response object.
  *
+ * @param   {object}    api         - The entire Swagger/OpenAPI API object
+ * @param   {object}    isCircular  - meta data from the SwaggerParser; does the API have circular $refs ??
  * @param   {string}    code        -  The HTTP response code (or "default")
  * @param   {object}    response    -  A Response object, from the Swagger API
  * @param   {string}    responseId  -  A value that uniquely identifies the response
  */
-function validateResponse (code, response, responseId) {
+function validateResponse (api, isCircular, code, response, responseId) {
+  let message = "";
+  let isValid = true;
+
   if (code !== "default" && (code < 100 || code > 599)) {
-    throw ono.syntax(`Validation failed. ${responseId} has an invalid response code (${code})`);
+    message += `Validation failed. ${responseId} has an invalid response code (${code})\n`;
+    isValid = false;
   }
 
+  // Check response Headers
   let headers = Object.keys(response.headers || {});
   for (let headerName of headers) {
     let header = response.headers[headerName];
     let headerId = responseId + "/headers/" + headerName;
-    validateSchema(header, headerId, primitiveTypes);
-  }
+    let schema, validTypes;
 
-  if (response.schema) {
-    let validTypes = schemaTypes.concat("file");
-    if (validTypes.indexOf(response.schema.type) === -1) {
-      throw ono.syntax(
-        `Validation failed. ${responseId} has an invalid response schema type (${response.schema.type})`);
+    // schema is always inside 'schema' tag in openapi specs...
+    if (api.openapi) {
+      schema = header.schema;
+      validTypes = schemaTypes;
     }
     else {
-      validateSchema(response.schema, responseId + "/schema", validTypes);
+      //  Swagger 2.0 was different...
+      schema = header;
+      validTypes = primitiveTypes;
+    }
+    try {
+      validateSchema(schema, headerId, validTypes, isCircular);
+    }
+    catch (err) {
+      message += err.message;
+      isValid = false;
+    }
+  }
+
+  // OpenAPI has different responses for each content-type
+  if (api.openapi) {
+    if (response.content) {
+      // Loop through the different content-types
+      let contentTypes = Object.keys(response.content);
+      for (let contentType of contentTypes) {
+
+        let content = response.content[contentType];
+        if (content.schema) {
+          let validTypes = schemaTypes.concat("file");
+          if (validTypes.indexOf(content.schema.type) === -1) {
+            message +=
+            `Validation failed. ${responseId}/content/${contentType}/schema has an invalid response schema type (${content.schema.type})\n`;
+            isValid = false;
+          }
+          else {
+            try {
+              validateSchema(content.schema, responseId + "/content/" + contentType + "/schema", validTypes, isCircular);
+            }
+            catch (err) {
+              message += err.message;
+              isValid = false;
+            }
+          }
+        }
+      }
+    }
+  }
+  else {
+    // Swagger 2.0 was different
+    if (response.schema) {
+      let validTypes = schemaTypes.concat("file");
+      if (validTypes.indexOf(response.schema.type) === -1) {
+        message +=
+          `Validation failed. ${responseId} has an invalid response schema type (${response.schema.type})\n`;
+        isValid = false;
+      }
+      else {
+        try {
+          validateSchema(response.schema, responseId + "/schema", validTypes, isCircular);
+        }
+        catch (err) {
+          message += err.message;
+          isValid = false;
+        }
+      }
     }
   }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
+  }
 }
 
 /**
@@ -293,15 +574,53 @@ function validateResponse (code, response, responseId) {
  * @param {object}    schema      - A Schema object, from the Swagger API
  * @param {string}    schemaId    - A value that uniquely identifies the schema object
  * @param {string[]}  validTypes  - An array of the allowed schema types
+ * @param {boolean}   circular    - Does the parent API have circular isCircular??
  */
-function validateSchema (schema, schemaId, validTypes) {
+function validateSchema (schema, schemaId, validTypes, circular) {
+  let message = "";
+  let isValid = true;
+
+  // make sure the schema type is known
   if (validTypes.indexOf(schema.type) === -1) {
-    throw ono.syntax(
-      `Validation failed. ${schemaId} has an invalid type (${schema.type})`);
+    message +=
+      `Validation failed. ${schemaId} has an invalid type (${schema.type})\n`;
+    isValid = false;
   }
 
+  // make sure that arrays have items defined
   if (schema.type === "array" && !schema.items) {
-    throw ono.syntax(`Validation failed. ${schemaId} is an array, so it must include an "items" schema`);
+    message += `Validation failed. ${schemaId} is an array, so it must include an "items" schema\n`;
+    isValid = false;
+  }
+
+  // make sure that all properties marked as 'required' actually exist
+  try {
+    validateRequiredPropertiesExist(schema, schemaId);
+  }
+  catch (err) {
+    message += err.message;
+    isValid = false;
+  }
+
+  // Recursively check all the properties (BUT ONLY if the API has no circular $refs)
+  //   TODO:  Make this check work on circular $refs
+  if (!circular) {
+    let propNames = Object.keys(schema.properties || {});
+    for (let propName of propNames) {
+
+      try {
+        validateSchema(schema.properties[propName], schemaId + "/" + propName, schemaTypes, circular);
+      }
+      catch (err) {
+        message += err.message;
+        isValid = false;
+      }
+    }
+  }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
   }
 }
 
@@ -312,6 +631,7 @@ function validateSchema (schema, schemaId, validTypes) {
  * @param {string}    schemaId    - A value that uniquely identifies the schema object
  */
 function validateRequiredPropertiesExist (schema, schemaId) {
+
   /**
    * Recursively collects all properties of the schema and its ancestors. They are added to the props object.
    */
@@ -330,15 +650,23 @@ function validateRequiredPropertiesExist (schema, schemaId) {
     }
   }
 
+  let message = "";
+  let isValid = true;
+
   if (schema.required && Array.isArray(schema.required)) {
     let props = {};
     collectProperties(schema, props);
     for (let requiredProperty of schema.required) {
       if (!props[requiredProperty]) {
-        throw ono.syntax(
-          `Validation failed. Property '${requiredProperty}' listed as required but does not exist in '${schemaId}'`
-        );
+        message +=
+          `Validation failed. Property '${requiredProperty}' listed as required but does not exist in '${schemaId}'\n`;
+        isValid = false;
       }
     }
   }
+
+  //  If we got some errors, then now is the time to throw the exception...
+  if (!isValid) {
+    throw ono.syntax(message);
+  }
 }
diff --git a/online/src/js/analytics.js b/online/src/js/analytics.js
index 4876fe6b..3aa372f8 100644
--- a/online/src/js/analytics.js
+++ b/online/src/js/analytics.js
@@ -43,7 +43,7 @@ analytics.trackEvent = function (category, action, label, value) {
 /**
  * Tracks an error in Google Analytics
  *
- * @param {Error} err
+ * @param {Error} err - The error we're tracking
  */
 analytics.trackError = function (err) {
   try {
diff --git a/online/src/js/dropdowns.js b/online/src/js/dropdowns.js
index cd4b8b01..025627f2 100644
--- a/online/src/js/dropdowns.js
+++ b/online/src/js/dropdowns.js
@@ -42,8 +42,8 @@ function dropdowns () {
  * Calls the given function whenever the user selects (or deselects)
  * a value in the given drop-down menu.
  *
- * @param {jQuery} menu
- * @param {function} setLabel
+ * @param {jQuery} menu        - dropdown menu we're using
+ * @param {Function} setLabel  - value to be set (or unset)
  */
 function onChange (menu, setLabel) {
   let dropdown = menu.parent(".dropdown");
@@ -144,7 +144,7 @@ function setSelectedMethod (methodName) {
 /**
  * Tracks changes to a checkbox option
  *
- * @param {jQuery} checkbox
+ * @param {jQuery} checkbox - Checkbox that we're tracking changes for
  */
 function trackCheckbox (checkbox) {
   checkbox.on("change", () => {
@@ -166,8 +166,8 @@ function trackButtonLabel (methodName) {
 /**
  * Examines the given checkboxes, and returns arrays of checked and unchecked values.
  *
- * @param {...jQuery} _checkboxes
- * @returns {{checked: string[], unchecked: string[]}}
+ * @param {...jQuery} _checkboxes                         - Checkboxes we're checking
+ * @returns {{checked: string[], unchecked: string[]}}    - Arrays of checked and unchecked values
  */
 function getCheckedAndUnchecked (_checkboxes) {
   let checked = [], unchecked = [];
diff --git a/online/src/js/editors.js b/online/src/js/editors.js
index 9414593e..e8640103 100644
--- a/online/src/js/editors.js
+++ b/online/src/js/editors.js
@@ -44,7 +44,7 @@ editors.showResult = function (title, content) {
 /**
  * Displays an error result
  *
- * @param {Error} err
+ * @param {Error} err - The error to be displayed
  */
 editors.showError = function (err) {
   editors.results.removeClass("hidden").addClass("error");
@@ -95,8 +95,8 @@ editors.addResult = function (title, content) {
 /**
  * Returns a short version of the given title text, to better fit in a tab
  *
- * @param {string} title
- * @returns {string}
+ * @param {string} title - The Title we're shortening
+ * @returns {string}     - The short version of the title
  */
 function getShortTitle (title) {
   // Get just the file name
@@ -134,8 +134,8 @@ function showResults () {
  * Converts the given object to text.
  * If possible, it is converted to JSON; otherwise, plain text.
  *
- * @param {object} obj
- * @returns {object}
+ * @param {object} obj - Object to be converted into text
+ * @returns {object}   - JSON or plain-text version of the object
  */
 function toText (obj) {
   if (obj instanceof Error) {
diff --git a/online/src/js/querystring.js b/online/src/js/querystring.js
index 3b63fb56..418a2c32 100644
--- a/online/src/js/querystring.js
+++ b/online/src/js/querystring.js
@@ -47,8 +47,8 @@ function setFormFields () {
 /**
  * Checks or unchecks the given checkbox, based on the given value.
  *
- * @param {jQuery} input
- * @param {*} value
+ * @param {jQuery} input - The checkbox we're manipulating
+ * @param {*} value      - Checked or Unchecked?
  */
 function setCheckbox (input, value) {
   if (!value || value === "true" || value === "on") {
diff --git a/package.json b/package.json
index 2b900584..16c76fbd 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
     "build:website": "simplifyify online/src/js/index.js --outfile online/js/bundle.js --bundle --debug --minify",
     "build:sass": "node-sass --source-map true --output-style compressed online/src/scss/style.scss online/css/style.min.css",
     "test": "npm run test:node && npm run test:typescript && npm run test:browser && npm run lint",
+    "test:quick": "mocha --quick-test",
     "test:node": "mocha",
     "test:browser": "karma start --single-run",
     "test:typescript": "tsc --noEmit --strict --lib esnext,dom test/specs/typescript-definition.spec.ts",
diff --git a/test/specs/real-world/known-errors.js b/test/specs/real-world/known-errors.js
index 8ce5172e..9fa7ba61 100644
--- a/test/specs/real-world/known-errors.js
+++ b/test/specs/real-world/known-errors.js
@@ -68,6 +68,62 @@ function getKnownApiErrors () {
       whatToDo: "ignore",
     },
 
+    // adyen.com v3.1.0 schemas seem to be interesting ...
+    {
+      api: "adyen.com",
+      error: "must NOT have unevaluated properties",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "airbyte.local:config",
+      error: "Property 'connection' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "airbyte.local:config",
+      error: "Property 'json_schema' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "apicurio.local:registry",
+      error: "Property 'group' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // TODO something to investigate
+    {
+      api: "apideck.com:file-storage",
+      error: "Cannot read property 'type' of undefined",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "apideck.com:webhook",
+      error: "Property 'data' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "apideck.com:hris",
+      error: "Property 'name' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "atlassian.com:jira",
+      error: "Property 'defaultScreen' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
     // Many Azure API definitions erroneously reference external files that don't exist.
     {
       api: "azure.com",
@@ -96,6 +152,41 @@ function getKnownApiErrors () {
       whatToDo: "ignore",
     },
 
+    // old field that used to exist included in 'required' ??
+    {
+      api: "box.com",
+      error: " Property 'grant_type' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "britbox.co.uk",
+      error: "Property 'email' listed as required but does not exist ",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "byautomata.io",
+      error: "Property 'terms' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "cdcgov.local:prime-data-hub",
+      error: "Property 'jurisdictionalFilter' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    // old field that used to exist included in 'required' ??
+    {
+      api: "clicksend.com",
+      error: "/paths/uploads?convert={convert}/post is missing path parameter(s) for {convert}",
+      whatToDo: "ignore"
+    },
+
     // Cloudmersive.com's API definition contains invalid JSON Schema types
     {
       api: "cloudmersive.com:ocr",
@@ -110,11 +201,37 @@ function getKnownApiErrors () {
       whatToDo: "ignore",
     },
 
+    // old field that used to exist included in 'required' ??
+    {
+      api: "dataflowkit.com",
+      error: "Property 'proxy' listed as required but does not exist ",
+      whatToDo: "ignore"
+    },
+
+    {
+      api: "digitallocker.gov.in:authpartner",
+      error: "Property 'id' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
     {
       api: "enode.io",
       error: "schema/items must NOT have additional properties",
       whatToDo: "ignore"
     },
+
+    {
+      api: "etsi.local:MEC010-2_AppPkgMgmt",
+      error: "Property 'featureName' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
+    {
+      api: "exavault.com",
+      error: "Property 'homeResource' listed as required but does not exist",
+      whatToDo: "ignore"
+    },
+
     {
       api: "frankiefinancial.io",
       error: "Property 'rowid' listed as required but does not exist",
diff --git a/test/specs/real-world/real-world.spec.js b/test/specs/real-world/real-world.spec.js
index 0dec3269..80d8e546 100644
--- a/test/specs/real-world/real-world.spec.js
+++ b/test/specs/real-world/real-world.spec.js
@@ -26,7 +26,7 @@ describe("Real-world APIs", () => {
     //   1) CI is really slow
     //   2) Some API definitions are HUGE and take a while to download
     //   3) If the download fails, we retry 2 times, which takes even more time
-    //   4) Really large API definitions take longer to pase, dereference, and validate
+    //   4) Really large API definitions take longer to parse, dereference, and validate
     this.currentTest.timeout(host.ci ? 300000 : 60000);     // 5 minutes in CI, 1 minute locally
     this.currentTest.slow(5000);
   });
@@ -83,6 +83,7 @@ describe("Real-world APIs", () => {
       else {
         // This is not a known error
         console.error("\n\nERROR IN THIS API:", JSON.stringify(api, null, 2));
+        console.error(JSON.stringify(error, null, 2));
         throw error;
       }
     }
diff --git a/test/specs/validate-spec/invalid/array-body-no-items.yaml b/test/specs/validate-spec/invalid-v2/array-body-no-items.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/array-body-no-items.yaml
rename to test/specs/validate-spec/invalid-v2/array-body-no-items.yaml
diff --git a/test/specs/validate-spec/invalid/array-no-items.yaml b/test/specs/validate-spec/invalid-v2/array-no-items.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/array-no-items.yaml
rename to test/specs/validate-spec/invalid-v2/array-no-items.yaml
diff --git a/test/specs/validate-spec/invalid/array-response-body-no-items.yaml b/test/specs/validate-spec/invalid-v2/array-request-body-no-items.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/array-response-body-no-items.yaml
rename to test/specs/validate-spec/invalid-v2/array-request-body-no-items.yaml
diff --git a/test/specs/validate-spec/invalid-v2/array-response-body-no-items.yaml b/test/specs/validate-spec/invalid-v2/array-response-body-no-items.yaml
new file mode 100644
index 00000000..d153e6fd
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v2/array-response-body-no-items.yaml
@@ -0,0 +1,13 @@
+swagger: "2.0"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    get:
+      responses:
+        200:
+          description:  hello world
+          schema:
+            type: array
diff --git a/test/specs/validate-spec/invalid/array-response-header-no-items.yaml b/test/specs/validate-spec/invalid-v2/array-response-header-no-items.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/array-response-header-no-items.yaml
rename to test/specs/validate-spec/invalid-v2/array-response-header-no-items.yaml
diff --git a/test/specs/validate-spec/invalid/body-and-form-params.yaml b/test/specs/validate-spec/invalid-v2/body-and-form-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/body-and-form-params.yaml
rename to test/specs/validate-spec/invalid-v2/body-and-form-params.yaml
diff --git a/test/specs/validate-spec/invalid/duplicate-operation-ids.yaml b/test/specs/validate-spec/invalid-v2/duplicate-operation-ids.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/duplicate-operation-ids.yaml
rename to test/specs/validate-spec/invalid-v2/duplicate-operation-ids.yaml
diff --git a/test/specs/validate-spec/invalid/duplicate-operation-params.yaml b/test/specs/validate-spec/invalid-v2/duplicate-operation-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/duplicate-operation-params.yaml
rename to test/specs/validate-spec/invalid-v2/duplicate-operation-params.yaml
diff --git a/test/specs/validate-spec/invalid/duplicate-path-params.yaml b/test/specs/validate-spec/invalid-v2/duplicate-path-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/duplicate-path-params.yaml
rename to test/specs/validate-spec/invalid-v2/duplicate-path-params.yaml
diff --git a/test/specs/validate-spec/invalid/duplicate-path-placeholders.yaml b/test/specs/validate-spec/invalid-v2/duplicate-path-placeholders.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/duplicate-path-placeholders.yaml
rename to test/specs/validate-spec/invalid-v2/duplicate-path-placeholders.yaml
diff --git a/test/specs/validate-spec/invalid/file-invalid-consumes.yaml b/test/specs/validate-spec/invalid-v2/file-invalid-consumes.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/file-invalid-consumes.yaml
rename to test/specs/validate-spec/invalid-v2/file-invalid-consumes.yaml
diff --git a/test/specs/validate-spec/invalid/file-no-consumes.yaml b/test/specs/validate-spec/invalid-v2/file-no-consumes.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/file-no-consumes.yaml
rename to test/specs/validate-spec/invalid-v2/file-no-consumes.yaml
diff --git a/test/specs/validate-spec/invalid/invalid-response-code.yaml b/test/specs/validate-spec/invalid-v2/invalid-response-code.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/invalid-response-code.yaml
rename to test/specs/validate-spec/invalid-v2/invalid-response-code.yaml
diff --git a/test/specs/validate-spec/invalid/multiple-body-params.yaml b/test/specs/validate-spec/invalid-v2/multiple-body-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/multiple-body-params.yaml
rename to test/specs/validate-spec/invalid-v2/multiple-body-params.yaml
diff --git a/test/specs/validate-spec/invalid/multiple-operation-body-params.yaml b/test/specs/validate-spec/invalid-v2/multiple-operation-body-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/multiple-operation-body-params.yaml
rename to test/specs/validate-spec/invalid-v2/multiple-operation-body-params.yaml
diff --git a/test/specs/validate-spec/invalid/multiple-path-body-params.yaml b/test/specs/validate-spec/invalid-v2/multiple-path-body-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/multiple-path-body-params.yaml
rename to test/specs/validate-spec/invalid-v2/multiple-path-body-params.yaml
diff --git a/test/specs/validate-spec/invalid/no-path-params.yaml b/test/specs/validate-spec/invalid-v2/no-path-params.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/no-path-params.yaml
rename to test/specs/validate-spec/invalid-v2/no-path-params.yaml
diff --git a/test/specs/validate-spec/invalid-v2/param-array-body-no-items.yaml b/test/specs/validate-spec/invalid-v2/param-array-body-no-items.yaml
new file mode 100644
index 00000000..beb48108
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v2/param-array-body-no-items.yaml
@@ -0,0 +1,16 @@
+swagger: "2.0"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    parameters:
+      - name: people
+        in: body
+        schema:
+          type: array
+    post:
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v2/param-array-no-items.yaml b/test/specs/validate-spec/invalid-v2/param-array-no-items.yaml
new file mode 100644
index 00000000..5c976e2b
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v2/param-array-no-items.yaml
@@ -0,0 +1,15 @@
+swagger: "2.0"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    parameters:
+      - name: tags
+        in: query
+        type: array
+    get:
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid/path-param-no-placeholder.yaml b/test/specs/validate-spec/invalid-v2/path-param-no-placeholder.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/path-param-no-placeholder.yaml
rename to test/specs/validate-spec/invalid-v2/path-param-no-placeholder.yaml
diff --git a/test/specs/validate-spec/invalid/path-placeholder-no-param.yaml b/test/specs/validate-spec/invalid-v2/path-placeholder-no-param.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/path-placeholder-no-param.yaml
rename to test/specs/validate-spec/invalid-v2/path-placeholder-no-param.yaml
diff --git a/test/specs/validate-spec/invalid/required-property-not-defined-definitions.yaml b/test/specs/validate-spec/invalid-v2/required-property-not-defined-definitions.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/required-property-not-defined-definitions.yaml
rename to test/specs/validate-spec/invalid-v2/required-property-not-defined-definitions.yaml
diff --git a/test/specs/validate-spec/invalid/required-property-not-defined-input.yaml b/test/specs/validate-spec/invalid-v2/required-property-not-defined-input.yaml
similarity index 100%
rename from test/specs/validate-spec/invalid/required-property-not-defined-input.yaml
rename to test/specs/validate-spec/invalid-v2/required-property-not-defined-input.yaml
diff --git a/test/specs/validate-spec/invalid-v2/response-array-header-no-items.yaml b/test/specs/validate-spec/invalid-v2/response-array-header-no-items.yaml
new file mode 100644
index 00000000..182e3498
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v2/response-array-header-no-items.yaml
@@ -0,0 +1,16 @@
+swagger: "2.0"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    get:
+      responses:
+        "default":
+          description:  hello world
+          headers:
+            Content-Type:
+              type: string
+            Last-Modified:
+              type: array
diff --git a/test/specs/validate-spec/invalid-v3/duplicate-operation-ids.yaml b/test/specs/validate-spec/invalid-v3/duplicate-operation-ids.yaml
new file mode 100644
index 00000000..64cda3c9
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/duplicate-operation-ids.yaml
@@ -0,0 +1,17 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    get:
+      operationId: users
+      responses:
+        default:
+          description:  hello world
+    post:
+      operationId: users          # <---- duplicate
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/duplicate-operation-params.yaml b/test/specs/validate-spec/invalid-v3/duplicate-operation-params.yaml
new file mode 100644
index 00000000..5fc48c96
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/duplicate-operation-params.yaml
@@ -0,0 +1,37 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}:
+    get:
+      parameters:
+        - name: username           # <---- Duplicate param
+          in: path
+          required: true
+          schema:
+            type: string
+        - name: bar
+          in: header
+          schema:
+            type: string
+          required: false
+        - name: username          # <---- Another username but in header is ok
+          in: header
+          schema:
+            type: string
+          required: false
+        - name: username          # <---- Another username but in query is ok
+          in: query
+          schema:
+            type: string
+          required: false
+        - name: username           # <---- Duplicate param
+          in: path
+          schema:
+            type: number
+          required: true
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/duplicate-path-params.yaml b/test/specs/validate-spec/invalid-v3/duplicate-path-params.yaml
new file mode 100644
index 00000000..063633dc
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/duplicate-path-params.yaml
@@ -0,0 +1,35 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}:
+    parameters:
+      - name: username
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: foo           # <---- Duplicate param
+        in: header
+        schema:
+          type: string
+        required: false
+      - name: username      # <---- Same param but in header is ok
+        in: header
+        schema:
+          type: string
+      - name: username      # <---- Same param but in query is ok
+        in: query
+        schema:
+          type: string
+      - name: foo           # <---- Duplicate param
+        in: header
+        schema:
+          type: number
+        required: true
+    get:
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/duplicate-path-placeholders.yaml b/test/specs/validate-spec/invalid-v3/duplicate-path-placeholders.yaml
new file mode 100644
index 00000000..578e079d
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/duplicate-path-placeholders.yaml
@@ -0,0 +1,22 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}/profile/{username}/image/{img_id}:   # <---- duplicate {username} placeholders
+    parameters:
+      - name: username
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: img_id
+        in: path
+        required: true
+        schema:
+          type: number
+    get:
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/multiple-cookie-params.yaml b/test/specs/validate-spec/invalid-v3/multiple-cookie-params.yaml
new file mode 100644
index 00000000..eddc211a
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/multiple-cookie-params.yaml
@@ -0,0 +1,22 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    patch:
+      parameters:
+        - name: username
+          in: cookie          # <---- Cookie param #1
+          required: true
+          schema:
+            type: string
+        - name: username
+          in: cookie          # <---- Cookie param #2
+          required: true
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/multiple-header-params.yaml b/test/specs/validate-spec/invalid-v3/multiple-header-params.yaml
new file mode 100644
index 00000000..33eff346
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/multiple-header-params.yaml
@@ -0,0 +1,22 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    patch:
+      parameters:
+        - name: username
+          in: header          # <---- Header param #1
+          required: true
+          schema:
+            type: string
+        - name: username
+          in: header          # <---- Header param #2
+          required: true
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/multiple-path-params.yaml b/test/specs/validate-spec/invalid-v3/multiple-path-params.yaml
new file mode 100644
index 00000000..c5738409
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/multiple-path-params.yaml
@@ -0,0 +1,22 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}:
+    patch:
+      parameters:
+        - name: username
+          in: path          # <---- Path param #1
+          required: true
+          schema:
+            type: string
+        - name: username
+          in: path          # <---- Path param #2
+          required: true
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/multiple-query-params.yaml b/test/specs/validate-spec/invalid-v3/multiple-query-params.yaml
new file mode 100644
index 00000000..69628acf
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/multiple-query-params.yaml
@@ -0,0 +1,22 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    patch:
+      parameters:
+        - name: username
+          in: query          # <---- Query param #1
+          required: true
+          schema:
+            type: string
+        - name: username
+          in: query          # <---- Query param #2
+          required: true
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/no-path-params.yaml b/test/specs/validate-spec/invalid-v3/no-path-params.yaml
new file mode 100644
index 00000000..34b29e20
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/no-path-params.yaml
@@ -0,0 +1,37 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}/{foo}:        # <---- {username} and {foo} placeholders
+    parameters:                   # <---- no path params
+      - $ref: '#/components/parameters/username'
+      - name: foo
+        in: query
+        schema:
+          type: string
+    get:
+      parameters:
+        - $ref: '#/components/parameters/username'
+        - name: foo              #  <----  no path params
+          in: header
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
+    post:
+      parameters:                 # <---- no path params
+        - $ref: '#/components/parameters/username'
+      responses:
+        default:
+          description:  hello world
+components:
+  parameters:
+    username:
+      name: username
+      in: header
+      required: true
+      schema:
+        type: string
diff --git a/test/specs/validate-spec/invalid-v3/param-array-no-items.yaml b/test/specs/validate-spec/invalid-v3/param-array-no-items.yaml
new file mode 100644
index 00000000..1f16af30
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/param-array-no-items.yaml
@@ -0,0 +1,16 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    parameters:
+      - name: tags
+        in: query
+        schema:
+          type: array
+    get:
+      responses:
+        default:
+          description:  hello world
diff --git a/test/specs/validate-spec/invalid-v3/path-param-no-placeholder.yaml b/test/specs/validate-spec/invalid-v3/path-param-no-placeholder.yaml
new file mode 100644
index 00000000..56c0f84a
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/path-param-no-placeholder.yaml
@@ -0,0 +1,42 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}:        # <---- {username} placeholder
+    parameters:
+      - $ref: '#/components/parameters/username'
+      - name: foo           # <---- foo in cookie is OK
+        in: cookie
+        schema:
+          type: string
+    get:
+      parameters:
+        - $ref: '#/components/parameters/username'
+        - name: foo         # <---- foo in query is OK
+          in: query
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
+    post:
+      parameters:
+        - $ref: '#/components/parameters/username'
+        - name: foo         # <---- There is no {foo} placeholder
+          in: path
+          required: true
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
+components:
+  parameters:
+    username:
+      name: username
+      in: path
+      required: true
+      schema:
+        type: string
diff --git a/test/specs/validate-spec/invalid-v3/path-placeholder-no-param.yaml b/test/specs/validate-spec/invalid-v3/path-placeholder-no-param.yaml
new file mode 100644
index 00000000..0a8ae173
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/path-placeholder-no-param.yaml
@@ -0,0 +1,37 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users/{username}/{foo}:        # <---- {username} and {foo} placeholders
+    parameters:
+      - $ref: '#/components/parameters/username'            # <---- "username" path param
+      - name: foo                                           # <---- "foo" not in path
+        in: query
+        schema:
+          type: string
+    get:
+      parameters:                   # <---- there's no "foo" path param
+        - $ref: '#/components/parameters/username'            # <---- "username" path param
+        - name: foo                                           # <---- "foo" not in path
+          in: cookie
+          schema:
+            type: number
+      responses:
+        default:
+          description:  hello world
+    post:
+      parameters:                   # <---- there's no "foo" path param
+        - $ref: '#/components/parameters/username'
+      responses:
+        default:
+          description:  hello world
+components:
+  parameters:
+    username:
+      name: username
+      in: path
+      required: true
+      schema:
+        type: string
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-parameters-unused.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-parameters-unused.yaml
new file mode 100644
index 00000000..197b89a4
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-parameters-unused.yaml
@@ -0,0 +1,51 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      parameters:
+        - $ref: '#/components/parameters/Pet'
+      responses:
+        '200':
+          description: pet response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
+components:
+  parameters:
+    Pet:
+      name: pet
+      in: query
+      description: Pet to add to the store
+      required: true
+      schema:
+        type: object
+        required:
+          - name
+        properties:
+          name:
+            type: string
+          color:
+            type: string
+    Unused:
+      name: unused
+      in: query
+      description: Unused to add to the store
+      required: true
+      schema:
+        type: object
+        required:
+          - name
+          - paramUnusedNotExists          # <--- does not exist
+        properties:
+          name:
+            type: string
+          color:
+            type: string
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-parameters.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-parameters.yaml
new file mode 100644
index 00000000..07de8bc8
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-parameters.yaml
@@ -0,0 +1,52 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      parameters:
+        - $ref: '#/components/parameters/Pet'
+      responses:
+        '200':
+          description: pet response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
+components:
+  parameters:
+    Pet:
+      name: pet
+      in: query
+      description: Pet to add to the store
+      required: true
+      schema:
+        type: object
+        required:
+          - name
+          - paramNotExists                # <--- does not exist
+        properties:
+          name:
+            type: string
+          color:
+            type: string
+    Unused:
+      name: unused
+      in: query
+      description: Unused to add to the store
+      required: true
+      schema:
+        type: object
+        required:
+          - name
+          - paramUnusedNotExists          # <--- does not exist
+        properties:
+          name:
+            type: string
+          color:
+            type: string
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-requestBodies.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-requestBodies.yaml
new file mode 100644
index 00000000..cb3d250e
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-requestBodies.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      requestBody:
+        $ref: '#/components/requestBodies/Pet'
+      responses:
+        '200':
+          description: pet response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
+
+components:
+  requestBodies:
+    Pet:
+      description: Pet for use in request bodies
+      content:
+        application/json:
+          schema:
+            type: object
+            required:
+              - reqBodyNotExists                # <--- does not exist
+            properties:
+              name:
+                type: string
+              color:
+                type: string
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-responses.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-responses.yaml
new file mode 100644
index 00000000..6d883a45
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-responses.yaml
@@ -0,0 +1,36 @@
+openapi: "3.0.2"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  '/pet/{petId}':
+      get:
+        summary: Find pet by ID
+        parameters:
+          - name: petId
+            in: path
+            description: ID of pet to return
+            required: true
+            schema:
+              type: integer
+              format: int64
+        responses:
+          '200':
+            $ref: '#/components/responses/Pet'
+components:
+  responses:
+    Pet:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            type: object
+            required:
+              - name
+              - photoUrls                        # <--- does not exist
+            properties:
+              name:
+                type: string
+                example: doggie
+              color:
+                type: string
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-schemas-recursive.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-schemas-recursive.yaml
new file mode 100644
index 00000000..ebb26117
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-schemas-recursive.yaml
@@ -0,0 +1,47 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      requestBody:
+        description: Pet to add to the store
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Pet'
+      responses:
+        '200':
+          description: pet response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - name
+      properties:
+        name:
+          type: string
+        color:
+          type: string
+        kind:
+          type: object
+          required:
+            - stripes
+            - secondLevelNotExists
+          properties:
+            stripes:
+              type: boolean
+            spots:
+              type: boolean
+
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-schemas.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-schemas.yaml
new file mode 100644
index 00000000..90e7fa48
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-components-schemas.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      requestBody:
+        description: Pet to add to the store
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Pet'
+      responses:
+        '200':
+          description: pet response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - notExists                # <--- does not exist
+      properties:
+        name:
+          type: string
+        color:
+          type: string
diff --git a/test/specs/validate-spec/invalid-v3/required-property-not-defined-param.yaml b/test/specs/validate-spec/invalid-v3/required-property-not-defined-param.yaml
new file mode 100644
index 00000000..0b6b2c44
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/required-property-not-defined-param.yaml
@@ -0,0 +1,32 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      parameters:
+        - name: pet
+          in: query
+          description: Pet to add to the store
+          required: true
+          schema:
+            type: object
+            required:
+              - notExists                # <--- does not exist
+            properties:
+              name:
+                type: string
+              color:
+                type: string
+      responses:
+        '200':
+          description: pet response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  name:
+                    type: string
diff --git a/test/specs/validate-spec/invalid-v3/response-array-no-items.yaml b/test/specs/validate-spec/invalid-v3/response-array-no-items.yaml
new file mode 100644
index 00000000..895eccae
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/response-array-no-items.yaml
@@ -0,0 +1,15 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    get:
+      responses:
+        default:
+          description:  hello world
+          content:
+            application/json:
+              schema:
+                type: array
diff --git a/test/specs/validate-spec/invalid-v3/response-header-array-no-items.yaml b/test/specs/validate-spec/invalid-v3/response-header-array-no-items.yaml
new file mode 100644
index 00000000..ca52a301
--- /dev/null
+++ b/test/specs/validate-spec/invalid-v3/response-header-array-no-items.yaml
@@ -0,0 +1,18 @@
+openapi: "3.0.2"
+info:
+  version: "1.0.0"
+  title: Invalid API
+
+paths:
+  /users:
+    get:
+      responses:
+        "default":
+          description:  hello world
+          headers:
+            Content-Type:
+              schema:
+                type: string
+            Last-Modified:
+              schema:
+                type: array
diff --git a/test/specs/validate-spec/valid/file-vendor-specific-consumes-formdata.yaml b/test/specs/validate-spec/valid-v2/file-vendor-specific-consumes-formdata.yaml
similarity index 100%
rename from test/specs/validate-spec/valid/file-vendor-specific-consumes-formdata.yaml
rename to test/specs/validate-spec/valid-v2/file-vendor-specific-consumes-formdata.yaml
diff --git a/test/specs/validate-spec/valid/file-vendor-specific-consumes-urlencoded.yaml b/test/specs/validate-spec/valid-v2/file-vendor-specific-consumes-urlencoded.yaml
similarity index 100%
rename from test/specs/validate-spec/valid/file-vendor-specific-consumes-urlencoded.yaml
rename to test/specs/validate-spec/valid-v2/file-vendor-specific-consumes-urlencoded.yaml
diff --git a/test/specs/validate-spec/valid/inherited-required-properties.yaml b/test/specs/validate-spec/valid-v2/inherited-required-properties.yaml
similarity index 100%
rename from test/specs/validate-spec/valid/inherited-required-properties.yaml
rename to test/specs/validate-spec/valid-v2/inherited-required-properties.yaml
diff --git a/test/specs/validate-spec/valid-v3/inherited-required-properties.yaml b/test/specs/validate-spec/valid-v3/inherited-required-properties.yaml
new file mode 100644
index 00000000..1208ac17
--- /dev/null
+++ b/test/specs/validate-spec/valid-v3/inherited-required-properties.yaml
@@ -0,0 +1,58 @@
+openapi: '3.0.2'
+info:
+  contact:
+    x-twitter: hello_iqualify
+  description: >+
+    The iQualify API for testing
+  title: iQualify
+  version: v1
+paths:
+  /offerings:
+    post:
+      description: Creates new offering.
+      requestBody:
+          description: create offering request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/OfferingRequired'
+      responses:
+        '201':
+          $ref: '#/components/responses/OfferingMetadataResponse'
+      summary: Create offering
+components:
+  schemas:
+    Offering:
+      properties:
+        contentId:
+          minLength: 1
+          type: string
+        end:
+          format: date-time
+          type: string
+        isReadonly:
+          type: boolean
+        name:
+          minLength: 1
+          type: string
+        start:
+          format: date-time
+          type: string
+    OfferingRequired:
+        allOf:
+          - $ref: '#/components/schemas/Offering'
+        required:
+          - contentId                        # <-- all required properties are inherited
+          - start
+          - end
+  responses:
+    OfferingMetadataResponse:
+      description: Offering response
+      content:
+        text/plain:
+          schema:
+            type: object
+            properties:
+              contentId:
+                minLength: 1
+                type: string
diff --git a/test/specs/validate-spec/validate-spec.spec.js b/test/specs/validate-spec/validate-spec-v2.spec.js
similarity index 85%
rename from test/specs/validate-spec/validate-spec.spec.js
rename to test/specs/validate-spec/validate-spec-v2.spec.js
index 70bd5041..cbe189c2 100644
--- a/test/specs/validate-spec/validate-spec.spec.js
+++ b/test/specs/validate-spec/validate-spec-v2.spec.js
@@ -1,10 +1,10 @@
 "use strict";
 
 const { expect } = require("chai");
-const SwaggerParser = require("../../..");
+const SwaggerParser = require("../../../lib");
 const path = require("../../utils/path");
 
-describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
+describe("Invalid APIs (Swagger v2.0 specification validation)", () => {
   let tests = [
     {
       name: "invalid response code",
@@ -16,13 +16,13 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
       name: "duplicate path parameters",
       valid: false,
       file: "duplicate-path-params.yaml",
-      error: 'Validation failed. /paths/users/{username} has duplicate parameters \nValidation failed. Found multiple header parameters named \"foo\"'
+      error: 'Validation failed. /paths/users/{username} has duplicate parameters\nValidation failed. Found multiple header parameters named \"foo\"'
     },
     {
       name: "duplicate operation parameters",
       valid: false,
       file: "duplicate-operation-params.yaml",
-      error: 'Validation failed. /paths/users/{username}/get has duplicate parameters \nValidation failed. Found multiple path parameters named \"username\"'
+      error: 'Validation failed. /paths/users/{username}/get has duplicate parameters\nValidation failed. Found multiple path parameters named \"username\"'
     },
     {
       name: "multiple body parameters in path",
@@ -75,13 +75,13 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
     {
       name: "array param without items",
       valid: false,
-      file: "array-no-items.yaml",
+      file: "param-array-no-items.yaml",
       error: 'Validation failed. /paths/users/get/parameters/tags is an array, so it must include an \"items\" schema'
     },
     {
       name: "array body param without items",
       valid: false,
-      file: "array-body-no-items.yaml",
+      file: "param-array-body-no-items.yaml",
       error: 'Validation failed. /paths/users/post/parameters/people is an array, so it must include an \"items\" schema'
     },
     {
@@ -90,6 +90,12 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
       file: "array-response-header-no-items.yaml",
       error: 'Validation failed. /paths/users/get/responses/default/headers/Last-Modified is an array, so it must include an \"items\" schema'
     },
+    {
+      name: "array response body without items",
+      valid: false,
+      file: "array-response-body-no-items.yaml",
+      error: 'Validation failed. /paths/users/get/responses/200/schema is an array, so it must include an \"items\" schema'
+    },
     {
       name: '"file" param without "consumes"',
       valid: false,
@@ -136,11 +142,11 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
       error: "Validation failed. Duplicate operation id 'users'"
     },
     {
-      name: "array response body without items",
+      name: "array request body without items",
       valid: false,
-      file: "array-response-body-no-items.yaml",
-      error: 'Validation failed. /paths/users/get/responses/200/schema is an array, so it must include an \"items\" schema'
-    }
+      file: "array-request-body-no-items.yaml",
+      error: "Validation failed. /paths/users/get/responses/200/schema is an array, so it must include an \"items\" schema"
+    },
   ];
 
   it('should pass validation if "options.validate.spec" is false', async () => {
@@ -148,7 +154,7 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
     expect(invalid.valid).to.equal(false);
 
     const api = await SwaggerParser
-      .validate(path.rel("specs/validate-spec/invalid/" + invalid.file), { validate: { spec: false }});
+      .validate(path.rel("specs/validate-spec/invalid-v2/" + invalid.file), { validate: { spec: false }});
     expect(api).to.be.an("object");
   });
 
@@ -157,8 +163,9 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
       it(test.name, async () => {
         try {
           const api = await SwaggerParser
-            .validate(path.rel("specs/validate-spec/valid/" + test.file));
+            .validate(path.rel("specs/validate-spec/valid-v2/" + test.file));
           expect(api).to.be.an("object");
+          expect(api.swagger).to.equal("2.0");
         }
         catch (err) {
           throw new Error("Validation should have succeeded, but it failed!\n" + err.stack);
@@ -168,13 +175,13 @@ describe("Invalid APIs (Swagger 2.0 specification validation)", () => {
     else {
       it(test.name, async () => {
         try {
-          await SwaggerParser.validate(path.rel("specs/validate-spec/invalid/" + test.file));
+          await SwaggerParser.validate(path.rel("specs/validate-spec/invalid-v2/" + test.file));
           throw new Error("Validation should have failed, but it succeeded!");
         }
         catch (err) {
           expect(err).to.be.an.instanceOf(SyntaxError);
-          expect(err.message).to.equal(test.error);
-          expect(err.message).to.match(/^Validation failed. \S+/);
+          expect(err.message).to.include(test.error);
+          expect(err.message).to.match(/^Specification check failed.\sValidation failed./);
         }
       });
     }
diff --git a/test/specs/validate-spec/validate-spec-v3.spec.js b/test/specs/validate-spec/validate-spec-v3.spec.js
new file mode 100644
index 00000000..9c5a5fa4
--- /dev/null
+++ b/test/specs/validate-spec/validate-spec-v3.spec.js
@@ -0,0 +1,186 @@
+"use strict";
+
+const { expect } = require("chai");
+const SwaggerParser = require("../../..");
+const path = require("../../utils/path");
+
+describe("Invalid APIs (OpenAPI v3.0 specification validation)", () => {
+  let tests = [
+    {
+      name: "duplicate path parameters",
+      valid: false,
+      file: "duplicate-path-params.yaml",
+      error: 'Validation failed. /paths/users/{username} has duplicate parameters\nValidation failed. Found multiple header parameters named \"foo\"'
+    },
+    {
+      name: "duplicate operation parameters",
+      valid: false,
+      file: "duplicate-operation-params.yaml",
+      error: 'Validation failed. /paths/users/{username}/get has duplicate parameters\nValidation failed. Found multiple path parameters named \"username\"'
+    },
+    {
+      name: "multiple parameters in path",
+      valid: false,
+      file: "multiple-path-params.yaml",
+      error: 'Validation failed. Found multiple path parameters named \"username\"'
+    },
+    {
+      name: "multiple parameters in query",
+      valid: false,
+      file: "multiple-query-params.yaml",
+      error: 'Validation failed. Found multiple query parameters named \"username\"'
+    },
+    {
+      name: "multiple parameters in header",
+      valid: false,
+      file: "multiple-header-params.yaml",
+      error: 'Validation failed. Found multiple header parameters named \"username\"'
+    },
+    {
+      name: "multiple parameters in cookie",
+      valid: false,
+      file: "multiple-cookie-params.yaml",
+      error: 'Validation failed. Found multiple cookie parameters named \"username\"'
+    },
+    {
+      name: "path param with no placeholder",
+      valid: false,
+      file: "path-param-no-placeholder.yaml",
+      error: 'Validation failed. /paths/users/{username}/post has a path parameter named \"foo\", but there is no corresponding {foo} in the path string'
+    },
+    {
+      name: "path placeholder with no param",
+      valid: false,
+      file: "path-placeholder-no-param.yaml",
+      error: "Validation failed. /paths/users/{username}/{foo}/get is missing path parameter(s) for {foo}"
+    },
+    {
+      name: "duplicate path placeholders",
+      valid: false,
+      file: "duplicate-path-placeholders.yaml",
+      error: "Validation failed. /paths/users/{username}/profile/{username}/image/{img_id}/get has multiple path placeholders named {username}"
+    },
+    {
+      name: "no path parameters",
+      valid: false,
+      file: "no-path-params.yaml",
+      error: "Validation failed. /paths/users/{username}/{foo}/get is missing path parameter(s) for {username},{foo}"
+    },
+    {
+      name: "array param without items",
+      valid: false,
+      file: "param-array-no-items.yaml",
+      error: 'Validation failed. /paths/users/get/parameters/tags is an array, so it must include an \"items\" schema'
+    },
+    {
+      name: "response array without items",
+      valid: false,
+      file: "response-array-no-items.yaml",
+      error: 'Validation failed. /paths/users/get/responses/default/content/application/json/schema is an array, so it must include an "items" schema'
+    },
+    {
+      name: "response header array without items",
+      valid: false,
+      file: "response-header-array-no-items.yaml",
+      error: 'Validation failed. /paths/users/get/responses/default/headers/Last-Modified is an array, so it must include an \"items\" schema'
+    },
+    {
+      name: "required property in param does not exist",
+      valid: false,
+      file: "required-property-not-defined-param.yaml",
+      error: "Validation failed. Property 'notExists' listed as required but does not exist in '/paths/pets/post/parameters/pet'"
+    },
+    {
+      name: "required property in components/parameters does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-parameters.yaml",
+      error: "Validation failed. Property \'paramNotExists\' listed as required but does not exist"
+    },
+    {
+      name: "required property in unused components/parameters does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-parameters-unused.yaml",
+      error: "Validation failed. Property \'paramUnusedNotExists\' listed as required but does not exist"
+    },
+    {
+      name: "required property in components/requestBodies does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-requestBodies.yaml",
+      error: "Validation failed. Property \'reqBodyNotExists\' listed as required but does not exist"
+    },
+    {
+      name: "required property in components/schemas does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-schemas.yaml",
+      error: "Validation failed. Property \'notExists\' listed as required but does not exist"
+    },
+    {
+      name: "required property in components/schemas second-level does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-schemas-recursive.yaml",
+      error: "Validation failed. Property \'secondLevelNotExists\' listed as required but does not exist"
+    },
+    {
+      name: "required property in components/responses does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-responses.yaml",
+      error: "Validation failed. Property \'photoUrls\' listed as required but does not exist"
+    },
+    {
+      name: "required property in components/parameters does not exist",
+      valid: false,
+      file: "required-property-not-defined-components-parameters.yaml",
+      error: "Validation failed. Property \'paramNotExists\' listed as required but does not exist"
+    },
+    {
+      name: "schema declares required properties which are inherited (allOf)",
+      valid: true,
+      file: "inherited-required-properties.yaml"
+    },
+    {
+      name: "duplicate operation IDs",
+      valid: false,
+      file: "duplicate-operation-ids.yaml",
+      error: "Validation failed. Duplicate operation id 'users'"
+    }
+  ];
+
+  it('should pass validation if "options.validate.spec" is false', async () => {
+    let invalid = tests[0];
+    expect(invalid.valid).to.equal(false);
+
+    const api = await SwaggerParser
+      .validate(path.rel("specs/validate-spec/invalid-v3/" + invalid.file), { validate: { spec: false }});
+    expect(api).to.be.an("object");
+    expect(api.openapi).to.match(/^3\.0/);
+  });
+
+  for (let test of tests) {
+    if (test.valid) {
+      it(test.name, async () => {
+        try {
+          const api = await SwaggerParser
+            .validate(path.rel("specs/validate-spec/valid-v3/" + test.file));
+          expect(api).to.be.an("object");
+          expect(api.openapi).to.match(/^3\.0/);
+        }
+        catch (err) {
+          throw new Error("Validation should have succeeded, but it failed!\n File:" + test.file + "\n" + err.stack);
+        }
+      });
+    }
+    else {
+      it(test.name, async () => {
+        try {
+          await SwaggerParser.validate(path.rel("specs/validate-spec/invalid-v3/" + test.file));
+          throw new Error("Validation should have failed, but it succeeded!\n File:" + test.file + "\n");
+        }
+        catch (err) {
+          expect(err).to.be.an.instanceOf(SyntaxError);
+          expect(err.message).to.include(test.error);
+          expect(err.message).to.match(/^Specification check failed.\sValidation failed./);
+        }
+      });
+    }
+  }
+});