{
  "openapi": "3.1.0",
  "info": {
    "title": "AI Security Directory public API",
    "version": "1.0.0",
    "summary": "Search and dump the live catalog of AI-era security vendors.",
    "description": "Public JSON for aisecuritydirectory.com. Search filters the live catalog. The dump returns every live listing. No authentication. Checkout, webhooks, and submission endpoints are not part of this document. Listing order is not a ranking and not paid placement. Stable REST uses a major version in the path, starting at `/api/v1/`. Unversioned `/api/search` and `/api/vendors.json` are aliases of the v1 handlers and stay. Backward-compatible fields may be added inside v1. A breaking change requires `/api/v2/`. If a version is retired, responses will carry RFC 8594 `Sunset` and `Deprecation` headers, and this document will name the retirement date at least 90 days ahead. Unknown `/api/*` paths return HTTP 404 application/json `{error:{code,message,docs}}`. GET `/api/search` and GET `/api/v1/search` return HTTP 400 in the same shape when `limit` is present and is not an integer in 1..100. Catalog GET responses send RateLimit, RateLimit-Policy, and X-RateLimit-* headers. A 429, if issued, includes Retry-After.",
    "contact": {
      "name": "AI Security Directory",
      "url": "https://aisecuritydirectory.com/about"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://aisecuritydirectory.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "catalog",
      "description": "Live directory listings. No auth."
    }
  ],
  "security": [],
  "paths": {
    "/api/vendors.json": {
      "get": {
        "operationId": "listVendors",
        "tags": [
          "catalog"
        ],
        "summary": "Dump the live catalog",
        "description": "Returns every live listing. Envelope field is `vendors`. Also includes `categories`, `categoryCount`, response-date `lastUpdated`, and catalog freshness `dataAsOf`. No authentication. Revalidated about every 60 seconds.",
        "responses": {
          "200": {
            "description": "Catalog dump",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VendorsDump"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "searchVendors",
        "tags": [
          "catalog"
        ],
        "summary": "Search live listings",
        "description": "Filter the live catalog. Dimensions AND together. Repeated `category` and `stage` OR within that dimension (any value may match). `tier` is single-valued exact match on `bh_tier`. `q` is a case-insensitive substring over name, summary, description, category slug, category name, and tags. `limit` defaults to 50. If `limit` is present and is not an integer from 1 to 100, the response is HTTP 400. Response `count` is the page size returned; `total` is matches before limit; `limit` is the applied cap.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Category slugs. Repeat or comma-separate. OR within the dimension."
          },
          {
            "name": "stage",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Funding stages. OR within the dimension."
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single-valued exact match on bh_tier."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring over name, summary, description, category, category name, tags."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Page size. Defaults to 50. If present, must be an integer from 1 to 100; otherwise HTTP 400."
          }
        ],
        "responses": {
          "200": {
            "description": "Search page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VendorsSearch"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "operationId": "searchVendorsV1",
        "tags": [
          "catalog"
        ],
        "summary": "Search live listings (v1)",
        "description": "Versioned alias of GET /api/search. Same query params, envelope, and errors. New integrations should call this path. Filter the live catalog. Dimensions AND together. Repeated `category` and `stage` OR within that dimension (any value may match). `tier` is single-valued exact match on `bh_tier`. `q` is a case-insensitive substring over name, summary, description, category slug, category name, and tags. `limit` defaults to 50. If `limit` is present and is not an integer from 1 to 100, the response is HTTP 400. Response `count` is the page size returned; `total` is matches before limit; `limit` is the applied cap.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Category slugs. Repeat or comma-separate. OR within the dimension."
          },
          {
            "name": "stage",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Funding stages. OR within the dimension."
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single-valued exact match on bh_tier."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring over name, summary, description, category, category name, tags."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Page size. Defaults to 50. If present, must be an integer from 1 to 100; otherwise HTTP 400."
          }
        ],
        "responses": {
          "200": {
            "description": "Search page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VendorsSearch"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          }
        }
      }
    },
    "/api/v1/vendors.json": {
      "get": {
        "operationId": "listVendorsV1",
        "tags": [
          "catalog"
        ],
        "summary": "Dump the live catalog (v1)",
        "description": "Versioned alias of GET /api/vendors.json. Same envelope. New integrations should call this path. Returns every live listing. Envelope field is `vendors`. Also includes `categories`, `categoryCount`, response-date `lastUpdated`, and catalog freshness `dataAsOf`. No authentication. Revalidated about every 60 seconds.",
        "responses": {
          "200": {
            "description": "Catalog dump",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VendorsDump"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "VendorsDump": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "directory",
          "url",
          "lastUpdated",
          "dataAsOf",
          "count",
          "categoryCount",
          "categories",
          "vendors"
        ],
        "properties": {
          "directory": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "lastUpdated": {
            "type": "string",
            "description": "Response date YYYY-MM-DD."
          },
          "dataAsOf": {
            "type": "string",
            "description": "Latest catalog date_added YYYY-MM-DD."
          },
          "count": {
            "type": "integer"
          },
          "categoryCount": {
            "type": "integer"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "vendors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicVendor"
            }
          }
        }
      },
      "VendorsSearch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "count",
          "total",
          "limit",
          "normalized",
          "results"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "description": "Length of `results` (page size)."
          },
          "total": {
            "type": "integer",
            "description": "Matches before limit."
          },
          "limit": {
            "type": "integer"
          },
          "normalized": {
            "type": "object",
            "additionalProperties": true
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VendorSearchHit"
            }
          }
        }
      },
      "VendorSearchHit": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "slug",
          "brief_summary",
          "category",
          "founded",
          "funding",
          "funding_stage",
          "url",
          "details"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "brief_summary": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "founded": {
            "type": [
              "string",
              "null"
            ]
          },
          "funding": {
            "type": [
              "string",
              "null"
            ]
          },
          "funding_stage": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string"
          },
          "details": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PublicVendor": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "name",
          "slug",
          "brief_summary",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "brief_summary": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "ApiError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message",
              "docs"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "not_found",
                  "invalid_input",
                  "method_not_allowed",
                  "rate_limited",
                  "internal"
                ]
              },
              "message": {
                "type": "string"
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Developer resources. https://aisecuritydirectory.com/for-agents"
              }
            }
          }
        }
      }
    },
    "headers": {
      "RateLimit": {
        "description": "RFC RateLimit remaining. Example: \"catalog\";r=120;t=60",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitPolicy": {
        "description": "RFC RateLimit-Policy. Example: \"catalog\";q=120;w=60",
        "schema": {
          "type": "string"
        }
      },
      "XRateLimitLimit": {
        "description": "Advertised catalog quota per window.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitRemaining": {
        "description": "Advertised remaining requests in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitReset": {
        "description": "Unix seconds when the advertised window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying after HTTP 429.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid query. GET /api/search returns this when limit is present and is not an integer in 1..100.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "NotFound": {
        "description": "Unknown /api path. Catch-all JSON 404; catalog routes themselves are not 404 for empty results.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Stay under the advertised catalog quota of 120 per 60 seconds.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "Internal": {
        "description": "Unexpected server error; safe to retry with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Wrong HTTP method. Catalog GET-only endpoints accept GET, HEAD, OPTIONS.",
        "headers": {
          "Allow": {
            "description": "GET, HEAD, OPTIONS",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    }
  }
}
