{
  "openapi": "3.1.0",
  "info": {
    "title": "Grepticon API",
    "version": "1.0.0",
    "description": "The Grepticon `/v1` REST surface: a hosted, read-only virtual filesystem for AI agents. A management plane (workspaces, files, tokens, audit) is driven with a `grp_sk_` key; agents read with `ls`/`find`/`cat`/`grep` using either the key or a scoped `grp_at_` token."
  },
  "servers": [
    {
      "url": "https://api.grepticon.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "workspaces",
      "description": "Create, list, and delete the isolated, permissioned workspaces your agents read from. Each workspace is its own virtual filesystem."
    },
    {
      "name": "files",
      "description": "Upload, delete, and list the files in a workspace. Uploads are ingested asynchronously, so every file carries a status you can poll."
    },
    {
      "name": "tokens",
      "description": "Mint and revoke the scoped, read-only tokens an agent reads with. Each token is bound to one workspace and carries its own claims."
    },
    {
      "name": "audit",
      "description": "Export the append-only audit log recording every read an agent made against a workspace."
    },
    {
      "name": "read",
      "description": "The four read tools an agent calls against a workspace: ls, find, cat, and grep. Plain text in, plain text out, with no query language."
    },
    {
      "name": "health",
      "description": "Liveness and readiness probes (no auth)."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/workspaces": {
      "post": {
        "tags": [
          "workspaces"
        ],
        "summary": "Create a workspace",
        "operationId": "createWorkspace",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/workspaceCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Workspace created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/workspaceCreatedBody"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed workspace name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "403": {
            "description": "Workspace quota reached for the plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "409": {
            "description": "A workspace with that name already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "workspaces"
        ],
        "summary": "List workspaces",
        "operationId": "listWorkspaces",
        "responses": {
          "200": {
            "description": "The account’s workspaces.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/workspaceListBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}": {
      "delete": {
        "tags": [
          "workspaces"
        ],
        "summary": "Delete a workspace",
        "operationId": "deleteWorkspace",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Workspace deleted."
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/files/{path}": {
      "put": {
        "tags": [
          "files"
        ],
        "summary": "Upload a file",
        "operationId": "uploadFile",
        "description": "Upload raw file bytes. Returns 202 immediately; ingestion (text extraction + indexing) runs asynchronously, so a freshly uploaded file is `pending` until it becomes `ready` (or `error`). Re-uploading the same path self-heals a failed ingest.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "File path within the workspace. May contain \"/\" — the entire trailing segment is the path (e.g. `src/index.ts`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visibility",
            "in": "query",
            "required": false,
            "description": "Comma-separated visibility claims controlling which scoped tokens can see the file, e.g. `?visibility=user:alice,group:eng`. Omit to make the file visible to every token (`*`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Raw file bytes. The `Content-Type` header is stored and echoed back on reads (default `application/octet-stream`).",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Upload accepted; ingestion enqueued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/uploadAckBody"
                }
              }
            }
          },
          "400": {
            "description": "Undecodable or invalid file path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "403": {
            "description": "File-count or account-storage limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "413": {
            "description": "File exceeds the per-file byte limit for the plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "503": {
            "description": "Blob storage or ingest enqueue failed; retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "files"
        ],
        "summary": "Delete a file",
        "operationId": "deleteFile",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "File path within the workspace. May contain \"/\" — the entire trailing segment is the path (e.g. `src/index.ts`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "File deleted."
          },
          "400": {
            "description": "Undecodable or invalid file path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace or file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/files": {
      "get": {
        "tags": [
          "files"
        ],
        "summary": "List files",
        "operationId": "listFiles",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by ingest status.",
            "schema": {
              "$ref": "#/components/schemas/fileStatus"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Pagination cursor; pass the previous response’s `nextCursor`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max files to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of files.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/fileListBody"
                }
              }
            }
          },
          "400": {
            "description": "Invalid status filter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/tokens": {
      "post": {
        "tags": [
          "tokens"
        ],
        "summary": "Mint a scoped read token",
        "operationId": "mintToken",
        "description": "Mint a `grp_at_` read-only token pinned to this workspace. The plaintext token is returned once and never again. Tokens are mint/revoke-only — there is no list endpoint.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Optional claims (visibility scope, default `[\"*\"]`) and TTL in seconds (60–86400, default 3600).",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/tokenMintBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Token minted (plaintext shown once).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/tokenMintedBody"
                }
              }
            }
          },
          "400": {
            "description": "Malformed claims or ttlSeconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tokens/{id}": {
      "delete": {
        "tags": [
          "tokens"
        ],
        "summary": "Revoke a token",
        "operationId": "revokeToken",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The token id (uuid) returned when the token was minted.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Token revoked (idempotent)."
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "Unknown, foreign, or malformed token id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/audit": {
      "get": {
        "tags": [
          "audit"
        ],
        "summary": "Export the audit log",
        "operationId": "exportAudit",
        "description": "Keyset-paginated export of read events (tool, actor, params summary, result count, latency) for the workspace.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "ISO 8601 timestamp; return only events strictly after it.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Pagination cursor; pass the previous response’s `nextCursor`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max events to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of audit events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auditExportBody"
                }
              }
            }
          },
          "400": {
            "description": "Invalid `after` timestamp or `cursor`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/errorBody"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/ls": {
      "get": {
        "tags": [
          "read"
        ],
        "summary": "List a directory (ls)",
        "operationId": "ls",
        "description": "List the immediate children of a directory in the workspace filesystem.\n\n- path: directory to list (default \"/\", the workspace root). Also accepts a file path, which returns that file's entry.\n- Output: directories first (with a trailing \"/\"), then files with their text sizes.\n- Directory names map the topics: ls \"/\" first to learn the layout, then ls into the directory most related to the question — it often names the exact file you need.\n- Shows at most 1,000 entries; if truncated, use find with a pattern to narrow.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "path",
            "in": "query",
            "required": false,
            "description": "Directory to list (default \"/\").",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool output as plain text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter (the message names it).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace, or the token is pinned elsewhere.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit reached for the credential or account; retry after `Retry-After`. Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/find": {
      "get": {
        "tags": [
          "read"
        ],
        "summary": "Find files by glob (find)",
        "operationId": "find",
        "description": "Find files by glob pattern over workspace file paths.\n\n- pattern: a glob like \"**/*.md\" or \"reports/**/q3*\". A bare name matches literally and exactly — for a substring match use \"**/*name*\".\n- path: optional directory to search under (default \"/\"); the pattern is matched relative to it.\n- head_limit: max results (default 100, up to 1,000).\n- Output: one \"/\"-prefixed path per line, most recently updated first. Use ls to explore directory structure instead of guessing patterns.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "pattern",
            "in": "query",
            "required": true,
            "description": "Glob over file paths, e.g. `**/*.md`.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "path",
            "in": "query",
            "required": false,
            "description": "Directory to search under (default \"/\").",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "head_limit",
            "in": "query",
            "required": false,
            "description": "Max results (default 100, up to 1000).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool output as plain text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter (the message names it).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace, or the token is pinned elsewhere.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit reached for the credential or account; retry after `Retry-After`. Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/cat": {
      "get": {
        "tags": [
          "read"
        ],
        "summary": "Read a file (cat)",
        "operationId": "cat",
        "description": "Read a file's text content with line numbers (like cat -n).\n\n- path: the file to read.\n- offset: 1-based line to start from (default 1). limit: max lines to return (default 2,000).\n- Line numbers compose with grep output: grep shows \"path:512:…\", so cat(path, offset=500) reads around that match.\n- Large responses are truncated with a footer telling you the offset to continue from; very long lines are cut with a marker. Binary originals (like PDFs) are served as their extracted text.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "path",
            "in": "query",
            "required": true,
            "description": "File to read.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "1-based line to start from (default 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 9007199254740991
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max lines to return (default 2000).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 9007199254740991
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool output as plain text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter (the message names it).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace, or the token is pinned elsewhere.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit reached for the credential or account; retry after `Retry-After`. Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces/{ws}/grep": {
      "get": {
        "tags": [
          "read"
        ],
        "summary": "Search file contents (grep)",
        "operationId": "grep",
        "description": "Search file contents with a regular expression.\n\n- pattern: an RE2 regex. Backreferences and lookaround are not supported (same family as ripgrep); everything else — literals, classes, alternation, quantifiers, anchors — works.\n- The documents rarely use your question's wording. Search short stems and domain synonyms, not question phrases: for \"how fast do they stop\" grep \"halt|brake|stop\", not \"how fast\". One or two words per attempt beats a long phrase.\n- path: optional directory to search under. glob: optional filename filter like \"*.md\".\n- output_mode: \"files_with_matches\" (default — matching file paths, densest first; start here to find the right FILE, then cat it) or \"content\" (matching lines as \"path:line:text\", with -A/-B/-C context — raw matches can include boilerplate from unrelated files) or \"count\" (matches per file).\n- -i: case-insensitive — use it by default. head_limit: cap the output.\n- Zero matches? Reword and try again — synonyms, a shorter stem, no glob. Several cheap greps with different wordings beat one perfect pattern.",
        "parameters": [
          {
            "name": "ws",
            "in": "path",
            "required": true,
            "description": "Workspace name. Workspace name must be 1-64 lowercase letters, digits, hyphens, or underscores, starting and ending with a letter or digit.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
            }
          },
          {
            "name": "pattern",
            "in": "query",
            "required": true,
            "description": "An RE2 regular expression.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "path",
            "in": "query",
            "required": false,
            "description": "Directory to search under.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "glob",
            "in": "query",
            "required": false,
            "description": "Filename filter, e.g. `*.md`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "output_mode",
            "in": "query",
            "required": false,
            "description": "`files_with_matches` (default), `content`, or `count`.",
            "schema": {
              "default": "files_with_matches",
              "type": "string",
              "enum": [
                "files_with_matches",
                "content",
                "count"
              ]
            }
          },
          {
            "name": "-i",
            "in": "query",
            "required": false,
            "description": "Case-insensitive.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "-A",
            "in": "query",
            "required": false,
            "description": "Lines of context after each match.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            }
          },
          {
            "name": "-B",
            "in": "query",
            "required": false,
            "description": "Lines of context before each match.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            }
          },
          {
            "name": "-C",
            "in": "query",
            "required": false,
            "description": "Lines of context around each match.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            }
          },
          {
            "name": "head_limit",
            "in": "query",
            "required": false,
            "description": "Cap the number of output lines.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 9007199254740991
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool output as plain text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter (the message names it).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or unrecognized bearer credential.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such workspace, or the token is pinned elsewhere.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit reached for the credential or account; retry after `Retry-After`. Too many failed authentication attempts from this address; retry after `Retry-After`.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Liveness probe",
        "operationId": "healthz",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up (`ok`).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/readyz": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Readiness probe",
        "operationId": "readyz",
        "description": "Checks both database pools (owner + app role). `503` when either is unavailable.",
        "security": [],
        "responses": {
          "200": {
            "description": "Ready.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/readyBody"
                }
              }
            }
          },
          "503": {
            "description": "Not ready.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/readyBody"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "errorBody": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "fileStatus": {
        "type": "string",
        "enum": [
          "pending",
          "ready",
          "error"
        ]
      },
      "workspaceCreateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "workspaceCreatedBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "workspaceListBody": {
        "type": "object",
        "properties": {
          "workspaces": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "version": {
                  "type": "number"
                },
                "createdAt": {
                  "type": "string"
                }
              },
              "required": [
                "name",
                "version",
                "createdAt"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "workspaces"
        ],
        "additionalProperties": false
      },
      "uploadAckBody": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "ready",
              "error"
            ]
          }
        },
        "required": [
          "path",
          "status"
        ],
        "additionalProperties": false
      },
      "fileListBody": {
        "type": "object",
        "properties": {
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "ready",
                    "error"
                  ]
                },
                "sizeBytes": {
                  "type": "number"
                },
                "contentType": {
                  "type": "string"
                },
                "updatedAt": {
                  "type": "string"
                },
                "errorDetail": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "path",
                "status",
                "sizeBytes",
                "contentType",
                "updatedAt",
                "errorDetail"
              ],
              "additionalProperties": false
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "files",
          "nextCursor"
        ],
        "additionalProperties": false
      },
      "tokenMintBody": {
        "type": "object",
        "properties": {
          "claims": {
            "minItems": 1,
            "maxItems": 64,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "ttlSeconds": {
            "type": "integer",
            "minimum": 60,
            "maximum": 86400
          }
        },
        "additionalProperties": false
      },
      "tokenMintedBody": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "token": {
            "type": "string"
          },
          "claims": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "expiresAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "token",
          "claims",
          "expiresAt"
        ],
        "additionalProperties": false
      },
      "auditExportBody": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "number"
                },
                "actorType": {
                  "type": "string"
                },
                "actorId": {
                  "type": "string"
                },
                "tool": {
                  "type": "string"
                },
                "params": {},
                "resultCount": {
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "latencyMs": {
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "ts": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "actorType",
                "actorId",
                "tool",
                "params",
                "resultCount",
                "latencyMs",
                "ts"
              ],
              "additionalProperties": false
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "events",
          "nextCursor"
        ],
        "additionalProperties": false
      },
      "readyBody": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "HTTP bearer credential in the `Authorization` header. Two planes share the\nscheme, distinguished by prefix:\n\n- `grp_sk_…` — a **management key**: full control of the account\n  (workspaces, files, token minting, audit export) plus the read tools.\n- `grp_at_…` — a **scoped read-only token**: minted per workspace, it may\n  call only the read tools (`ls`/`find`/`cat`/`grep`) and is rejected by\n  every management route."
      }
    }
  }
}
