{
  "openapi": "3.1.0",
  "info": {
    "title": "yakbin",
    "version": "2",
    "description": "Pipe text, logs and files to a URL, and read the right slice of them back. Content is stored exactly as sent: possible secrets are reported, and only masked if you ask (redact=1) or refused (strict=1). Every paste has a SHA-256. Agents can also use the MCP endpoint at /mcp."
  },
  "servers": [{ "url": "/" }],
  "components": {
    "securitySchemes": {
      "bearer": { "type": "http", "scheme": "bearer", "description": "An API key (yk_...), created with POST /api/keys, or a legacy account ID. Optional for anonymous pastes." }
    },
    "schemas": {
      "Finding": {
        "type": "object",
        "description": "A possible secret. Never contains the value.",
        "properties": { "type": { "type": "string" }, "line": { "type": "integer" } }
      },
      "Error": { "type": "object", "properties": { "error": { "type": "string" } } }
    }
  },
  "paths": {
    "/": {
      "post": {
        "summary": "Store the request body as a paste",
        "description": "Plain text response: optional notices, then the URL on its own line. Anonymous unless an Authorization header is sent.",
        "security": [{}, { "bearer": [] }],
        "parameters": [
          { "name": "expires", "in": "query", "schema": { "type": "string", "enum": ["5m", "30m", "1h", "12h", "1d", "3d", "7d", "30d"] } },
          { "name": "desc", "in": "query", "schema": { "type": "string", "maxLength": 100 }, "description": "Label. Accounts only." },
          { "name": "redact", "in": "query", "schema": { "type": "boolean" }, "description": "Mask detected secrets before storing." },
          { "name": "strict", "in": "query", "schema": { "type": "boolean" }, "description": "Refuse (HTTP 422) if any secret is detected; nothing is stored." },
          { "name": "private", "in": "query", "schema": { "type": "boolean" }, "description": "Readable only by the owner or through a signed link. Accounts only." },
          { "name": "collab", "in": "query", "schema": { "type": "boolean" }, "description": "Also return a collab-token that lets its holder comment." }
        ],
        "requestBody": { "required": true, "content": { "*/*": { "schema": { "type": "string", "format": "binary" } } } },
        "responses": {
          "201": { "description": "Created. Header X-Yakbin-Findings holds the number of possible secrets found." },
          "422": { "description": "strict=1 and a possible secret was found. Nothing was stored." },
          "429": { "description": "Rate limited. See Retry-After." }
        }
      }
    },
    "/api/paste": {
      "post": {
        "summary": "Store a paste from multipart form data; returns JSON",
        "security": [{}, { "bearer": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": { "type": "string" },
                  "file": { "type": "string", "format": "binary" },
                  "expires": { "type": "string" },
                  "desc": { "type": "string" },
                  "redact": { "type": "boolean" },
                  "strict": { "type": "boolean" },
                  "private": { "type": "boolean" },
                  "collab": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": { "201": { "description": "url, id, size, sha256, findings[], findings_count, redacted, private, collab_token" }, "422": { "description": "Refused by strict" } }
      }
    },
    "/{id}": {
      "get": {
        "summary": "Read a paste, optionally as a shaped view",
        "description": "Without view parameters the stored bytes are returned unchanged. Text is served as text/plain and never as HTML. Headers: X-Content-SHA256 (of the whole paste), and for views X-Total-Lines, X-Token-Estimate and X-Truncated.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "lines", "in": "query", "schema": { "type": "string" }, "description": "10-20, 10 or 10-" },
          { "name": "head", "in": "query", "schema": { "type": "integer" } },
          { "name": "tail", "in": "query", "schema": { "type": "integer" } },
          { "name": "grep", "in": "query", "schema": { "type": "string" }, "description": "RE2 regular expression" },
          { "name": "context", "in": "query", "schema": { "type": "integer", "maximum": 20 } },
          { "name": "numbers", "in": "query", "schema": { "type": "boolean" } },
          { "name": "view", "in": "query", "schema": { "type": "string", "enum": ["raw", "llm"] }, "description": "llm strips ANSI/progress noise and folds repeated lines" },
          { "name": "tokens", "in": "query", "schema": { "type": "integer" }, "description": "Approximate token budget; keeps head and tail" },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["json"] } },
          { "name": "t", "in": "query", "schema": { "type": "string" }, "description": "Signed link token for private pastes" }
        ],
        "responses": { "200": { "description": "Content" }, "404": { "description": "Missing, expired, deleted, or private without access" } }
      }
    },
    "/info/{id}": {
      "get": {
        "summary": "Paste metadata",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "id, filename, mime, size, sha256, expires_at, findings, redacted, private, collab" } }
      }
    },
    "/v/{id}": {
      "get": {
        "summary": "HTML viewer with line anchors (#L12, #L12-L20)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "HTML" } }
      }
    },
    "/api/scan": {
      "post": {
        "summary": "Check text for possible secrets without storing it",
        "requestBody": { "required": true, "content": { "text/plain": { "schema": { "type": "string" } } } },
        "responses": { "200": { "description": "clean, count, findings[]" } }
      }
    },
    "/api/bundle": {
      "post": {
        "summary": "Store several files under one link",
        "security": [{}, { "bearer": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["files"],
                "properties": {
                  "description": { "type": "string" },
                  "expires": { "type": "string" },
                  "private": { "type": "boolean" },
                  "redact": { "type": "boolean" },
                  "strict": { "type": "boolean" },
                  "files": { "type": "array", "maxItems": 50, "items": { "type": "object", "required": ["path", "content"], "properties": { "path": { "type": "string" }, "content": { "type": "string" } } } }
                }
              }
            },
            "multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "array", "items": { "type": "string", "format": "binary" } } } } }
          }
        },
        "responses": { "201": { "description": "id, url, llm_url, files[], expires_at" }, "422": { "description": "Refused by strict" } }
      }
    },
    "/b/{id}": {
      "get": {
        "summary": "Bundle manifest; ?view=llm returns every file in one document (?tokens=N to fit a budget); ?format=json for JSON",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "view", "in": "query", "schema": { "type": "string", "enum": ["llm"] } },
          { "name": "tokens", "in": "query", "schema": { "type": "integer" } },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["json"] } },
          { "name": "t", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Manifest or combined document" } }
      }
    },
    "/b/{id}/{path}": {
      "get": {
        "summary": "One file from a bundle (accepts the same view parameters as /{id})",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "path", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "File content" } }
      }
    },
    "/api/bundle/{id}": {
      "delete": { "summary": "Delete a bundle you own", "security": [{ "bearer": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "deleted" } } }
    },
    "/api/bundle/{id}/link": {
      "post": { "summary": "Signed, expiring read link for a bundle you own", "security": [{ "bearer": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "ttl", "in": "query", "schema": { "type": "string" } }], "responses": { "200": { "description": "url, expires_at" } } }
    },
    "/api/paste/{id}": {
      "delete": { "summary": "Delete a paste you own", "security": [{ "bearer": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "deleted" } } }
    },
    "/api/paste/{id}/link": {
      "post": { "summary": "Signed, expiring read link for a paste you own (works for private pastes)", "security": [{ "bearer": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "ttl", "in": "query", "schema": { "type": "string" } }], "responses": { "200": { "description": "url, expires_at" } } }
    },
    "/api/paste/{id}/comments": {
      "get": {
        "summary": "Comments on a paste; with wait=<seconds> (max 30) blocks until a new one arrives",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "after", "in": "query", "schema": { "type": "integer" }, "description": "Only comments with a greater ID; pass next_after from the previous response" },
          { "name": "wait", "in": "query", "schema": { "type": "integer", "maximum": 30 } }
        ],
        "responses": { "200": { "description": "comments[], next_after" } }
      },
      "post": {
        "summary": "Add a comment. Needs the owner's key or the paste's collab token (Authorization: Bearer yc_... or X-Collab-Token).",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["body"], "properties": { "body": { "type": "string" }, "line_start": { "type": "integer" }, "line_end": { "type": "integer" }, "author": { "type": "string" } } } } } },
        "responses": { "201": { "description": "The comment" } }
      }
    },
    "/api/keys": {
      "get": { "summary": "List your API keys", "security": [{ "bearer": [] }], "responses": { "200": { "description": "keys[]" } } },
      "post": {
        "summary": "Create a scoped API key. The token is shown once.",
        "security": [{ "bearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "scopes": { "type": "array", "items": { "type": "string", "enum": ["read", "write", "admin"] } }, "expires_in": { "type": "string", "description": "30m, 12h, 90d (max 365d)" } } } } } },
        "responses": { "201": { "description": "id, token, scopes, expires_at" } }
      }
    },
    "/api/keys/{id}": {
      "delete": { "summary": "Revoke an API key", "security": [{ "bearer": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "revoked" } } }
    },
    "/api/pastes": {
      "get": { "summary": "List your pastes", "security": [{ "bearer": [] }], "responses": { "200": { "description": "pastes[]" } } }
    },
    "/api/account/create": {
      "post": { "summary": "Create an account (no email). Rate limited.", "responses": { "201": { "description": "id, token" } } }
    },
    "/mcp": {
      "post": { "summary": "Model Context Protocol endpoint (Streamable HTTP, JSON-RPC). Tools: create_paste, read_paste, paste_info, scan_text, create_bundle, read_bundle, comment, get_comments, create_link, list_pastes, delete_paste.", "security": [{}, { "bearer": [] }], "responses": { "200": { "description": "JSON-RPC response" } } }
    }
  }
}
