{"openapi":"3.1.0","info":{"title":"Seal API","description":"Anonymous encrypted file sharing service","version":"1.0.0"},"paths":{"/seal":{"post":{"tags":["seal"],"summary":"Create Seal","description":"Create a new seal (anonymous or from Vault) - PURE ASYNC","operationId":"create_seal_seal_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SealCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SealResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/append":{"post":{"tags":["seal"],"summary":"Append Files To Seal","description":"Append files to seal (PURE ASYNC). seal_id auto-validated via ValidSealId.","operationId":"append_files_to_seal_seal__seal_id__append_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppendFilesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SealResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}":{"get":{"tags":["seal"],"summary":"Get Seal","description":"Get public seal metadata (PURE ASYNC). seal_id auto-validated.\n\nIdea #6 (Phase 2): the body delegates each concern to a named\nhelper above. The orchestration here is intentionally short — if\nyou find yourself adding logic to this function, ask whether it\nbelongs in one of the helpers (or a new one) instead.","operationId":"get_seal_seal__seal_id__get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SealPublicMeta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/revoke":{"post":{"tags":["seal"],"summary":"Revoke Seal","description":"Revoke a seal (owner only) - PURE ASYNC.\n\nP0.5: Revoking a seal must DELETE the underlying ciphertext blobs from\nobject storage, not just flip `revoked_at`. Without storage cleanup, an\nattacker who already holds a presigned URL (or harvested s3_key) keeps\nindefinite access to the encrypted blobs even after the user \"revokes\".\nMirrors the per-file revoke flow at `revoke_file`.","operationId":"revoke_seal_seal__seal_id__revoke_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/owner":{"get":{"tags":["seal"],"summary":"Get Seal As Owner","description":"Get seal metadata as owner (includes files even when revoked).\n\nOwner-only endpoint that returns full seal data including:\n- All files (even revoked ones)\n- Per-file access flags\n- Effective computed flags\n- Total bytes (excluding revoked)","operationId":"get_seal_as_owner_seal__seal_id__owner_get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SealResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/otp":{"post":{"tags":["seal"],"summary":"Verify Otp","description":"Verify OTP and set session cookie - thin route","operationId":"verify_otp_seal__seal_id__otp_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPVerify"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/nda":{"post":{"tags":["seal"],"summary":"Accept Nda","description":"Accept NDA with signature and set session cookie - thin route","operationId":"accept_nda_seal__seal_id__nda_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"description":"NDA acceptance request with signature.\n\nAll free-form text fields carry an explicit `max_length` so a single\nrequest can never coerce ReportLab into rendering a multi-megabyte\ndocument. The signature payload is additionally constrained to a strict\n`data:image/png;base64,…` prefix (P1-3).","properties":{"signature":{"anyOf":[{"maxLength":200000,"type":"string"},{"type":"null"}],"description":"User signature as data:image/png;base64,…","title":"Signature"},"timestamp":{"anyOf":[{"maxLength":64,"type":"string"},{"type":"null"}],"description":"Signature timestamp (ISO-8601)","title":"Timestamp"},"first_name":{"anyOf":[{"maxLength":128,"type":"string"},{"type":"null"}],"description":"User's first name","title":"First Name"},"last_name":{"anyOf":[{"maxLength":128,"type":"string"},{"type":"null"}],"description":"User's last name","title":"Last Name"},"company":{"anyOf":[{"maxLength":256,"type":"string"},{"type":"null"}],"description":"User's company","title":"Company"},"file_id":{"anyOf":[{"format":"uuid","type":"string"},{"type":"null"}],"description":"A file of an organization's share link","title":"File Id"},"share_code":{"anyOf":[{"maxLength":4,"type":"string"},{"type":"null"}],"description":"The share link the file is opened by","title":"Share Code"},"nda_text":{"anyOf":[{"maxLength":16384,"type":"string"},{"type":"null"}],"description":"The exact NDA text the recipient saw and signed. The backend renders this into the PDF that is emailed to the seal owner and then discards it; it is NEVER stored in the database.","title":"Nda Text"}},"title":"NDAAccept","type":"object"}}}}}},"/seal/{seal_id}/files/{file_id}/revoke":{"post":{"tags":["seal"],"summary":"Revoke File","description":"Revoke a specific file (owner only) - PURE ASYNC","operationId":"revoke_file_seal__seal_id__files__file_id__revoke_post","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"File Id"}},{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/files/{file_id}/access":{"patch":{"tags":["seal"],"summary":"Update File Access Settings","description":"Update access settings for a specific file (PURE ASYNC)","operationId":"update_file_access_settings_seal__seal_id__files__file_id__access_patch","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"File Id"}},{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileAccessSettingsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/download":{"get":{"tags":["seal"],"summary":"Get Download Url","description":"Download file - thin route using download_service","operationId":"get_download_url_seal__seal_id__download_get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"},{"name":"file_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"File ID to download","title":"File Id"},"description":"File ID to download"},{"name":"share_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The share link a recipient of an organization's seal came by: the file must be in its scope","title":"Share Code"},"description":"The share link a recipient of an organization's seal came by: the file must be in its scope"},{"name":"range","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"bytes=<A>-<B>: a URL signed for this window only (organization seals)","title":"Range"},"description":"bytes=<A>-<B>: a URL signed for this window only (organization seals)"},{"name":"otp_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Otp Session"}},{"name":"nda_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nda Session"}}],"responses":{"307":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/audit":{"get":{"tags":["seal"],"summary":"Get Seal Audit","description":"Get audit events for a seal (owner only).\n\nReturns all audit events for the seal in reverse chronological order.","operationId":"get_seal_audit_seal__seal_id__audit_get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/audit/download":{"get":{"tags":["seal"],"summary":"Download Seal Audit Trail","description":"Download full audit trail for a seal as JSONL (one signed event per line).\n\nRequires owner_token authentication via X-Owner-Token header.\nEach line contains cryptographic signature for legal proof.","operationId":"download_seal_audit_trail_seal__seal_id__audit_download_get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/blob":{"get":{"tags":["seal"],"summary":"Get Owner Blob","description":"Get owner encrypted blob (public - blob is encrypted).\n\nReturns the encrypted blob which client decrypts with linkKey from URL hash.\nNo auth required — blob is useless without linkKey.\n\nRate limit: 1000/min per IP (высокий лимит для NAT/офисов)\n\nP3-1 (Phase 1): emits `ETag: W/\"<lock_version>\"`. The same value\nis mirrored in the body via `BlobResponse.version` so stateless\nclients (curl, scripts) can use the same CAS contract without\nparsing response headers.","operationId":"get_owner_blob_seal__seal_id__blob_get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["seal"],"summary":"Update Owner Blob","description":"Update owner encrypted blob (owner only, optimistic CAS).\n\nRequires X-Owner-Token header for authentication.\n\nRate limit: 60/min per seal — counted AFTER owner-token verification\n(P3-0.5). Pre-fix the per-seal counter was incremented by every\nrequest bearing a valid `seal_id`, regardless of whether the\n`X-Owner-Token` was correct, which let any attacker who knew the\nseal id (8-char alphanumeric — leaked via share screenshots, etc.)\nburn the legitimate owner's quota and lock them out of their own\nseal. Verifying first means a successful 401/403/404 short-circuit\ncosts nothing in the seal's bucket.\n\nConcurrency (P3-1, C1 Р25):\n\n  * `If-Match: W/\"<n>\"` present → atomic CAS via `cas_update`.\n    Mismatch → 412 + `code: blob.version_mismatch`.\n  * `If-Match` absent → the first write of the blob; over an\n    existing blob → 428 + `code: blob.precondition_required`.\n\nBoth writes return the new `lock_version` via the `ETag` header\nAND the response body, so a successful PATCH gives the client an\nimmediate fresh ETag to use on its next round-trip without an\nintervening GET.","operationId":"update_owner_blob_seal__seal_id__blob_patch","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressedBlob"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/share":{"post":{"tags":["seal"],"summary":"Create Share Link","description":"Create short share link (owner only).\n\nCreates a share link with an encrypted blob holding the keys of the files\nit opens (`file_ids`, or all of them).\n\nRate limit: 30/min per seal — counted AFTER the owner check (P3-0.5): any\nattacker who knows a seal id could otherwise burn the legitimate owner's\nbucket without ever holding a valid token.","operationId":"create_share_link_seal__seal_id__share_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLinkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/share/{share_code}":{"get":{"tags":["seal"],"summary":"Get Share Blob","description":"Get share encrypted blob (public - blob is encrypted).\n\nReturns the encrypted blob for a share link; the client decrypts it with\nthe key in the URL fragment, or — an addressed link — with the key in\n`fragment`, wrapped to the recipient's bundle (C1 Р19).\n\nRate limit: 500/min per IP","operationId":"get_share_blob_seal__seal_id__share__share_code__get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"},{"name":"share_code","in":"path","required":true,"schema":{"type":"string","description":"4-char base64url share code","title":"Share Code"},"description":"4-char base64url share code"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/share/{share_code}/revoke":{"post":{"tags":["seal"],"summary":"Revoke Share Link","description":"Revoke individual share link (owner only).\n\nSets revoked_at timestamp on the share link (does NOT delete data). A\nrepeat answers 200 with the first `revoked_at` and writes nothing (C1 Р22),\nas for a seal.","operationId":"revoke_share_link_seal__seal_id__share__share_code__revoke_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"},{"name":"share_code","in":"path","required":true,"schema":{"type":"string","description":"4-char base64url share code","title":"Share Code"},"description":"4-char base64url share code"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/subscriptions":{"post":{"tags":["seal"],"summary":"Create Subscription","description":"Register a signed-webhook subscription for a seal (owner only).\n\nReturns the created subscription plus the derived HMAC `secret`\nEXACTLY ONCE — the receiver stores it to verify future deliveries.","operationId":"create_subscription_seal__seal_id__subscriptions_post","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["seal"],"summary":"List Subscriptions","description":"List a seal's active subscriptions (owner only). No secrets.","operationId":"list_subscriptions_seal__seal_id__subscriptions_get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/seal/{seal_id}/subscriptions/{subscription_id}":{"delete":{"tags":["seal"],"summary":"Delete Subscription","description":"Deactivate a subscription (owner only). Idempotent.\n\nSoft-delete (`active = False`) rather than a row delete so an\nin-flight delivery the worker already leased can still resolve, and\nso the owner's audit of \"what was subscribed\" survives.","operationId":"delete_subscription_seal__seal_id__subscriptions__subscription_id__delete","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Subscription Id"}},{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","description":"8-char base64url seal ID","title":"Seal Id"},"description":"8-char base64url seal ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/initiate":{"post":{"tags":["upload"],"summary":"Initiate Presigned Upload","description":"Step 1: Initiate presigned upload for 100K+ concurrent support.\n\nReturns a presigned PUT URL for direct client→storage upload.\nMarks file status as \"pending\" in database.\n\nUses dedicated signing executor (4 workers) for CPU-bound operations.\nNon-blocking for event loop — supports 40K signs/sec per pod.\n\nFlow:\n    1. Client calls POST /upload/initiate\n    2. Server validates file record exists\n    3. Server marks status=\"pending\"\n    4. Server returns presigned URL\n    5. Client uploads directly to R2 (server NOT in data path)\n    6. Client calls POST /upload/confirm\n\nSecurity:\n    - SEAL: Requires X-Owner-Token header\n    - AEON/Vault: Requires JWT Bearer token + org membership","operationId":"initiate_presigned_upload_upload_initiate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadInitiateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadInitiateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/confirm":{"post":{"tags":["upload"],"summary":"Confirm Presigned Upload","description":"Step 2: Confirm a direct upload. The file is verified HERE.\n\nOne HEAD against storage: the object exists and has the size the row\nauthorizes → `verified` in the same transaction, signed `file_verified`\nevent included. No queue and no polling on this path, and the server\nnever reads the object (audit/SPEC-UPLOAD-SCALE.md §3).\n\nStatus flow:\n    pending → verified                      (the normal case)\n    pending → pending_verification → …      (storage could not be asked:\n                                             the durable worker repeats\n                                             the same check)\n\nSecurity:\n    - SEAL: Requires X-Owner-Token header\n    - AEON/Vault: Requires JWT Bearer token + org membership","operationId":"confirm_presigned_upload_upload_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmUploadRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/status/{seal_id}/{sha256_hex}":{"get":{"tags":["upload"],"summary":"Get Upload Status By Hash","description":"Get upload verification status.\n\nUsed by frontend to poll for verification completion after /confirm.\n\nReturns:\n    status: current upload status\n    verified_at: ISO timestamp when verified (if verified)\n    size: file size in bytes","operationId":"get_upload_status_by_hash_upload_status__seal_id___sha256_hex__get","parameters":[{"name":"seal_id","in":"path","required":true,"schema":{"type":"string","title":"Seal Id"}},{"name":"sha256_hex","in":"path","required":true,"schema":{"type":"string","title":"Sha256 Hex"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/multipart/init":{"post":{"tags":["upload"],"summary":"Multipart Init","description":"Initialize a part-based multipart upload.\n\nReturns upload_id and part_size. Client should:\n1. Split file into parts of part_size bytes\n2. Get presigned URLs for the parts from POST /multipart/parts\n3. PUT encrypted chunk directly to presigned URL\n4. Collect ETags from PUT responses\n5. Call POST /multipart/complete with all parts + ETags","operationId":"multipart_init_upload_multipart_init_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartInitRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartInitResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/multipart/parts":{"post":{"tags":["upload"],"summary":"Multipart Parts Batch","description":"Get presigned URLs for parts of a multipart upload — the one presign\nroute, a batch or a single part (C1 Р28).\n\nEliminates per-part round-trip overhead: one auth check, one Redis\nlookup, N presigned URLs generated in parallel via the signing\nexecutor pool.","operationId":"multipart_parts_batch_upload_multipart_parts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartBatchPartsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartBatchPartsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/multipart/status":{"get":{"tags":["upload"],"summary":"Multipart Status","description":"Get status of a multipart upload.\n\nSECURITY FIX (AUTH-3): Added authorization check.\nReturns uploaded parts with ETags for resume capability.\nClient can use this to determine which parts need to be uploaded.","operationId":"multipart_status_upload_multipart_status_get","parameters":[{"name":"upload_id","in":"query","required":true,"schema":{"type":"string","description":"Upload ID","title":"Upload Id"},"description":"Upload ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/multipart/complete":{"post":{"tags":["upload"],"summary":"Multipart Complete","description":"Complete multipart upload.\n\nClient must provide all uploaded parts with their ETags.\nS3 will assemble the final object from the parts.","operationId":"multipart_complete_upload_multipart_complete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartCompleteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartCompleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/upload/multipart/abort":{"post":{"tags":["upload"],"summary":"Multipart Abort","description":"Abort multipart upload and cleanup.\n\nRemoves uploaded parts from S3 and cleans up state.","operationId":"multipart_abort_upload_multipart_abort_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartAbortRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartAbortResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake":{"post":{"tags":["intake"],"summary":"Create Intake","operationId":"create_intake_intake_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["intake"],"summary":"Read Inbox","operationId":"read_inbox_intake_get","parameters":[{"name":"to","in":"query","required":true,"schema":{"type":"string","description":"The address whose inbox to read.","title":"To"},"description":"The address whose inbox to read."},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":0,"default":0,"title":"Wait"}},{"name":"seen","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"description":"`version` of the last answer.","title":"Seen"},"description":"`version` of the last answer."},{"name":"all","in":"query","required":false,"schema":{"type":"boolean","description":"Owner copies and cards too (a person's inbox).","default":false,"title":"All"},"description":"Owner copies and cards too (a person's inbox)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["intake"],"summary":"Dismiss Inbox Item","operationId":"dismiss_inbox_item_intake_delete","parameters":[{"name":"to","in":"query","required":true,"schema":{"type":"string","description":"The address whose inbox to clean.","title":"To"},"description":"The address whose inbox to clean."},{"name":"item","in":"query","required":true,"schema":{"type":"string","maxLength":20,"description":"The item's `id`.","title":"Item"},"description":"The item's `id`."}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/proof":{"post":{"tags":["intake"],"summary":"Address Proof","operationId":"address_proof_intake_proof_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofChallenge"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/bundles":{"put":{"tags":["intake"],"summary":"Put Own Bundle","description":"A client puts its bundle at its first run and whenever it is gone (§5.4):\nno authorization — the bundle proves itself — and a limit per IP.","operationId":"put_own_bundle_intake_bundles_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundlePut"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundlePlaced"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/bundles/{address}":{"get":{"tags":["intake"],"summary":"Get Bundle","operationId":"get_bundle_intake_bundles__address__get","parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string","title":"Address"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/migrate":{"post":{"tags":["intake"],"summary":"Migrate","description":"Move the inbox before C1 to the proven address, once (Р18; until the\nremoval): `{old_address}` answers the old challenge, sealed to the old key;\n`{old_address, old_proof}` moves the rows.","operationId":"migrate_intake_migrate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ProofChallenge"},{"$ref":"#/components/schemas/Migrated"}],"title":"Response Migrate Intake Migrate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/push-key":{"get":{"tags":["intake"],"summary":"Push Key","operationId":"push_key_intake_push_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushKey"}}}}}}},"/intake/subscriptions":{"post":{"tags":["intake"],"summary":"Subscribe Push","operationId":"subscribe_push_intake_subscriptions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushSubscription"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["intake"],"summary":"Unsubscribe Push","operationId":"unsubscribe_push_intake_subscriptions_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushSubscription"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/{intake_id}":{"get":{"tags":["intake"],"summary":"Get Intake","operationId":"get_intake_intake__intake_id__get","parameters":[{"name":"intake_id","in":"path","required":true,"schema":{"type":"string","title":"Intake Id"}},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":0,"default":0,"title":"Wait"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/intake/{intake_id}/fulfil":{"post":{"tags":["intake"],"summary":"Fulfil Intake","operationId":"fulfil_intake_intake__intake_id__fulfil_post","parameters":[{"name":"intake_id","in":"path","required":true,"schema":{"type":"string","title":"Intake Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeFulfil"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/checkout":{"post":{"tags":["billing"],"summary":"Create Checkout","description":"Create a Stripe Checkout Session for a seal upgrade.\n\nMoved server-side in CF-3, made durable in ISSUE-0609. See module\ndocstring for the full contract.","operationId":"create_checkout_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clients":{"get":{"tags":["clients"],"summary":"Clients","description":"The client contract as is: the version header, each client's minimum and\nlatest version and its update command. `seal update` reads `latest` here.","operationId":"clients_clients_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientsContract"}}}}}}}},"components":{"schemas":{"AddressedBlob":{"properties":{"encrypted_blob":{"type":"string","maxLength":1468006,"title":"Encrypted Blob","description":"base64url encoded encrypted blob, max 1MB"},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"The recipient's address, 43 base64url characters."},"fragment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fragment","description":"The blob's key wrapped to the bundle of `to`: a packed sealed box v2, base64url."},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Label","description":"Who sends: an agent's name, shown as is."}},"type":"object","required":["encrypted_blob"],"title":"AddressedBlob","description":"A blob, optionally addressed (SPEC-ADDRESS F2, C1 Р19).\n\n`POST /seal/{id}/share`: the share link lands in the address's inbox;\n`PATCH /seal/{id}/blob`: the seal's owner copy names the address's inbox."},"AppendFilesRequest":{"properties":{"files_meta":{"items":{"$ref":"#/components/schemas/FileMeta"},"type":"array","maxItems":30,"minItems":1,"title":"Files Meta"},"otp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Otp","description":"OTP password for files that require OTP. Only used if any file has require_otp=True"}},"type":"object","required":["files_meta"],"title":"AppendFilesRequest","description":"Append files to existing seal request"},"BlobResponse":{"properties":{"encrypted_blob":{"type":"string","title":"Encrypted Blob"},"version":{"type":"integer","title":"Version","default":0},"fragment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fragment"}},"type":"object","required":["encrypted_blob"],"title":"BlobResponse","description":"Response with encrypted blob.\n\nP3-1: `version` mirrors the value emitted via the `ETag` response\nheader. Stateless clients (curl, integration scripts) can rely on\nthe body field; browser SPAs typically read the `ETag` instead and\npass it back as `If-Match`. Both paths are equivalent — the field\nis the integer `lock_version` of the seal row at the moment of the\nresponse.\n\n`fragment`: an addressed share link's key wrapped to its recipient's\nbundle (C1 Р19) — the link's URL carries no key; null for the others."},"BundlePlaced":{"properties":{"address":{"type":"string","title":"Address","description":"The address the server computed from the bundle."}},"type":"object","required":["address"],"title":"BundlePlaced"},"BundlePut":{"properties":{"bundle":{"type":"string","title":"Bundle","description":"A packed bundle v2, base64url."}},"type":"object","required":["bundle"],"title":"BundlePut"},"BundleView":{"properties":{"bundle":{"type":"string","title":"Bundle","description":"The packed bundle v2 filed under the address, base64url."}},"type":"object","required":["bundle"],"title":"BundleView"},"CheckoutRequest":{"properties":{"tier":{"type":"string","title":"Tier","description":"Tier code: 5gb | 50gb | 500gb"},"seal_id":{"type":"string","title":"Seal Id","description":"Seal ID (8-char base64url)"}},"type":"object","required":["tier","seal_id"],"title":"CheckoutRequest","description":"POST /billing/checkout body.\n\n`seal_id` lives in the body (not the URL) because the legacy\nfrontend route signed contracts with this exact shape. Public\nURL surface stability beats path-style aesthetics here.\n\n`ValidSealId` from `_common.py` is a `Path()`-bound dependency\n(it parses the seal_id off the URL) so we can't use it as a\nfield type. The inline validator below mirrors the same regex\n(`SEAL_ID_PATTERN`) so the two sites can't drift."},"CheckoutResponse":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"CheckoutResponse","description":"The payment page: `/pay` with Stripe's form (see `_pay_url`)."},"ClientsContract":{"properties":{"header":{"type":"string","title":"Header"},"headerless":{"additionalProperties":{"type":"string"},"type":"object","title":"Headerless"},"clients":{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object","title":"Clients"}},"type":"object","required":["header","headerless","clients"],"title":"ClientsContract","description":"`clients.json`: the version header, the rule for a request without it per API\n(`seal`, `pro`), and per client its minimum, latest version and update command."},"ConfirmUploadRequest":{"properties":{"seal_id":{"type":"string","title":"Seal Id","description":"Seal ID (8-char base64url)"},"file_id":{"type":"string","format":"uuid","title":"File Id","description":"The file row from registration"},"on_conflict":{"type":"string","enum":["fail","keep_both"],"title":"On Conflict","description":"A version whose base another version closed first: 412, or keep as a second head","default":"fail"}},"type":"object","required":["seal_id","file_id"],"title":"ConfirmUploadRequest","description":"Confirm upload completed request: the row by its id (C1 Р6, Р24)."},"ConfirmUploadResponse":{"properties":{"version_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version Of"},"head":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Head"},"conflict_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conflict With"},"status":{"type":"string","title":"Status"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"verified":{"type":"boolean","title":"Verified","default":false},"size":{"type":"integer","title":"Size","default":0}},"type":"object","required":["status"],"title":"ConfirmUploadResponse","description":"Upload confirmation response"},"FileAccessSettingsUpdate":{"properties":{"require_nda":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Require Nda","description":"Require NDA for this file. None = inherit from seal"},"require_otp":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Require Otp","description":"Require OTP for this file. None = inherit from seal"},"single_use_download":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Single Use Download","description":"Single-use download for this file. None = inherit from seal"}},"type":"object","title":"FileAccessSettingsUpdate","description":"Update file access settings request"},"FileMeta":{"properties":{"name_encrypted":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name Encrypted","description":"AES-256-GCM encrypted filename (Base64)"},"name_nonce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name Nonce","description":"Nonce for name encryption (Base64)"},"mime_encrypted":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Encrypted","description":"AES-256-GCM encrypted MIME type (Base64)"},"mime_nonce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Nonce","description":"Nonce for MIME encryption (Base64)"},"encryption_version":{"type":"integer","title":"Encryption Version","description":"The AEON format version the file is written in: only the current one registers","default":4},"size":{"type":"integer","exclusiveMinimum":0.0,"title":"Size","description":"Length of the AEON v4 ciphertext"},"sha256_hex":{"type":"string","maxLength":64,"minLength":64,"pattern":"^[a-f0-9]{64}$","title":"Sha256 Hex","description":"SHA-256 of the AEON v4 header (lowercase hex)"},"require_nda":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Require Nda","description":"Require NDA for this file. None = inherit from seal"},"require_otp":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Require Otp","description":"Require OTP for this file. None = inherit from seal"},"single_use_download":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Single Use Download","description":"Single-use download for this file. None = inherit from seal"},"parent_folder_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Folder Id","description":"ID of parent folder if this file is inside a folder"},"supersedes":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Supersedes","description":"Head file this upload replaces (organization seals only)"},"layout":{"type":"string","const":"whole","title":"Layout","description":"Object layout; 'chunked' opens in D1b","default":"whole"}},"type":"object","required":["size","sha256_hex"],"title":"FileMeta","description":"File metadata: the name and MIME only encrypted (C1 Р23)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InboxItem":{"properties":{"id":{"type":"string","title":"Id","description":"`s.<seal>.<code>` a delivery, `o.<seal>` an owner copy, `r.<id>` a card."},"item":{"type":"string","enum":["delivery","owner","request"],"title":"Item"},"path":{"type":"string","title":"Path","description":"`/s/{seal}/{code}`, `/s/{seal}` or `/i/{id}` on seal.net, without a fragment."},"fragment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fragment","description":"The link's or the owner blob's key wrapped to this address's bundle (sealed box v2); null for a card."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"sender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender","description":"The sender's address when it proved it, else null."},"at":{"type":"string","format":"date-time","title":"At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"what":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"What","description":"A card: what is asked for."},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"A card: the request kind."},"recipient_bundle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Bundle","description":"A card: the bundle the answer is sealed to."}},"type":"object","required":["id","item","path","fragment","label","sender","at","expires_at"],"title":"InboxItem"},"InboxList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InboxItem"},"type":"array","title":"Items"},"version":{"type":"string","title":"Version","description":"Pass as `seen` to wait for a change; opaque."},"now":{"type":"string","format":"date-time","title":"Now"}},"type":"object","required":["items","version","now"],"title":"InboxList"},"IntakeCreate":{"properties":{"recipient_bundle":{"type":"string","title":"Recipient Bundle","description":"The requester's packed bundle v2, base64url: the answer's key is wrapped to it."},"label":{"type":"string","maxLength":80,"minLength":1,"title":"Label","description":"Who asks: the agent's name, shown to the person."},"what":{"type":"string","maxLength":120,"minLength":1,"title":"What","description":"What is asked for."},"where_url":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Where Url","description":"https page where the secret is created."},"kind":{"type":"string","enum":["secret","file","any","pair","device"],"title":"Kind","default":"any"},"ttl_seconds":{"type":"integer","maximum":86400.0,"minimum":60.0,"title":"Ttl Seconds","default":900},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"The address the request is asked of: it becomes a card in that key's inbox."}},"type":"object","required":["recipient_bundle","label","what"],"title":"IntakeCreate"},"IntakeFulfil":{"properties":{"seal_id":{"type":"string","title":"Seal Id"},"share_code":{"type":"string","title":"Share Code"},"fragment":{"type":"string","title":"Fragment"}},"type":"object","required":["seal_id","share_code","fragment"],"title":"IntakeFulfil"},"IntakeView":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","enum":["pending","fulfilled","expired"],"title":"Status"},"label":{"type":"string","title":"Label"},"what":{"type":"string","title":"What"},"where_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Where Url"},"kind":{"type":"string","title":"Kind"},"recipient_bundle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Bundle","description":"The requester's bundle, base64url (null on a request before C1)."},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"share_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Path","description":"`/s/{seal}/{code}` without a fragment, only while fulfilled and not expired."},"fragment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fragment","description":"The link's key wrapped to the requester's bundle, with `share_path`."}},"type":"object","required":["id","status","label","what","where_url","kind","recipient_bundle","expires_at"],"title":"IntakeView"},"MigrateRequest":{"properties":{"old_address":{"type":"string","title":"Old Address","description":"The address before C1: the X25519 key, 43 base64url characters."},"old_proof":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Old Proof","description":"`<ts>.<token>` the old key opened; without it, the challenge."}},"type":"object","required":["old_address"],"title":"MigrateRequest"},"Migrated":{"properties":{"moved":{"type":"integer","title":"Moved","description":"Rows of the old inbox now under this address; zero is a success."}},"type":"object","required":["moved"],"title":"Migrated"},"MultipartAbortRequest":{"properties":{"upload_id":{"type":"string","title":"Upload Id","description":"Upload ID to abort"},"discard_file":{"type":"boolean","title":"Discard File","description":"Also revoke the file row, if its upload never completed","default":false}},"type":"object","required":["upload_id"],"title":"MultipartAbortRequest","description":"Request to abort multipart upload."},"MultipartAbortResponse":{"properties":{"status":{"type":"string","title":"Status","description":"'aborted' on success"}},"type":"object","required":["status"],"title":"MultipartAbortResponse","description":"Response from multipart abort."},"MultipartBatchPartsRequest":{"properties":{"upload_id":{"type":"string","title":"Upload Id","description":"Upload ID from init"},"part_numbers":{"items":{"type":"integer"},"type":"array","maxItems":100,"minItems":1,"title":"Part Numbers","description":"Part numbers (1-10000)"}},"type":"object","required":["upload_id","part_numbers"],"title":"MultipartBatchPartsRequest","description":"Request to get presigned URLs for multiple parts at once."},"MultipartBatchPartsResponse":{"properties":{"urls":{"additionalProperties":{"type":"string"},"type":"object","title":"Urls","description":"Map of part_number → presigned_url"}},"type":"object","required":["urls"],"title":"MultipartBatchPartsResponse","description":"Response with presigned URLs for multiple parts."},"MultipartCompletePartInfo":{"properties":{"part_number":{"type":"integer","title":"Part Number"},"etag":{"type":"string","title":"Etag"}},"type":"object","required":["part_number","etag"],"title":"MultipartCompletePartInfo","description":"Part info for complete request."},"MultipartCompleteRequest":{"properties":{"upload_id":{"type":"string","title":"Upload Id","description":"Upload ID"},"parts":{"items":{"$ref":"#/components/schemas/MultipartCompletePartInfo"},"type":"array","title":"Parts","description":"List of uploaded parts with ETags"},"on_conflict":{"type":"string","enum":["fail","keep_both"],"title":"On Conflict","description":"A version whose base another version closed first: 412, or keep as a second head","default":"fail"}},"type":"object","required":["upload_id","parts"],"title":"MultipartCompleteRequest","description":"Request to complete multipart upload; the row keeps its registered `sha256_hex`."},"MultipartCompleteResponse":{"properties":{"version_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version Of"},"head":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Head"},"conflict_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conflict With"},"status":{"type":"string","title":"Status","description":"'verified' — assembled and checked; 'accepted' — queued, poll /upload/status"}},"type":"object","required":["status"],"title":"MultipartCompleteResponse","description":"Response from multipart complete."},"MultipartInitRequest":{"properties":{"seal_id":{"type":"string","title":"Seal Id","description":"Seal ID"},"content_type":{"type":"string","title":"Content Type","description":"MIME type"},"content_length":{"type":"integer","exclusiveMinimum":0.0,"title":"Content Length","description":"Ciphertext length: the registered size"},"file_id":{"type":"string","format":"uuid","title":"File Id","description":"The file row from registration"},"part_size":{"anyOf":[{"type":"integer","maximum":268435456.0,"minimum":5242880.0},{"type":"null"}],"title":"Part Size","description":"Client part size in bytes (defaults to the server's)"}},"type":"object","required":["seal_id","content_type","content_length","file_id"],"title":"MultipartInitRequest","description":"Request to initialize a part-based multipart upload."},"MultipartInitResponse":{"properties":{"upload_id":{"type":"string","title":"Upload Id","description":"Unique upload ID for this multipart session"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key","description":"Storage key (internal, for debugging)"},"part_size":{"type":"integer","title":"Part Size","description":"Recommended part size in bytes"}},"type":"object","required":["upload_id","part_size"],"title":"MultipartInitResponse","description":"Response from multipart init."},"MultipartPartInfo":{"properties":{"part_number":{"type":"integer","title":"Part Number"},"etag":{"type":"string","title":"Etag"},"size":{"type":"integer","title":"Size"}},"type":"object","required":["part_number","etag","size"],"title":"MultipartPartInfo","description":"Information about an uploaded part."},"MultipartStatusResponse":{"properties":{"upload_id":{"type":"string","title":"Upload Id"},"seal_id":{"type":"string","title":"Seal Id"},"content_length":{"type":"integer","title":"Content Length"},"content_type":{"type":"string","title":"Content Type"},"part_size":{"type":"integer","title":"Part Size"},"total_parts":{"type":"integer","title":"Total Parts"},"parts":{"items":{"$ref":"#/components/schemas/MultipartPartInfo"},"type":"array","title":"Parts"},"status":{"type":"string","title":"Status"},"expires_at":{"type":"string","title":"Expires At"}},"type":"object","required":["upload_id","seal_id","content_length","content_type","part_size","total_parts","parts","status","expires_at"],"title":"MultipartStatusResponse","description":"Response from multipart status check."},"OTPVerify":{"properties":{"code":{"type":"string","maxLength":10,"minLength":6,"title":"Code"},"file_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"File Id","description":"A file of an organization's share link"},"share_code":{"anyOf":[{"type":"string","maxLength":4},{"type":"null"}],"title":"Share Code","description":"The share link the file is opened by"}},"type":"object","required":["code"],"title":"OTPVerify","description":"OTP verification request"},"ProofChallenge":{"properties":{"address":{"type":"string","title":"Address"},"ts":{"type":"integer","title":"Ts","description":"Decimal Unix second the challenge was made at."},"challenge":{"type":"string","title":"Challenge","description":"The server's HMAC of the address and `ts`, 43 characters, for the identity key to sign (seal_address.json proof); for the old address of a migration, a token sealed to its X25519 key."},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["address","ts","challenge","expires_at"],"title":"ProofChallenge"},"ProofRequest":{"properties":{"address":{"type":"string","title":"Address","description":"The address to prove, 43 base64url characters."}},"type":"object","required":["address"],"title":"ProofRequest"},"PushKey":{"properties":{"vapid_public":{"type":"string","title":"Vapid Public","description":"`applicationServerKey`: the uncompressed P-256 point, base64url."}},"type":"object","required":["vapid_public"],"title":"PushKey"},"PushSubscription":{"properties":{"to":{"type":"string","title":"To","description":"The address whose inbox pushes."},"endpoint":{"type":"string","maxLength":2048,"title":"Endpoint","description":"The browser's push endpoint, on a listed push service."}},"type":"object","required":["to","endpoint"],"title":"PushSubscription"},"SealCreate":{"properties":{"owner_token_hash":{"anyOf":[{"type":"string","maxLength":64,"minLength":64,"pattern":"^[a-f0-9]{64}$"},{"type":"null"}],"title":"Owner Token Hash","description":"SHA256 hex hash of client-generated owner_token (anonymous seals)"},"files_meta":{"items":{"$ref":"#/components/schemas/FileMeta"},"type":"array","maxItems":30,"minItems":0,"title":"Files Meta"},"ttl":{"anyOf":[{"type":"string","pattern":"^(5m|30m|1h|1d|7d)$"},{"type":"null"}],"title":"Ttl"},"otp":{"anyOf":[{"type":"string","maxLength":10,"minLength":6},{"type":"null"}],"title":"Otp"},"nda":{"type":"boolean","title":"Nda","default":false},"owner_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Owner Email"},"geo":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Geo"},"max_opens":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Max Opens"},"single_use":{"type":"boolean","title":"Single Use","default":false},"room_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Room Id","description":"Vault room ID (only for authenticated Vault users)"},"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier","description":"Paid tier for a seal above the free limit: 5gb | 50gb | 500gb (ttl 1d or 7d)"}},"type":"object","required":["files_meta"],"title":"SealCreate","description":"Create seal request"},"SealPublicMeta":{"properties":{"id":{"type":"string","title":"Id"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"nda_required":{"type":"boolean","title":"Nda Required"},"otp_required":{"type":"boolean","title":"Otp Required","default":false},"geo_allowlist":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Geo Allowlist"},"max_opens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Opens"},"single_use":{"type":"boolean","title":"Single Use"},"files":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Files"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"nda_session_valid":{"type":"boolean","title":"Nda Session Valid","default":true},"otp_session_valid":{"type":"boolean","title":"Otp Session Valid","default":true},"org_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Org Id"}},"type":"object","required":["id","expires_at","nda_required","geo_allowlist","max_opens","single_use","files"],"title":"SealPublicMeta","description":"Public seal metadata (no secrets) — recipient view of a seal.\n\nRecipient-only fields:\n  * `revoked_at`         — surface revocation cleanly (alongside 410\n                            for expired) so the SPA can render\n                            \"this link was revoked at …\".\n  * `nda_session_valid`  — P1-1: server-side check of the (httponly)\n                            NDA cookie against Redis.\n  * `otp_session_valid`  — P1-1: same for the OTP cookie.\n  * `org_id`             — the organization of an organization's seal, whose brand its\n                            share link shows (C0c Р8); null for an anonymous seal.\n\nDefaults `True` for the two `*_session_valid` flags match\n\"no gate required → no gate to pass\": legacy code paths that omit\nthem stay safely unblocked."},"SealResponse":{"properties":{"id":{"type":"string","title":"Id"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"nda_required":{"type":"boolean","title":"Nda Required"},"otp_required":{"type":"boolean","title":"Otp Required","default":false},"geo_allowlist":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Geo Allowlist"},"max_opens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Opens"},"single_use":{"type":"boolean","title":"Single Use"},"files":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Files"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier","default":"free"},"total_bytes":{"type":"integer","title":"Total Bytes","default":0}},"type":"object","required":["id","expires_at","nda_required","geo_allowlist","max_opens","single_use","files","created_at"],"title":"SealResponse","description":"Seal response — owner view (returned by create / append / owner-get).\n\nOwner-only fields:\n  * `created_at` — for the owner UI's \"created N min ago\" label.\n  * `tier`       — storage tier (`free`, `5gb`, `50gb`, `500gb`).\n  * `total_bytes`— quota usage display.\n\nOwner does NOT see recipient-side state (`revoked_at` is exposed via\na separate `revoke` endpoint; `*_session_valid` are recipient flags)."},"ShareCreate":{"properties":{"encrypted_blob":{"type":"string","maxLength":1468006,"title":"Encrypted Blob","description":"base64url encoded encrypted blob, max 1MB"},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"The recipient's address, 43 base64url characters."},"fragment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fragment","description":"The blob's key wrapped to the bundle of `to`: a packed sealed box v2, base64url."},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Label","description":"Who sends: an agent's name, shown as is."},"file_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":30},{"type":"null"}],"title":"File Ids","description":"The files of this seal the link opens; all of them when absent (C1 Р38)."}},"type":"object","required":["encrypted_blob"],"title":"ShareCreate","description":"`POST /seal/{id}/share`: the share blob, its scope and, optionally, its address."},"ShareLinkResponse":{"properties":{"share_code":{"type":"string","title":"Share Code"}},"type":"object","required":["share_code"],"title":"ShareLinkResponse","description":"Response for share link creation."},"SubscriptionCreate":{"properties":{"target":{"type":"string","maxLength":2048,"title":"Target","description":"Webhook URL the backend POSTs signed events to."},"events":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Events","description":"Subset of NotificationEvent values to receive; omit/null = all. Valid values: ['file.burned', 'file.opened', 'inbox.item', 'nda.signed', 'seal.burned', 'seal.expired']"},"reference":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Reference","description":"Opaque, NON-secret app handle echoed back on delivery so the receiver can route locally (e.g. a Telegram user id)."},"channel":{"type":"string","title":"Channel","description":"Delivery channel. Only 'webhook' is supported today.","default":"webhook"}},"type":"object","required":["target"],"title":"SubscriptionCreate","description":"Webhook subscription registration request."},"UploadInitiateRequest":{"properties":{"seal_id":{"type":"string","title":"Seal Id","description":"Seal ID (8-char base64url)"},"file_id":{"type":"string","format":"uuid","title":"File Id","description":"The file row from registration"},"content_type":{"type":"string","title":"Content Type","description":"MIME type","default":"application/octet-stream"},"content_length":{"type":"integer","maximum":10737418240.0,"exclusiveMinimum":0.0,"title":"Content Length","description":"Ciphertext length: the registered size"}},"type":"object","required":["seal_id","file_id","content_length"],"title":"UploadInitiateRequest","description":"Initiate presigned upload request: the row registration made, at the\nlength it registered (C1 Р24); its `sha256_hex` is the registration's."},"UploadInitiateResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"key":{"type":"string","title":"Key"},"expires_in":{"type":"integer","title":"Expires In"},"use_presigned":{"type":"boolean","title":"Use Presigned","default":true},"already_verified":{"type":"boolean","title":"Already Verified","default":false}},"type":"object","required":["upload_url","key","expires_in"],"title":"UploadInitiateResponse","description":"Presigned upload initiation response: one PUT with Content-MD5."},"UploadStatusResponse":{"properties":{"status":{"type":"string","title":"Status"},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified At"},"size":{"type":"integer","title":"Size","default":0}},"type":"object","required":["status"],"title":"UploadStatusResponse","description":"Upload verification status response"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}