{"openapi":"3.1.0","info":{"title":"Harbor External API","version":"0.1.0","description":"Curated external API surface for third-party developers. Authenticate every request with `Authorization: Bearer hbr_…` (API keys are minted in the Harbor web app)."},"servers":[{"url":"https://api.testnet.harbor.walrus.xyz","description":"Testnet"}],"tags":[{"name":"Spaces","description":"Space management"},{"name":"Buckets","description":"Bucket management"},{"name":"Files","description":"File metadata and upload"}],"paths":{"/api/v1/spaces":{"get":{"tags":["Spaces"],"summary":"List spaces","description":"List all spaces accessible to the authenticated user (personal + team).","operationId":"listSpaces","security":[{"bearerAuth":[]}],"parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["personal","team"]},"description":"Filter by space type"}],"responses":{"200":{"description":"List of spaces","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SpaceListItem"}}}}}}},"401":{"description":"Not authenticated"}}}},"/api/v1/spaces/{id}/buckets":{"get":{"tags":["Buckets"],"summary":"List buckets in a space","operationId":"listBucketsBySpace","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Space UUID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":100},"description":"Maximum number of buckets to return."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque cursor returned by a previous page."},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":255},"description":"Case-insensitive substring match on bucket name."},{"name":"visibility","in":"query","required":false,"schema":{"type":"string","enum":["public","private"]},"description":"Filter buckets by visibility."},{"name":"sortField","in":"query","required":false,"schema":{"type":"string","enum":["name","date","storage"],"default":"date"},"description":"Field to sort by (date = createdAt)."},{"name":"sortOrder","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Sort direction."}],"responses":{"200":{"description":"Buckets in the space (paginated).","content":{"application/json":{"schema":{"type":"object","required":["buckets"],"properties":{"buckets":{"type":"array","items":{"$ref":"#/components/schemas/Bucket"}},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page; null when no more results."}}}}}},"400":{"description":"Invalid UUID"},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Space not found"}}},"post":{"tags":["Buckets"],"summary":"Create bucket in a space","operationId":"createBucket","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Space UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","scope"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"scope":{"type":"string","enum":["private"]}}}}}},"responses":{"201":{"description":"Private bucket reserved. Sign the returned `bytes` locally with the service key and POST the signature to POST /buckets/{id}/finalize to complete creation.","content":{"application/json":{"schema":{"type":"object","description":"Private bucket — reserve response. Sign `bytes` locally with the service key, then POST the signature to /buckets/{id}/finalize.","required":["bucket_id","bytes","digest","state"],"properties":{"bucket_id":{"type":"string","format":"uuid"},"bytes":{"type":"string","description":"Base64 sponsored transaction bytes."},"digest":{"type":"string"},"state":{"type":"string","enum":["pending_policy"]}}}}}},"400":{"description":"Validation error"},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Space not found"},"409":{"description":"Bucket name conflict"},"422":{"description":"Plan limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanLimitExceededResponse"}}}}}}},"/api/v1/spaces/{id}/files":{"get":{"tags":["Files"],"summary":"List files across all buckets in a space (global search)","operationId":"listSpaceFiles","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Space UUID"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's next_cursor"},{"name":"q","in":"query","schema":{"type":"string","maxLength":255},"description":"Case-insensitive substring match on file name."},{"name":"sortField","in":"query","schema":{"type":"string","enum":["name","date","size","type"],"default":"date"}},{"name":"sortOrder","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"File list (across all buckets in the space)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Opaque cursor for the next page, null when no more results"}}}}}}}},"400":{"description":"Invalid request — malformed space UUID or query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Space not found"}}}},"/api/v1/buckets/{id}":{"get":{"tags":["Buckets"],"summary":"Get bucket by ID","operationId":"getBucketById","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"}],"responses":{"200":{"description":"Bucket details","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Bucket"}}}}}},"400":{"description":"Invalid UUID"},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Bucket not found"}}},"put":{"tags":["Buckets"],"summary":"Update bucket","operationId":"updateBucket","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"sealPolicyId":{"type":["string","null"],"minLength":1,"maxLength":255},"visibility":{"type":"string","enum":["public","private"]}}}}}},"responses":{"200":{"description":"Bucket updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/UpdateBucketResponse"}}}}}},"400":{"description":"Validation error"},"401":{"description":"Not authenticated"},"403":{"description":"Access denied or visibility change attempted (immutable in v1)"},"404":{"description":"Bucket not found"},"409":{"description":"Bucket name conflict"}}},"delete":{"tags":["Buckets"],"summary":"Delete bucket","description":"Requires query parameter `confirm=true`.","operationId":"deleteBucket","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"}],"responses":{"204":{"description":"Bucket deleted","headers":{"X-Deleted-Id":{"description":"UUID of the deleted bucket","schema":{"type":"string","format":"uuid"}}}},"400":{"description":"Missing confirm=true, validation error, or bucket is not empty (files still present or storage in use)."},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Bucket not found"}}}},"/api/v1/buckets/{id}/files":{"get":{"tags":["Files"],"summary":"List files in bucket","operationId":"listBucketFiles","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's next_cursor"},{"name":"q","in":"query","schema":{"type":"string","maxLength":255}},{"name":"sortField","in":"query","schema":{"type":"string","enum":["name","date","size","type"],"default":"date"}},{"name":"sortOrder","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"File list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Opaque cursor for the next page, null when no more results"}}}}}}}},"400":{"description":"Invalid path parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Files"],"summary":"Upload file (async)","operationId":"uploadBucketFile","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"name":{"type":"string","maxLength":255},"metadata":{"type":"string","description":"Optional JSON object string persisted in Harbor metadata column. Maximum 8KB.","example":"{\"tag\":\"blue\"}"}}}}}},"responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FileSummary"}},"required":["data"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Bucket not found"},"409":{"description":"Bucket not finalized or duplicate file name in bucket","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited"}}}},"/api/v1/buckets/{id}/finalize":{"post":{"tags":["Buckets"],"summary":"Finalize private bucket","description":"Submit the on-chain signature to finalize a pending private bucket. Transitions the bucket from 'pending' to 'active'.","operationId":"finalizeBucket","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["signature"],"properties":{"signature":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Bucket finalized","content":{"application/json":{"schema":{"type":"object","properties":{"bucket_id":{"type":"string","format":"uuid"},"seal_policy_id":{"type":["string","null"]},"state":{"type":"string"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Bucket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/seal/sponsor":{"post":{"tags":["SEAL"],"summary":"Sponsor a SEAL transaction","description":"Build and sponsor an on-chain bucket-policy PTB via Enoki. Returns transaction bytes for the client to sign, plus a digest the client must echo to the execute endpoint.","operationId":"sealSponsor","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["kind","groupIds","recipientAddress","scope"],"properties":{"kind":{"type":"string","enum":["grant_bucket_access"]},"groupIds":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":1000},"recipientAddress":{"type":"string"},"scope":{"type":"string","enum":["read","readwrite"]}}},{"type":"object","required":["kind","bucketId"],"properties":{"kind":{"type":"string","enum":["bucket_group_create"]},"bucketId":{"type":"string","minLength":1}}},{"type":"object","required":["kind","groupId","member"],"properties":{"kind":{"type":"string","enum":["share_admin"]},"groupId":{"type":"string"},"member":{"type":"string"}}},{"type":"object","required":["kind","groupId","member"],"properties":{"kind":{"type":"string","enum":["unshare"]},"groupId":{"type":"string"},"member":{"type":"string"}}},{"type":"object","required":["kind","groupId","serviceSignerAddress"],"properties":{"kind":{"type":"string","enum":["unshare_bucket_access"]},"groupId":{"type":"string"},"serviceSignerAddress":{"type":"string"}}}]}}}},"responses":{"200":{"description":"Transaction bytes and digest","content":{"application/json":{"schema":{"type":"object","properties":{"bytes":{"type":"string"},"digest":{"type":"string"}}}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied or precheck failed"}}}},"/api/v1/seal/sponsor/{digest}/execute":{"post":{"tags":["SEAL"],"summary":"Execute a sponsored SEAL transaction","description":"Submit the wallet signature over the sponsored bytes. Enoki broadcasts the transaction and returns the on-chain digest.","operationId":"sealSponsorExecute","security":[{"bearerAuth":[]}],"parameters":[{"name":"digest","in":"path","required":true,"schema":{"type":"string","minLength":1},"description":"Sponsor digest returned by POST /seal/sponsor"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["signature"],"properties":{"signature":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"On-chain transaction digest","content":{"application/json":{"schema":{"type":"object","properties":{"digest":{"type":"string"}}}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"Digest not found or expired"}}}},"/api/v1/buckets/{id}/files/{fileId}":{"get":{"tags":["Files"],"summary":"Get file metadata","operationId":"getFileById","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"},{"name":"fileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"File UUID"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FileSummary"}},"required":["data"]}}}},"400":{"description":"Invalid id"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not found (or file not in this bucket)"}}},"delete":{"tags":["Files"],"summary":"Delete file (async, soft-delete + worker)","description":"Marks the file as `deleting` and enqueues a background job that asks Oyster to remove the underlying blob. Returns 204 immediately; storage is released only after the worker confirms the Oyster delete. Subsequent retries are deduplicated by file id.","operationId":"deleteBucketFile","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"},{"name":"fileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"File UUID"}],"responses":{"204":{"description":"Accepted; file marked deleting"},"400":{"description":"Invalid path parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied"},"404":{"description":"File not found in this bucket"}}}},"/api/v1/buckets/{id}/files/{fileId}/status":{"get":{"tags":["Files"],"summary":"Get upload job status","description":"Poll the state of an in-flight upload job after the upload route returns 202. Maps the BullMQ job state to a public enum (queued, active, completed, failed). Returns 404 once the job has been removed from the queue per BullMQ retention limits — clients should rely on the file metadata route to confirm completion thereafter.","operationId":"getBucketFileUploadStatus","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"},{"name":"fileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"File UUID"}],"responses":{"200":{"description":"Current upload status","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["state"],"properties":{"state":{"type":"string","enum":["queued","active","completed","failed"]},"progress":{"type":"number","description":"Fractional progress (0..1), present only when the worker reports it."},"error":{"type":"object","description":"Structured sanitized error, present only when state is 'failed'.","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"http_status":{"type":"number"}}}}}}}}}},"400":{"description":"Invalid path parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File or upload job not found"}}}},"/api/v1/buckets/{id}/files/{fileId}/download":{"get":{"tags":["Files"],"summary":"Download file","description":"Stream the raw file content. Returns the file with appropriate Content-Type and Content-Disposition headers. Private/authenticated content is never cached by shared proxies.","operationId":"downloadBucketFile","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Bucket UUID"},{"name":"fileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"File UUID"}],"responses":{"200":{"description":"File stream","headers":{"Content-Type":{"schema":{"type":"string"},"description":"MIME type of the file"},"Content-Disposition":{"schema":{"type":"string"},"description":"attachment; filename=\"<encoded name>\""},"Content-Length":{"schema":{"type":"integer"},"description":"File size in bytes (omitted when unknown)"},"Cache-Control":{"schema":{"type":"string","example":"private, no-store"}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid path parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Bucket or file not found"},"423":{"description":"File row is in 'deleting' or 'deleted' status"},"500":{"description":"Blob stream unavailable"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"API key passed as Bearer token. Created via POST /api/v1/api-keys."}},"schemas":{"ErrorResponse":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"string","description":"Machine-readable error code (when available)","enum":["USED_NONCE","EXPIRED_TIMESTAMP","ADDRESS_MISMATCH","INVALID_CHALLENGE","unauthorized","api_key_registering","api_key_revoking","api_key_revoked","read_only_api_key","bucket_not_in_scope","mirror_missing_grant","bucket_not_finalized","quota_exceeded","payload_too_large","bad_request"]}},"required":["error"]},"PlanLimitExceededResponse":{"type":"object","required":["code","limit","currentTier"],"properties":{"code":{"type":"string","enum":["PLAN_LIMIT_EXCEEDED"]},"limit":{"type":"string","enum":["storage","users","buckets"]},"currentTier":{"type":"string","enum":["free"]}}},"AddSpaceMemberRequest":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["admin","editor","viewer"],"default":"viewer"}}},"ChallengeResult":{"type":"object","properties":{"challenge":{"type":"string","description":"Structured challenge message to sign with wallet"},"nonce":{"type":"string","format":"uuid","description":"Server-generated single-use nonce"},"issued_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time","description":"Challenge expires 5 minutes after issuance"}},"required":["challenge","nonce","issued_at","expires_at"]},"AuthenticatedUser":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":["string","null"]},"sui_address":{"type":"string"},"harbor_wallet_address":{"type":["string","null"]},"auth_provider":{"$ref":"#/components/schemas/AuthProvider"},"created_at":{"type":"string","format":"date-time"}}},"UserProfileDTO":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":["string","null"]},"sui_address":{"type":"string"},"harbor_wallet_address":{"type":["string","null"]},"auth_provider":{"$ref":"#/components/schemas/AuthProvider"},"personal_space_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"}}},"AuthProvider":{"type":"string","enum":["google","apple","facebook","twitch","wallet"]},"LinkedAccount":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"provider":{"$ref":"#/components/schemas/AuthProvider"},"provider_address":{"type":"string"},"linked_at":{"type":"string","format":"date-time"}}},"ApiKeySummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"prefix":{"type":"string","description":"First 8 characters of the key for identification","example":"a1b2c3d4"},"space_id":{"type":"string","format":"uuid"},"permissions":{"type":"string","enum":["read_only","read_write"]},"last_used_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"ApiKeyCreateResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"key":{"type":"string","description":"The full raw API key — shown only on creation, never retrievable again","example":"hbr_a1b2c3d4..."},"space_id":{"type":"string","format":"uuid"},"permissions":{"type":"string","enum":["read_only","read_write"]},"created_at":{"type":"string","format":"date-time"}}},"SpaceListItem":{"type":"object","required":["id","type","name","storage_used","storage_cap","bucket_count","role","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["personal","team"]},"name":{"type":"string"},"storage_used":{"type":"integer","description":"Bytes currently stored"},"storage_cap":{"type":"integer","description":"Maximum bytes allowed"},"bucket_count":{"type":"integer"},"member_count":{"type":["integer","null"],"description":"Only present for team spaces"},"role":{"type":"string","enum":["owner","admin","editor","viewer"],"description":"owner for personal, team role for team spaces"},"created_at":{"type":"string","format":"date-time"}}},"Bucket":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"space_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"oyster_bucket_name":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"seal_policy_id":{"type":["string","null"]},"storage_used":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","space_id","name","oyster_bucket_name","visibility","seal_policy_id","storage_used","storage_cap","created_at","updated_at"]},"CreateBucketRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"visibility":{"type":"string","enum":["public","private"]},"seal_policy_id":{"type":"string","description":"Required when visibility=private. Must be omitted for public buckets."}},"required":["name","visibility","seal_policy_id"],"additionalProperties":false},"UpdateBucketRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"seal_policy_id":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"SEAL policy ID. Pass null to clear."},"visibility":{"type":"string","enum":["public","private"],"description":"Immutable in v1 — if provided, the request is rejected with 403."}},"required":["name"],"additionalProperties":false},"UpdateBucketResponse":{"type":"object","required":["id","name","visibility","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"updated_at":{"type":"string","format":"date-time"}}},"FileSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"bucket_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"blob_id":{"type":["string","null"]},"oyster_object_id":{"type":["string","null"]},"size":{"type":"integer"},"mime_type":{"type":["string","null"]},"status":{"type":"string"},"is_private":{"type":"boolean"},"metadata":{"type":"object","additionalProperties":true,"description":"User-defined metadata persisted by Harbor."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"deleted_at":{"type":["string","null"],"format":"date-time"}},"required":["id","bucket_id","name","size","status","is_private","created_at","updated_at"]}}}}