{"components":{"parameters":{"OrgSlug":{"description":"Organization slug","example":"my-org","in":"path","name":"org_slug","required":true,"schema":{"type":"string"}},"ProjectSlug":{"description":"Project slug","example":"my-project","in":"path","name":"project_slug","required":true,"schema":{"type":"string"}},"StoragePath":{"description":"Full chronological storage path","example":"2025/W03/reports/weekly/report.pdf","in":"path","name":"storage_path","required":true,"schema":{"type":"string"}}},"responses":{"Forbidden":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden \u2014 insufficient permissions"},"InternalError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Internal server error"},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Resource not found"},"Unauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized \u2014 missing or invalid credentials"}},"schemas":{"ApiKey":{"properties":{"expires_at":{"format":"date-time","nullable":true,"type":"string"},"id":{"type":"integer"},"is_active":{"type":"boolean"},"key_prefix":{"description":"First 12 characters of the key for identification","example":"parn_a1b2c3","type":"string"},"last_used_at":{"format":"date-time","nullable":true,"type":"string"},"name":{"description":"Human-readable name","type":"string"},"scopes":{"description":"Permission scopes","items":{"enum":["read","write","delete","admin"],"type":"string"},"type":"array"}},"type":"object"},"BillingPlan":{"properties":{"api_calls_limit":{"description":"API calls limit per billing cycle","type":"integer"},"api_calls_this_cycle":{"description":"API calls used in the current billing cycle","type":"integer"},"bandwidth_limit_bytes":{"description":"Bandwidth limit in bytes per billing cycle","type":"integer"},"bandwidth_this_cycle":{"description":"Bandwidth used in the current billing cycle (bytes)","type":"integer"},"billing_cycle_start":{"format":"date-time","nullable":true,"type":"string"},"created_at":{"format":"date-time","type":"string"},"id":{"type":"integer"},"plan_type":{"enum":["free","starter","pro","enterprise"],"type":"string"},"price_per_gb_month":{"description":"Price per GB per month in USD","format":"float","type":"number"},"storage_limit_bytes":{"description":"Storage limit in bytes","type":"integer"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"ChainAlert":{"description":"A chain sync alert for admin notification","properties":{"alert_type":{"description":"Type of alert","enum":["gas_depleted","repeated_failure","sync_error","lock_stuck"],"type":"string"},"created_at":{"description":"When the alert was created","format":"date-time","type":"string"},"id":{"description":"Alert entity ID","type":"string"},"is_resolved":{"description":"Whether the alert has been resolved","type":"boolean"},"message":{"description":"Human-readable alert message","type":"string"},"resolved_at":{"description":"When the alert was resolved","format":"date-time","nullable":true,"type":"string"},"resolved_by":{"description":"Who resolved the alert (user ID or API key ID)","nullable":true,"type":"string"}},"type":"object"},"ChainRecord":{"properties":{"blake3_hash":{"description":"Blake3 hash","type":"string"},"chain_error":{"description":"Error message if chain write failed","nullable":true,"type":"string"},"chain_written":{"description":"Whether the record has been written to chain","type":"boolean"},"chain_written_at":{"format":"date-time","nullable":true,"type":"string"},"channel_id":{"description":"Metarium channel ID","type":"string"},"file_size":{"description":"File size in bytes (for file_hash records)","nullable":true,"type":"integer"},"file_storage_path":{"description":"Storage path (for file_hash records)","nullable":true,"type":"string"},"id":{"type":"integer"},"index_week":{"description":"Week (for weekly_index records)","nullable":true,"type":"integer"},"index_year":{"description":"Year (for index records)","nullable":true,"type":"integer"},"kuri":{"description":"KURI string (blake3://<hash>)","type":"string"},"record_type":{"description":"Type of chain record","enum":["file_hash","weekly_index","yearly_index","project_index","org_manifest"],"type":"string"},"written_at":{"format":"date-time","type":"string"}},"type":"object"},"ChainStatus":{"description":"Chain sync status for an organization","properties":{"channel_id":{"description":"Metarium channel ID","example":"42","nullable":true,"type":"string"},"custodian_metadata_kuri":{"description":"Current custodian metadata KURI on chain","example":"blake3://abc123...","nullable":true,"type":"string"},"custodian_metadata_updated_at":{"description":"When custodian metadata was last updated","format":"date-time","nullable":true,"type":"string"},"failed_writes":{"description":"Number of failed chain writes with errors","type":"integer"},"has_uncommitted_changes":{"description":"Whether the org has changes not yet committed to chain","type":"boolean"},"last_synced_at":{"description":"When the last chain sync was performed","format":"date-time","nullable":true,"type":"string"},"pending_writes":{"description":"Number of pending (unwritten) chain records","type":"integer"},"uncommitted_since":{"description":"When uncommitted changes were first detected","format":"date-time","nullable":true,"type":"string"},"unresolved_alerts":{"description":"Number of unresolved chain alerts","type":"integer"}},"type":"object"},"Error":{"properties":{"error":{"description":"Error type identifier","example":"Bad request","type":"string"},"message":{"description":"Human-readable error message","example":"name and slug are required","type":"string"}},"required":["error","message"],"type":"object"},"ForceSyncResult":{"description":"Result of a force-sync or force-commit operation","properties":{"message":{"description":"Human-readable summary of the operation","type":"string"},"retry_results":{"description":"Results from the pending write retry phase","properties":{"alerts_created":{"type":"integer"},"failed":{"type":"integer"},"gas_error_halt":{"type":"boolean"},"succeeded":{"type":"integer"},"total_pending":{"type":"integer"}},"type":"object"},"status":{"description":"Whether the sync completed fully or was partial","enum":["partial","complete"],"type":"string"},"sync_results":{"description":"Results from the weekly sync phase (null if skipped)","nullable":true,"type":"object"}},"type":"object"},"McpTool":{"description":"MCP (Model Context Protocol) tool definition","properties":{"description":{"description":"Human-readable description of the tool","type":"string"},"inputSchema":{"description":"JSON Schema for the tool's input parameters","properties":{"properties":{"type":"object"},"required":{"items":{"type":"string"},"type":"array"},"type":{"example":"object","type":"string"}},"type":"object"},"name":{"description":"Tool name (matches operationId)","type":"string"}},"type":"object"},"OrgMember":{"properties":{"created_at":{"format":"date-time","type":"string"},"id":{"type":"integer"},"org_id":{"description":"Organization key (urlsafe)","type":"string"},"role":{"enum":["owner","admin","member"],"type":"string"},"user_id":{"description":"User key (urlsafe)","type":"string"}},"type":"object"},"Organization":{"properties":{"chain_channel_id":{"description":"Metarium channel ID for this org","nullable":true,"type":"string"},"chain_spec_hash":{"description":"Metarium chain spec hash","nullable":true,"type":"string"},"created_at":{"format":"date-time","type":"string"},"description":{"nullable":true,"type":"string"},"id":{"type":"integer"},"is_active":{"type":"boolean"},"name":{"type":"string"},"slug":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"PendingWrite":{"description":"A pending chain write record with error details","properties":{"chain_error":{"description":"Raw error message from chain write attempt","nullable":true,"type":"string"},"created_at":{"description":"When the record was created","format":"date-time","type":"string"},"error_code":{"description":"Classified error code","enum":["gas_error","duplicate_kuri","network_error","unknown_error"],"nullable":true,"type":"string"},"file_storage_path":{"description":"Storage path (for file_hash records)","nullable":true,"type":"string"},"kuri":{"description":"KURI string (blake3://<hash>)","type":"string"},"next_retry_at":{"description":"When the next retry is scheduled","format":"date-time","nullable":true,"type":"string"},"record_type":{"description":"Type of chain record","enum":["file_hash","weekly_index","yearly_index","project_index","org_manifest"],"type":"string"},"retry_count":{"description":"Number of retry attempts","type":"integer"}},"type":"object"},"Project":{"properties":{"created_at":{"format":"date-time","type":"string"},"description":{"nullable":true,"type":"string"},"file_count":{"description":"Total number of files","type":"integer"},"id":{"type":"integer"},"is_active":{"type":"boolean"},"name":{"type":"string"},"slug":{"type":"string"},"storage_bytes_used":{"description":"Total storage bytes consumed","type":"integer"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"StoredFile":{"properties":{"blake3_hash":{"description":"Blake3 hash of file contents","type":"string"},"chain_kuri":{"description":"KURI on chain (blake3://<hash>), null if not yet written","nullable":true,"type":"string"},"chain_written_at":{"description":"Timestamp when KURI was written to chain","format":"date-time","nullable":true,"type":"string"},"content_type":{"description":"MIME content type","example":"application/pdf","type":"string"},"created_at":{"format":"date-time","type":"string"},"file_path":{"description":"User-provided file path","type":"string"},"file_size":{"description":"File size in bytes","type":"integer"},"id":{"description":"Entity ID","type":"integer"},"is_deleted":{"description":"Whether the file has been soft-deleted","type":"boolean"},"storage_path":{"description":"Full chronological storage path (YYYY/W<WW>/filepath)","type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"UsageSummary":{"description":"Aggregated usage statistics for a date range","properties":{"total_api_calls":{"type":"integer"},"total_bytes_downloaded":{"type":"integer"},"total_bytes_uploaded":{"type":"integer"},"total_deletes":{"type":"integer"},"total_downloads":{"type":"integer"},"total_list_operations":{"type":"integer"},"total_uploads":{"type":"integer"}},"type":"object"}},"securitySchemes":{"BearerAuth":{"description":"API key authentication. Pass the key as a Bearer token. Key format: parn_ + 48 hex characters.\n","scheme":"bearer","type":"http"},"SessionAuth":{"description":"Cookie-based session authentication via OAuth login.","in":"cookie","name":"session","type":"apiKey"}}},"info":{"contact":{"name":"Parann Support","url":"https://parann.app"},"description":"Persistent file storage for AI agents with blockchain-backed provenance via Metarium.\n\nEvery file uploaded is blake3-hashed and its KURI (blake3://<hash>) is written to the Metarium blockchain. Files are organized chronologically by ISO year and week.\nWeekly cron jobs lock weekly indexes, hash them, and write the index KURI to chain, enabling independent verification and backup/sync.\n","license":{"name":"Proprietary"},"title":"Parann Storage API","version":"1.0.0"},"openapi":"3.0.3","paths":{"/docs":{"get":{"description":"Returns the full OpenAPI spec as JSON.","operationId":"get_openapi_spec","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OpenAPI JSON"}},"security":[],"summary":"OpenAPI specification","tags":["System"]}},"/docs/mcp":{"get":{"description":"Returns Model Context Protocol tool definitions derived from the OpenAPI spec. Each tool maps to one API operation.\n","operationId":"get_mcp_tools","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/McpTool"},"type":"array"}}},"description":"Array of MCP tool definitions"}},"security":[],"summary":"MCP tool definitions","tags":["System"]}},"/docs/ui":{"get":{"description":"Interactive API documentation powered by Swagger UI.","operationId":"get_swagger_ui","responses":{"200":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Swagger UI HTML page"}},"security":[],"summary":"Swagger UI","tags":["System"]}},"/health":{"get":{"description":"Returns API health status and version.","operationId":"health_check","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"service":{"example":"parann-storage","type":"string"},"status":{"example":"ok","type":"string"},"version":{"example":"1.0.0","type":"string"}},"type":"object"}}},"description":"Healthy"}},"security":[],"summary":"Health check","tags":["System"]}},"/orgs":{"get":{"description":"List all organizations the authenticated user belongs to.","operationId":"list_orgs","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"orgs":{"items":{"$ref":"#/components/schemas/Organization"},"type":"array"}},"type":"object"}}},"description":"List of organizations"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"SessionAuth":[]}],"summary":"List user's organizations","tags":["Organizations"]},"post":{"description":"Create a new organization. The authenticated user becomes the owner.","operationId":"create_org","requestBody":{"content":{"application/json":{"schema":{"properties":{"description":{"description":"Optional description","type":"string"},"name":{"description":"Display name","example":"My Organization","type":"string"},"slug":{"description":"URL-friendly identifier (unique)","example":"my-org","type":"string"}},"required":["name","slug"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"org":{"$ref":"#/components/schemas/Organization"}},"type":"object"}}},"description":"Organization created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request \u2014 missing fields or slug already taken"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"SessionAuth":[]}],"summary":"Create an organization","tags":["Organizations"]}},"/orgs/{org_slug}":{"delete":{"description":"Soft-deactivate an organization. Requires owner role.","operationId":"deactivate_org","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"message":{"example":"Organization deactivated","type":"string"}},"type":"object"}}},"description":"Organization deactivated"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"Deactivate organization","tags":["Organizations"]},"get":{"description":"Get details for an organization the user is a member of.","operationId":"get_org","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"org":{"$ref":"#/components/schemas/Organization"}},"type":"object"}}},"description":"Organization details"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Get organization details","tags":["Organizations"]},"put":{"description":"Update organization name and/or description. Requires admin role.","operationId":"update_org","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"description":{"type":"string"},"name":{"type":"string"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"org":{"$ref":"#/components/schemas/Organization"}},"type":"object"}}},"description":"Organization updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"Update organization","tags":["Organizations"]}},"/orgs/{org_slug}/chain":{"get":{"description":"Get blockchain integration details for the organization, including chain spec hash, channel ID, and total KURI count.\n","operationId":"get_chain_info","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"chain_channel_id":{"nullable":true,"type":"string"},"chain_spec_hash":{"nullable":true,"type":"string"},"chain_ws_url":{"description":"Always null (not exposed publicly)","nullable":true,"type":"string"},"last_synced_at":{"format":"date-time","nullable":true,"type":"string"},"total_kuris":{"description":"Total number of KURIs written for this org","type":"integer"}},"type":"object"}}},"description":"Chain information"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"Get blockchain chain info","tags":["Organizations"]}},"/orgs/{org_slug}/chain/alerts":{"get":{"description":"List chain alerts for the organization, including both resolved and unresolved alerts. Requires admin role.\n","operationId":"chain_alerts","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"alerts":{"items":{"$ref":"#/components/schemas/ChainAlert"},"type":"array"}},"type":"object"}}},"description":"List of chain alerts"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"List chain alerts","tags":["Organizations"]}},"/orgs/{org_slug}/chain/alerts/{alert_id}/resolve":{"post":{"description":"Mark a chain alert as resolved. Sets is_resolved=true, records the resolution timestamp and the resolving user. Requires admin role.\n","operationId":"resolve_chain_alert","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"description":"ID of the chain alert to resolve","in":"path","name":"alert_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"alert":{"$ref":"#/components/schemas/ChainAlert"},"message":{"example":"Alert resolved","type":"string"}},"type":"object"}}},"description":"Alert resolved"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Resolve a chain alert","tags":["Organizations"]}},"/orgs/{org_slug}/chain/force-commit":{"post":{"description":"Force commit ALL pending chain changes for this organization. Same as force-sync but ensures full cascade including custodian metadata update. Resets retry backoff, retries pending writes, then runs weekly sync with index and custodian metadata commits. Requires owner role.\n","operationId":"force_commit","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForceSyncResult"}}},"description":"Commit results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Force commit all pending changes","tags":["Organizations"]}},"/orgs/{org_slug}/chain/force-sync":{"post":{"description":"Trigger an immediate chain sync for this organization. Resets retry backoff on all pending writes, retries them, and if all succeed proceeds to run the full weekly sync (indexes + custodian metadata). If pending writes remain after retry, returns a partial result and does NOT proceed to index/custodian commits. Requires admin role.\n","operationId":"force_sync","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForceSyncResult"}}},"description":"Sync results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Force immediate chain sync","tags":["Organizations"]}},"/orgs/{org_slug}/chain/pending":{"get":{"description":"List all pending (unwritten) chain records for the organization, including error details, retry counts, and next retry times. Requires admin role.\n","operationId":"chain_pending","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"pending":{"items":{"$ref":"#/components/schemas/PendingWrite"},"type":"array"}},"type":"object"}}},"description":"List of pending chain writes"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"List pending chain writes","tags":["Organizations"]}},"/orgs/{org_slug}/chain/status":{"get":{"description":"Get the current chain sync status for the organization, including pending and failed write counts, uncommitted change tracking, and unresolved alert counts.\n","operationId":"chain_status","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainStatus"}}},"description":"Chain sync status"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Get chain sync status","tags":["Organizations"]}},"/orgs/{org_slug}/members":{"get":{"description":"List all members of the organization. Requires member role.","operationId":"list_members","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"members":{"items":{"$ref":"#/components/schemas/OrgMember"},"type":"array"}},"type":"object"}}},"description":"List of members"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"List organization members","tags":["Organizations"]},"post":{"description":"Invite a user by email with a specific role. Requires admin role.","operationId":"add_member","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"email":{"description":"Email of the user to add","format":"email","type":"string"},"role":{"description":"Role to assign","enum":["owner","admin","member"],"type":"string"}},"required":["email","role"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"member":{"$ref":"#/components/schemas/OrgMember"}},"type":"object"}}},"description":"Member added"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request \u2014 missing fields or invalid email"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"Add a member to the organization","tags":["Organizations"]}},"/orgs/{org_slug}/members/{user_id}":{"delete":{"description":"Remove a member from the organization. Requires admin role.","operationId":"remove_member","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"description":"ID of the user to remove","in":"path","name":"user_id","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"message":{"example":"Member removed","type":"string"}},"type":"object"}}},"description":"Member removed"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"Remove a member","tags":["Organizations"]}},"/orgs/{org_slug}/projects":{"get":{"description":"List all projects in the organization. Requires member role.","operationId":"list_projects","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"projects":{"items":{"$ref":"#/components/schemas/Project"},"type":"array"}},"type":"object"}}},"description":"List of projects"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"List projects","tags":["Projects"]},"post":{"description":"Create a new project within the organization. Requires admin role.","operationId":"create_project","parameters":[{"$ref":"#/components/parameters/OrgSlug"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"description":{"type":"string"},"name":{"example":"My Project","type":"string"},"slug":{"example":"my-project","type":"string"}},"required":["name","slug"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"project":{"$ref":"#/components/schemas/Project"}},"type":"object"}}},"description":"Project created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"SessionAuth":[]}],"summary":"Create a project","tags":["Projects"]}},"/orgs/{org_slug}/projects/{project_slug}":{"delete":{"description":"Soft-deactivate a project. Requires owner role.","operationId":"deactivate_project","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"message":{"example":"Project deactivated","type":"string"}},"type":"object"}}},"description":"Project deactivated"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Deactivate project","tags":["Projects"]},"get":{"description":"Get details for a specific project. Requires member role.","operationId":"get_project","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"project":{"$ref":"#/components/schemas/Project"}},"type":"object"}}},"description":"Project details"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Get project details","tags":["Projects"]},"put":{"description":"Update project name and/or description. Requires admin role.","operationId":"update_project","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"description":{"type":"string"},"name":{"type":"string"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"project":{"$ref":"#/components/schemas/Project"}},"type":"object"}}},"description":"Project updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Update project","tags":["Projects"]}},"/orgs/{org_slug}/projects/{project_slug}/api-keys":{"get":{"description":"List all API keys for the project. Requires admin role.","operationId":"list_api_keys","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"api_keys":{"items":{"$ref":"#/components/schemas/ApiKey"},"type":"array"}},"type":"object"}}},"description":"List of API keys"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"List API keys","tags":["Projects"]},"post":{"description":"Create a new API key for the project. The full key is returned only once in the response \u2014 store it securely. Requires admin role.\n","operationId":"create_api_key","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"expires_in_days":{"description":"Number of days until the key expires (omit for no expiration)","type":"integer"},"name":{"description":"Human-readable name for the key","example":"my-agent","type":"string"},"scopes":{"default":[],"description":"Permission scopes","items":{"enum":["read","write","delete","admin"],"type":"string"},"type":"array"}},"required":["name"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"api_key":{"properties":{"expires_at":{"format":"date-time","nullable":true,"type":"string"},"key":{"description":"Full API key (shown only once). Format: parn_ + 48 hex chars","example":"parn_a1b2c3d4e5f6...","type":"string"},"name":{"type":"string"},"prefix":{"description":"Key prefix for display","example":"parn_a1b2c3","type":"string"},"scopes":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"}}},"description":"API key created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request \u2014 missing name or invalid scopes"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Create an API key","tags":["Projects"]}},"/orgs/{org_slug}/projects/{project_slug}/api-keys/{key_id}":{"delete":{"description":"Revoke (deactivate) an API key. Requires admin role.","operationId":"revoke_api_key","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"},{"description":"ID of the API key to revoke","in":"path","name":"key_id","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"message":{"example":"API key revoked","type":"string"}},"type":"object"}}},"description":"API key revoked"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Revoke an API key","tags":["Projects"]}},"/orgs/{org_slug}/projects/{project_slug}/billing/plan":{"get":{"description":"Get the current billing plan and usage percentages for a project.","operationId":"get_plan","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"plan":{"$ref":"#/components/schemas/BillingPlan"},"usage_percentages":{"description":"Current usage as percentage of plan limits","properties":{"api_calls":{"format":"float","type":"number"},"bandwidth":{"format":"float","type":"number"},"storage":{"format":"float","type":"number"}},"type":"object"}},"type":"object"}}},"description":"Billing plan and usage"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Get billing plan","tags":["Billing"]},"put":{"description":"Initiate a plan upgrade via Stripe checkout. Returns a checkout URL to redirect the user to. Requires owner role.\n","operationId":"upgrade_plan","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"plan_type":{"description":"Target plan type","enum":["free","starter","pro","enterprise"],"type":"string"}},"required":["plan_type"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"checkout_url":{"description":"Stripe checkout URL to redirect the user to","format":"uri","type":"string"}},"type":"object"}}},"description":"Stripe checkout URL"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request \u2014 invalid plan type"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"SessionAuth":[]}],"summary":"Upgrade billing plan","tags":["Billing"]}},"/orgs/{org_slug}/projects/{project_slug}/billing/usage":{"get":{"description":"Get usage summary for a project within a date range. Both start_date and end_date are required in ISO format.\n","operationId":"get_usage","parameters":[{"$ref":"#/components/parameters/OrgSlug"},{"$ref":"#/components/parameters/ProjectSlug"},{"description":"Start date in ISO format","example":"2025-01-01T00:00:00","in":"query","name":"start_date","required":true,"schema":{"format":"date-time","type":"string"}},{"description":"End date in ISO format","example":"2025-01-31T23:59:59","in":"query","name":"end_date","required":true,"schema":{"format":"date-time","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"usage":{"$ref":"#/components/schemas/UsageSummary"}},"type":"object"}}},"description":"Usage summary"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request \u2014 missing or invalid dates"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"SessionAuth":[]}],"summary":"Get usage summary","tags":["Billing"]}},"/storage/browse":{"get":{"description":"Browse the project's file hierarchy as a directory tree. Returns subdirectories and files at the given path.\n","operationId":"browse_directory","parameters":[{"description":"Directory path to browse (empty string for root)","example":"2025/W03","in":"query","name":"path","schema":{"default":"","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"directories":{"description":"Subdirectory names","items":{"type":"string"},"type":"array"},"files":{"items":{"$ref":"#/components/schemas/StoredFile"},"type":"array"},"path":{"description":"Current directory path","type":"string"}},"type":"object"}}},"description":"Directory listing"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing project parameter"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Browse file directory","tags":["Storage"]}},"/storage/download/{storage_path}":{"get":{"description":"Generate a time-limited signed URL for downloading a file. The URL expires after 3600 seconds (1 hour).\n","operationId":"download_file","parameters":[{"$ref":"#/components/parameters/StoragePath"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"download_url":{"description":"Signed GCS download URL","format":"uri","type":"string"},"expires_in":{"description":"Seconds until the URL expires","example":3600,"type":"integer"}},"type":"object"}}},"description":"Signed download URL"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"properties":{"limit_type":{"type":"string"}},"type":"object"}]}}},"description":"Bandwidth limit exceeded"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Get a signed download URL","tags":["Storage"]}},"/storage/files":{"get":{"description":"List files with optional year/week filters and cursor-based pagination.\n","operationId":"list_files","parameters":[{"description":"Filter by year (e.g. 2025)","in":"query","name":"year","schema":{"type":"integer"}},{"description":"Filter by ISO week number (1\u201353)","in":"query","name":"week","schema":{"type":"integer"}},{"description":"Opaque cursor from a previous response for pagination","in":"query","name":"cursor","schema":{"type":"string"}},{"description":"Maximum number of files to return (max 100)","in":"query","name":"limit","schema":{"default":50,"maximum":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"files":{"items":{"$ref":"#/components/schemas/StoredFile"},"type":"array"},"has_more":{"description":"Whether more pages are available","type":"boolean"},"next_cursor":{"description":"Cursor for the next page, null if no more pages","nullable":true,"type":"string"},"total_in_page":{"description":"Number of files returned in this page","type":"integer"}},"type":"object"}}},"description":"Paginated file list"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing project parameter"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"List files in a project","tags":["Storage"]}},"/storage/files/{storage_path}":{"delete":{"description":"Soft-delete a file by its storage path.","operationId":"delete_file","parameters":[{"$ref":"#/components/parameters/StoragePath"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"message":{"example":"File deleted","type":"string"},"storage_path":{"type":"string"}},"type":"object"}}},"description":"File deleted"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing project parameter"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Delete a file","tags":["Storage"]},"get":{"description":"Retrieve metadata for a single file by its storage path.","operationId":"get_file_metadata","parameters":[{"$ref":"#/components/parameters/StoragePath"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"file":{"$ref":"#/components/schemas/StoredFile"}},"type":"object"}}},"description":"File metadata"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing project parameter"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Get file metadata","tags":["Storage"]}},"/storage/provenance/{storage_path}":{"get":{"description":"Retrieve blockchain provenance information for a file, including its blake3 hash, KURI, chain channel, and the full chain record if available.\n","operationId":"get_file_provenance","parameters":[{"$ref":"#/components/parameters/StoragePath"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"blake3_hash":{"type":"string"},"chain_channel_id":{"nullable":true,"type":"string"},"chain_kuri":{"nullable":true,"type":"string"},"chain_record":{"$ref":"#/components/schemas/ChainRecord"},"chain_written_at":{"format":"date-time","nullable":true,"type":"string"},"file_path":{"type":"string"}},"type":"object"}}},"description":"Provenance information"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Get blockchain provenance for a file","tags":["Storage"]}},"/storage/upload":{"post":{"description":"Upload a file via multipart/form-data. The file is blake3-hashed on receipt and stored under a chronological path (YYYY/W<WW>/<filepath>). If the organization has a Metarium chain channel, the KURI is written to the blockchain.\n","operationId":"upload_file","requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"file":{"description":"The file to upload (max 100 MB).","format":"binary","type":"string"},"path":{"description":"Optional destination path within the weekly directory. Defaults to the original filename.\n","example":"reports/weekly","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"blake3_hash":{"description":"Blake3 hash of the file contents","type":"string"},"chain_kuri":{"description":"KURI written to chain, e.g. blake3://<hash>","nullable":true,"type":"string"},"file":{"$ref":"#/components/schemas/StoredFile"},"storage_path":{"description":"Full chronological storage path","example":"2025/W03/reports/weekly/report.pdf","type":"string"}},"type":"object"}}},"description":"File uploaded successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request \u2014 no file provided or no filename"},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"properties":{"limit_type":{"description":"Which limit was exceeded (storage, bandwidth, api_calls)","type":"string"}},"type":"object"}]}}},"description":"File too large or storage/bandwidth limit exceeded"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"summary":"Upload a file to storage","tags":["Storage"]}}},"security":[{"BearerAuth":[]},{"SessionAuth":[]}],"servers":[{"url":"/api/v1"}],"tags":[{"description":"File upload, download, listing, browsing, and provenance","name":"Storage"},{"description":"Organization CRUD and membership management","name":"Organizations"},{"description":"Project CRUD and API key management","name":"Projects"},{"description":"Usage tracking, plan info, and plan upgrades","name":"Billing"},{"description":"Health checks and documentation","name":"System"}]}
