{
  "openapi": "3.0.0",
  "info": {
    "title": "Beecargo Agent API",
    "description": "File hosting API for AI agents and integrators.",
    "version": "1.0.0"
  },
  "servers": [{ "url": "https://api.beecargo.net" }],
  "paths": {
    "/agent/capabilities": {
      "get": {
        "summary": "Agent capability discovery",
        "tags": ["Agent"],
        "responses": {
          "200": {
            "description": "Capabilities JSON",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentCapabilities" }
              }
            }
          }
        }
      }
    },
    "/agent/register/challenge": {
      "post": {
        "summary": "Issue a bootstrap register proof-of-work challenge",
        "description": "Public. Rate-limited per IP. Returns a short-lived challenge bound to the client IP. Official MCP/CLI solve this automatically.",
        "tags": ["Agent"],
        "responses": {
          "200": {
            "description": "Challenge issued",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentRegisterChallenge" }
              }
            }
          },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/agent/register": {
      "post": {
        "summary": "Self-register and get an agent API key",
        "description": "Public. Requires a solved proof-of-work from POST /agent/register/challenge. Rate-limited per IP (about 5/hour). Returns free-tier agent quotas. The key starts with bc_ and is shown once; store it securely. Prefer beecargo_register_agent or the CLI.",
        "tags": ["Agent"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AgentRegisterRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Machine key created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentRegisterResponse" }
              }
            }
          },
          "400": { "description": "Missing or invalid proof-of-work" },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Health check",
        "tags": ["System"]
      }
    },
    "/files/upload": {
      "post": {
        "summary": "Direct file upload (under 4MB)",
        "description": "Optional publish fields: visibility, direct, retention, expiresAt, ttl, once, maxDownloads, protect, handoffMessage, runId, openShare, shareShortId. Send Idempotency-Key for safe retries. Machine downloads wait for the safety check.",
        "tags": ["Files"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string" },
            "description": "Safe retries: same key + same body returns the first result"
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": { "$ref": "#/components/schemas/FileUploadRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload success",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FileUploadResponse" }
              }
            }
          }
        }
      }
    },
    "/files/remote-upload": {
      "post": {
        "summary": "Import file from public URL",
        "description": "Same publish fields as direct upload (including openShare / shareShortId). Send Idempotency-Key for safe retries.",
        "tags": ["Files"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RemoteUploadRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import success",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FileUploadResponse" }
              }
            }
          }
        }
      }
    },
    "/files/claim": {
      "post": {
        "summary": "Claim anonymous upload to API key owner",
        "tags": ["Files"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ClaimFileRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File claimed",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ClaimFileResponse" }
              }
            }
          }
        }
      }
    },
    "/files/share/{shortId}": {
      "get": {
        "summary": "Share metadata including safety-check status",
        "description": "Public share metadata. Poll scanStatus until clean before machine download. Share link works immediately; downloads wait for the safety check.",
        "tags": ["Files"],
        "parameters": [
          {
            "name": "shortId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Public share code from upload (also used in /d/{shortId})"
          }
        ],
        "responses": {
          "200": {
            "description": "Share metadata",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ShareMetaResponse" }
              }
            }
          },
          "404": { "description": "Share not found or expired" }
        }
      }
    },
    "/files/download/{fileId}": {
      "get": {
        "summary": "Get signed download URL",
        "description": "Returns a short-lived signed URL when the safety check has finished (scanStatus clean). If still checking, the body includes scanPending and retryAfterSeconds — wait and retry.",
        "tags": ["Files"],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "unlockCode",
            "in": "query",
            "required": false,
            "description": "Required when the share has unlock protection (unless unlockToken is provided)",
            "schema": { "type": "string" }
          },
          {
            "name": "unlockToken",
            "in": "query",
            "required": false,
            "description": "Short-lived token from POST /downloads/unlock",
            "schema": { "type": "string" }
          },
          {
            "name": "handoffToken",
            "in": "query",
            "required": false,
            "description": "Private token from /h/{token}; alternative to unlockCode",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed download URL when the safety check is clean",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DownloadUrlResponse" }
              }
            }
          },
          "409": {
            "description": "Safety check still running. Public envelope: scanPending + retryAfterSeconds (~15). Machine/V1 style may use errorCode SCAN_PENDING (or error.code).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DownloadScanPendingError" }
              }
            }
          },
          "404": {
            "description": "File not found, expired, or unavailable after a failed safety check (scanBlocked / SCAN_BLOCKED)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DownloadScanBlockedError" }
              }
            }
          }
        }
      }
    },
    "/downloads/unlock": {
      "post": {
        "summary": "Verify unlock code or handoff token",
        "description": "Body: { shortId|fileId, unlockCode } or { handoffToken }. Returns unlockToken for later download calls.",
        "tags": ["Downloads"]
      }
    },
    "/files/handoff/{token}": {
      "get": {
        "summary": "Delivery link metadata",
        "description": "Public meta for /h/{token}: shortId, name, message. Does not return file bytes.",
        "tags": ["Files"],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ]
      }
    },
    "/files/share-settings": {
      "patch": {
        "summary": "Update share settings",
        "description": "Body: fileId and/or shortId (growable Shipment or one-file share), plus visibility, direct, retention, expiresAt, extendTtl, protect, handoffMessage, immutable, upstreamFileIds. Returns unlockCode and handoffUrl once when protect is enabled.",
        "tags": ["Files"],
        "security": [{ "bearerAuth": [] }]
      }
    },
    "/files/link-protection": {
      "patch": {
        "summary": "Protect an anonymous upload",
        "description": "Body: { fileId, claimToken, protect, handoffMessage? }. Returns unlockCode and handoffUrl once when protection is enabled.",
        "tags": ["Files"]
      }
    },
    "/files/info": {
      "get": {
        "summary": "Batch file metadata by short codes",
        "tags": ["Files"]
      }
    },
    "/files/list": {
      "get": {
        "summary": "List owned files",
        "description": "List owned files and optional sibling folders. Pass runId to list only files uploaded with that pipeline id (same payload as GET /files/run/{runId}).",
        "tags": ["Files"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "runId",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "maxLength": 120 },
            "description": "When set, return artifacts for this run id (ignores folder/page filters)"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "folderId",
            "in": "query",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "name": "includeFolders",
            "in": "query",
            "required": false,
            "schema": { "type": "boolean", "default": true }
          }
        ]
      }
    },
    "/files/run/{runId}": {
      "get": {
        "summary": "List files uploaded under a run id",
        "description": "Alias of GET /files/list?runId=. Prefer the list query form. Used by MCP beecargo_list_files when runId is set.",
        "tags": ["Files"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "maxLength": 120 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ]
      }
    },
    "/connect": {
      "post": {
        "summary": "Seller Stripe Connect",
        "description": "Unified Connect entry for paid-share sellers. action=status checks readyToSell; action=onboard mints Express onboarding URL; action=login mints Express dashboard login. Requires dashboard API key or OAuth (not agent bootstrap keys). Legacy aliases: GET /connect/status, POST /connect/onboard, POST /connect/login-link.",
        "tags": ["Connect"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["action"],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": ["status", "onboard", "login"]
                  },
                  "sync": {
                    "type": "boolean",
                    "description": "status only: refresh flags from Stripe (default true)"
                  },
                  "country": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 2,
                    "description": "onboard only: ISO country for new Express accounts"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/files/upload-delegations": {
      "post": {
        "summary": "Create short-lived upload delegation (advanced)",
        "description": "Create a short-lived upload URL and delegation token for a worker that must not hold your full API key. Prefer normal upload for most agents.",
        "tags": ["Files"],
        "security": [{ "bearerAuth": [] }]
      }
    },
    "/files/upload-delegations/complete": {
      "post": {
        "summary": "Complete an upload delegation",
        "tags": ["Files"]
      }
    },
    "/files/upload-delegations/multipart/batch-urls": {
      "post": {
        "summary": "Presigned part URLs for a delegated multipart upload",
        "tags": ["Files"]
      }
    },
    "/files/upload-delegations/multipart/complete": {
      "post": {
        "summary": "Complete a delegated multipart upload",
        "tags": ["Files"]
      }
    },
    "/files/delete": {
      "delete": {
        "summary": "Delete file (API key or deletion token)",
        "tags": ["Files"]
      }
    },
    "/files/multipart/init": {
      "post": {
        "summary": "Start multipart upload",
        "tags": ["Files"]
      }
    },
    "/files/multipart/batch-urls": {
      "post": {
        "summary": "Presigned multipart part URLs",
        "tags": ["Files"]
      }
    },
    "/files/multipart/complete": {
      "post": {
        "summary": "Complete multipart upload",
        "tags": ["Files"]
      }
    },
    "/files/multipart/abort": {
      "post": {
        "summary": "Abort multipart upload",
        "tags": ["Files"]
      }
    },
    "/files/remote-multipart/init": {
      "post": {
        "summary": "Start async remote import job",
        "tags": ["Files"]
      }
    },
    "/files/remote-multipart/{jobId}": {
      "get": {
        "summary": "Remote import job status (bytesDone/percent; sharePath when done)",
        "tags": ["Files"]
      }
    },
    "/files/remote-multipart/{jobId}/events": {
      "get": {
        "summary": "SSE progress stream for remote import job",
        "tags": ["Files"]
      }
    },
    "/folders": {
      "post": {
        "summary": "Create folder",
        "tags": ["Folders"],
        "security": [{ "bearerAuth": [] }]
      }
    },
    "/folders/list": {
      "get": {
        "summary": "List folders",
        "tags": ["Folders"],
        "security": [{ "bearerAuth": [] }]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key (Authorization: Bearer bc_…) or dashboard session token"
      }
    },
    "schemas": {
      "AgentRegisterChallenge": {
        "type": "object",
        "required": ["challenge_id", "difficulty", "expires_at"],
        "properties": {
          "challenge_id": { "type": "string" },
          "difficulty": {
            "type": "integer",
            "description": "Leading zero bits required for SHA-256(challenge_id + ':' + nonce)"
          },
          "expires_at": { "type": "string", "format": "date-time" }
        }
      },
      "AgentRegisterRequest": {
        "type": "object",
        "required": ["challenge_id", "nonce"],
        "properties": {
          "label": { "type": "string", "maxLength": 120 },
          "challenge_id": { "type": "string" },
          "nonce": { "type": "string" }
        }
      },
      "AgentRegisterResponse": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "One-time API key secret (starts with bc_)"
          },
          "owner_id": { "type": "string", "format": "uuid" },
          "key_prefix": { "type": "string" },
          "scope": { "type": "string", "enum": ["files_write", "files_read", "full"] },
          "tier": { "type": "string", "enum": ["bootstrap", "premium"] },
          "note": { "type": "string" }
        }
      },
      "AgentCapabilities": {
        "type": "object",
        "description": "Discovery payload from GET /agent/capabilities"
      },
      "PublishFields": {
        "type": "object",
        "properties": {
          "visibility": { "type": "string", "enum": ["unlisted", "public"] },
          "direct": {
            "type": "boolean",
            "description": "Pro: start download when the /d share opens"
          },
          "retention": { "type": "string", "enum": ["ttl", "forever"] },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Exact expiry when retention is ttl"
          },
          "ttl": {
            "type": "string",
            "description": "Keep-time preset from now, such as 1h, 24h, or 7d"
          },
          "once": {
            "type": "boolean",
            "description": "When true, the share can be downloaded only once"
          },
          "maxDownloads": {
            "type": "integer",
            "minimum": 1,
            "description": "Max downloads (ignored when once is true)"
          },
          "protect": {
            "type": "boolean",
            "description": "Create unlock code and private delivery link"
          },
          "handoffMessage": {
            "type": "string",
            "maxLength": 480,
            "description": "Optional delivery-link note"
          },
          "runId": {
            "type": "string",
            "maxLength": 120,
            "description": "Optional pipeline id; list later via GET /files/list?runId="
          },
          "openShare": {
            "type": "boolean",
            "description": "Authenticated: open a growable multi-file Shipment; response shortId is the share link. Use shareShortId on later uploads."
          },
          "shareShortId": {
            "type": "string",
            "minLength": 4,
            "maxLength": 16,
            "description": "Authenticated: attach this upload to an existing growable Shipment from a prior openShare"
          }
        }
      },
      "FileUploadRequest": {
        "allOf": [
          {
            "type": "object",
            "required": ["file"],
            "properties": {
              "file": { "type": "string", "format": "binary" },
              "folderId": { "type": "string", "format": "uuid" }
            }
          },
          { "$ref": "#/components/schemas/PublishFields" }
        ]
      },
      "RemoteUploadRequest": {
        "allOf": [
          {
            "type": "object",
            "required": ["url"],
            "properties": {
              "url": { "type": "string", "format": "uri" },
              "folderId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              }
            }
          },
          { "$ref": "#/components/schemas/PublishFields" }
        ]
      },
      "ClaimFileRequest": {
        "type": "object",
        "required": ["fileId", "claimToken"],
        "properties": {
          "fileId": { "type": "string", "minLength": 8 },
          "claimToken": { "type": "string", "minLength": 8 }
        }
      },
      "ClaimFileResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "data": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "shortCode": { "type": "string" }
            }
          }
        }
      },
      "FileUploadResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "data": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "shortCode": { "type": "string" },
              "shareUrl": { "type": "string", "format": "uri" },
              "claimToken": { "type": "string" },
              "deletionToken": { "type": "string" }
            }
          }
        }
      },
      "ShareMetaResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "fileId" },
          "short_id": { "type": "string" },
          "name": { "type": "string" },
          "size_bytes": { "type": "integer" },
          "expires_at": { "type": "string", "format": "date-time", "nullable": true },
          "scanStatus": {
            "type": "string",
            "enum": ["pending", "scanning", "clean", "unavailable"],
            "description": "Safety check status. Download only when clean."
          },
          "unlockRequired": {
            "type": "boolean",
            "description": "True when an unlock code or delivery link is required"
          },
          "content_type": { "type": "string", "nullable": true },
          "directEffective": { "type": "boolean" },
          "sponsoredDelivery": { "type": "boolean" }
        }
      },
      "DownloadUrlResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Signed download URL when ready"
          }
        }
      },
      "DownloadScanPendingError": {
        "type": "object",
        "description": "Not ready yet. Prefer scanning for scanPending or errorCode SCAN_PENDING.",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": { "type": "string" },
          "scanPending": {
            "type": "boolean",
            "description": "True when the safety check is still running (public envelope)"
          },
          "retryAfterSeconds": {
            "type": "integer",
            "example": 15,
            "description": "Suggested wait before retry"
          },
          "errorCode": {
            "type": "string",
            "enum": ["SCAN_PENDING"],
            "description": "Machine/V1-style code when present"
          }
        }
      },
      "DownloadScanBlockedError": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": { "type": "string" },
          "scanBlocked": {
            "type": "boolean",
            "description": "True when the file is unavailable after the safety check"
          },
          "errorCode": {
            "type": "string",
            "enum": ["SCAN_BLOCKED"],
            "description": "Machine/V1-style code when present"
          }
        }
      }
    }
  }
}
