{
  "openapi": "3.1.0",
  "info": {
    "title": "GRC Agents Directory public API",
    "version": "1.0.0",
    "summary": "Search and dump the live catalog of software agents that do GRC work.",
    "description": "Public JSON for grcagents.io. 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/agents.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": "GRC Agents Directory",
      "url": "https://grcagents.io/about"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://grcagents.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "catalog",
      "description": "Live directory listings. No auth."
    }
  ],
  "security": [],
  "paths": {
    "/api/agents.json": {
      "get": {
        "operationId": "listAgents",
        "tags": [
          "catalog"
        ],
        "summary": "Dump the live catalog",
        "description": "Returns every live listing. Envelope field is `services`. No authentication. Revalidated about every 60 seconds.",
        "responses": {
          "200": {
            "description": "Catalog dump",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentsDump"
                }
              }
            },
            "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": "searchAgents",
        "tags": [
          "catalog"
        ],
        "summary": "Search live listings",
        "description": "Filter the live catalog. Dimensions AND together. Repeated `job`, `framework`, `integration`, and `buyer` AND within that dimension (every value must match). `deployment` and `residency` are single-valued, case-insensitive exact match. `q` is a case-insensitive substring over name, summary, description, jobs, frameworks, and integrations. `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. There is no `total` field.",
        "parameters": [
          {
            "name": "job",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Agent job tags. Repeat or comma-separate. AND within the dimension."
          },
          {
            "name": "framework",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Framework tags. AND within the dimension."
          },
          {
            "name": "integration",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Integration tags. AND within the dimension."
          },
          {
            "name": "deployment",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single-valued exact match on deployment."
          },
          {
            "name": "residency",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single-valued exact match on data_residency."
          },
          {
            "name": "buyer",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Buyer tags. AND within the dimension."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring over name, summary, description, jobs, frameworks, integrations."
          },
          {
            "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/AgentsSearch"
                }
              }
            },
            "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": "searchAgentsV1",
        "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 `job`, `framework`, `integration`, and `buyer` AND within that dimension (every value must match). `deployment` and `residency` are single-valued, case-insensitive exact match. `q` is a case-insensitive substring over name, summary, description, jobs, frameworks, and integrations. `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. There is no `total` field.",
        "parameters": [
          {
            "name": "job",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Agent job tags. Repeat or comma-separate. AND within the dimension."
          },
          {
            "name": "framework",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Framework tags. AND within the dimension."
          },
          {
            "name": "integration",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Integration tags. AND within the dimension."
          },
          {
            "name": "deployment",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single-valued exact match on deployment."
          },
          {
            "name": "residency",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single-valued exact match on data_residency."
          },
          {
            "name": "buyer",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Buyer tags. AND within the dimension."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring over name, summary, description, jobs, frameworks, integrations."
          },
          {
            "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/AgentsSearch"
                }
              }
            },
            "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/agents.json": {
      "get": {
        "operationId": "listAgentsV1",
        "tags": [
          "catalog"
        ],
        "summary": "Dump the live catalog (v1)",
        "description": "Versioned alias of GET /api/agents.json. Same envelope. New integrations should call this path. Returns every live listing. Envelope field is `services`. No authentication. Revalidated about every 60 seconds.",
        "responses": {
          "200": {
            "description": "Catalog dump",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentsDump"
                }
              }
            },
            "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": {
      "AgentsDump": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "directory",
          "url",
          "lastUpdated",
          "count",
          "services"
        ],
        "properties": {
          "directory": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "lastUpdated": {
            "type": "string",
            "description": "Response date YYYY-MM-DD."
          },
          "count": {
            "type": "integer"
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicAgent"
            }
          }
        }
      },
      "AgentsSearch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "count",
          "normalized",
          "results"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "description": "Length of `results` (page size)."
          },
          "normalized": {
            "type": "object",
            "additionalProperties": true
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentSearchHit"
            }
          }
        }
      },
      "AgentSearchHit": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "slug",
          "brief_summary",
          "agent_job",
          "frameworks",
          "integrations",
          "deployment",
          "data_residency",
          "buyer",
          "url",
          "docs_url",
          "install_url",
          "mcp_package",
          "details",
          "is_house_product"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "brief_summary": {
            "type": "string"
          },
          "agent_job": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "frameworks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "integrations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "deployment": {
            "type": "string"
          },
          "data_residency": {
            "type": "string"
          },
          "buyer": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string"
          },
          "docs_url": {
            "type": "string"
          },
          "install_url": {
            "type": "string"
          },
          "mcp_package": {
            "type": "string"
          },
          "details": {
            "type": "string",
            "format": "uri"
          },
          "is_house_product": {
            "type": "boolean"
          }
        }
      },
      "PublicAgent": {
        "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"
          },
          "is_house_product": {
            "type": "boolean"
          }
        }
      },
      "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://grcagents.io/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"
            }
          }
        }
      }
    }
  }
}
