{"openapi":"3.0.0","info":{"title":"SwipeFlow REST API Documentation","version":"v1"},"servers":[{"url":"https://api.swipeflow.io","description":"SwipeFlow API"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for programmatic access"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT token from either the login endpoints, or an OAuth 2.1 access token issued by this backend's OAuth 2.1 Authorization Server (auth.swipeflow.io) — both are accepted on the same header; OAuth tokens are additionally scope-restricted (see each operation)"}},"schemas":{"WebhookDeliveryStatus":{"type":"string","enum":["queued","running","retrying","succeeded","dead","held","gone","rejected","cancelled"],"description":"Lifecycle state of one delivery. `held` = suspended mid-flight, resumable via reactivate. `cancelled` = permanent (webhook disabled or the source event expired). `dead` = retries exhausted, abandoned.","example":"succeeded"},"DeliveryError":{"type":"object","nullable":true,"properties":{"kind":{"type":"string","enum":["transient","permanent"]},"category":{"type":"string","example":"http_5xx"},"status":{"type":"integer","nullable":true,"example":503},"message":{"type":"string","nullable":true}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","example":"507f1f77bcf86cd799439011"},"webhookId":{"type":"string","example":"507f1f77bcf86cd799439012"},"eventId":{"type":"string","description":"Public event id","example":"evt_507f1f77bcf86cd799439013"},"eventType":{"type":"string","example":"item.approved"},"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"},"attempts":{"type":"integer","example":2},"replayOf":{"type":"string","nullable":true,"description":"The delivery this one replays, when it is a replay"},"lastError":{"$ref":"#/components/schemas/DeliveryError"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time","nullable":true}}},"WebhookDeliveryAttempt":{"type":"object","properties":{"id":{"type":"string","example":"507f1f77bcf86cd799439011"},"n":{"type":"integer","description":"Attempt number, 1-based","example":1},"at":{"type":"string","format":"date-time"},"latencyMs":{"type":"integer","nullable":true},"status":{"type":"integer","nullable":true,"description":"HTTP response status code, when the request reached the receiver"},"success":{"type":"boolean"},"category":{"type":"string","nullable":true},"error":{"type":"string","nullable":true},"responseSnippet":{"type":"string","nullable":true}}},"WebhookDeliveryDetail":{"allOf":[{"$ref":"#/components/schemas/WebhookDelivery"},{"type":"object","properties":{"attemptsList":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryAttempt"}},"payload":{"description":"The source event's data — the request body this delivery sends","additionalProperties":true}}}]},"WebhookTest":{"type":"object","properties":{"id":{"type":"string","example":"507f1f77bcf86cd799439011"},"status":{"type":"string","enum":["queued","succeeded","failed"]},"statusCode":{"type":"integer","nullable":true,"description":"HTTP response status code, when the request reached the receiver"},"latencyMs":{"type":"integer","nullable":true},"error":{"type":"string","nullable":true},"responseSnippet":{"type":"string","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true}}},"WebhookDeliveryPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"nextCursor":{"type":"string","nullable":true},"hasMore":{"type":"boolean"}}},"ReplayDeliveriesRequest":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"},"since":{"type":"string","format":"date-time","description":"Only replay deliveries created at or after this time"}}},"ReplayDeliveriesResult":{"type":"object","properties":{"replayedCount":{"type":"integer"},"skippedCount":{"type":"integer","description":"Candidates skipped because their source event has already expired"}}},"BackfillRequest":{"type":"object","properties":{"after":{"type":"string","description":"Public event id to backfill strictly after (exclusive). Typically the webhook's reconciliation cursor.","example":"evt_507f1f77bcf86cd799439013"},"since":{"type":"string","format":"date-time","description":"Backfill events created at or after this time. Ignored when `after` is given."}}},"BackfillResult":{"type":"object","properties":{"enqueuedCount":{"type":"integer"}}},"ReconciliationEvent":{"type":"object","properties":{"id":{"type":"string","example":"evt_507f1f77bcf86cd799439013"},"type":{"type":"string","example":"item.approved"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":true}}},"ReconciliationEventPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ReconciliationEvent"}},"nextCursor":{"type":"string","nullable":true},"hasMore":{"type":"boolean"}}},"WebhookCursor":{"type":"object","properties":{"cursor":{"type":"string","nullable":true,"description":"Public event id watermark - everything at or before it is settled for this webhook. Null when nothing is safe to advance to yet.","example":"evt_507f1f77bcf86cd799439013"}}},"WebhookEvent":{"type":"string","enum":["item.created","item.updated","item.deleted","item.approved","item.rejected","item.change_requested","item.processed","project.trigger"],"description":"Webhook event type","example":"item.created"},"Webhook":{"type":"object","properties":{"id":{"type":"string","description":"Webhook ID","example":"507f1f77bcf86cd799439011"},"projectId":{"type":"string","description":"Project ID","example":"507f1f77bcf86cd799439012"},"name":{"type":"string","description":"Webhook name","example":"My Webhook"},"url":{"type":"string","format":"uri","description":"Webhook URL","example":"https://example.com/webhook"},"type":{"type":"string","enum":["user","dynamic"],"description":"Webhook type","example":"user"},"integrationProvider":{"type":"string","description":"Integration provider name","example":"zapier"},"integrationLink":{"type":"string","description":"Link to integration setup","example":"https://zapier.com/apps/myapp"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"},"description":"Events that trigger this webhook","example":["item.created","item.approved"]},"active":{"type":"boolean","description":"Whether the webhook is active","example":true},"triggerCount":{"type":"integer","description":"Number of times webhook has been triggered","example":42},"deliveryStatus":{"$ref":"#/components/schemas/WebhookDeliveryHealth"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp","example":"2024-01-01T00:00:00Z"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp","example":"2024-01-15T10:30:00Z"}}},"WebhookDeliveryHealth":{"type":"object","description":"System-owned delivery health, separate from the user-set `active` toggle.","properties":{"active":{"type":"boolean","description":"false means deliveries are suspended (sustained failure rate); reactivate to resume","example":true},"suspendedAt":{"type":"string","format":"date-time","nullable":true},"reason":{"type":"string","nullable":true,"example":"failure_rate"},"lastError":{"$ref":"#/components/schemas/DeliveryError"}}},"RotateWebhookSecretRequest":{"type":"object","properties":{"secret":{"type":"string","description":"Custom secret to rotate in. A cryptographically random one is generated when omitted."},"gracePeriodSeconds":{"type":"integer","description":"How long the previous secret keeps signing alongside the new one (as `X-Swipeflow-Signature-Previous`). 0 rotates immediately with no grace window.","default":86400,"example":86400}}},"RotateWebhookSecretResponse":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"The new secret, shown once. It is never returned again by any other endpoint.","example":"8f14e45fceea167a5a36dedd4bea2543"}}}]},"CreateWebhookRequest":{"type":"object","required":["url","events"],"properties":{"name":{"type":"string","description":"Webhook name","example":"My Webhook","minLength":1,"maxLength":100},"url":{"type":"string","format":"uri","description":"Webhook URL","example":"https://example.com/webhook"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"},"description":"Events to trigger the webhook","example":["item.created","item.approved"],"minItems":1},"secret":{"type":"string","description":"Secret for webhook signature","example":"my_secret_key"}}},"UpdateWebhookRequest":{"type":"object","properties":{"name":{"type":"string","description":"Webhook name","example":"Updated Webhook Name"},"url":{"type":"string","format":"uri","description":"Webhook URL","example":"https://example.com/new-webhook"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"},"description":"Events to trigger the webhook","example":["item.created","item.approved"]},"active":{"type":"boolean","description":"Whether the webhook is active","example":true},"secret":{"type":"string","description":"Secret for webhook signature","example":"new_secret_key"}}},"WebhookList":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"WebhookLogPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookLog"}},"nextCursor":{"type":"string","nullable":true},"hasMore":{"type":"boolean"}}},"WebhookLog":{"type":"object","properties":{"id":{"type":"string","description":"Log ID","example":"507f1f77bcf86cd799439011"},"webhookId":{"type":"string","description":"Webhook ID","example":"507f1f77bcf86cd799439012"},"deliveryId":{"type":"string","nullable":true,"description":"The delivery this log entry belongs to. Absent for legacy pre-delivery-worker log rows.","example":"507f1f77bcf86cd799439014"},"event":{"type":"string","description":"Event that triggered the webhook","example":"item.created"},"payload":{"type":"object","description":"Payload sent to webhook","example":{"itemId":"123","action":"created"}},"responseStatus":{"type":"integer","description":"HTTP response status code","example":200},"responseBody":{"type":"string","description":"Response body from webhook endpoint","example":"{\"success\":true}"},"error":{"type":"string","description":"Error message if delivery failed","example":"Connection timeout"},"timestamp":{"type":"string","format":"date-time","description":"Delivery timestamp","example":"2024-01-15T10:30:00Z"},"latency":{"type":"integer","description":"Request latency in milliseconds","example":150}}},"UserAddress":{"type":"object","properties":{"street":{"type":"string","example":"123 Main St"},"city":{"type":"string","example":"San Francisco"},"state":{"type":"string","example":"CA"},"postalCode":{"type":"string","example":94102},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country the post goes to","example":"US"}}},"RecentProject":{"type":"object","properties":{"projectId":{"type":"string","description":"Project ID","example":"507f1f77bcf86cd799439011"},"name":{"type":"string","description":"Project name","example":"My Project"},"pendingCount":{"type":"number","description":"Number of pending items","example":5},"lastAccessedAt":{"type":"string","format":"date-time","description":"Last access timestamp","example":"2024-01-15T10:30:00Z"}}},"UserProfile":{"type":"object","properties":{"id":{"type":"string","description":"User ID","example":"507f1f77bcf86cd799439011"},"email":{"type":"string","format":"email","description":"User's email address","example":"user@example.com"},"firstName":{"type":"string","description":"User's first name","example":"John"},"lastName":{"type":"string","description":"User's last name","example":"Doe"},"picture":{"type":"string","description":"URL to user's profile picture","example":"https://example.com/profile.jpg"},"phoneNumber":{"type":"string","description":"User's phone number","example":1234567890},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country of the user, independent of the address","example":"US"},"address":{"$ref":"#/components/schemas/UserAddress"},"membership":{"type":"string","enum":["free","pro","team"],"description":"User's membership tier","example":"pro"},"emailVerified":{"type":"boolean","description":"Whether email is verified","example":true},"isAdmin":{"type":"boolean","description":"Indicates whether the user has administrative privileges. Used for UI purposes only — all admin actions are validated server-side.\n","example":false},"createdAt":{"type":"string","format":"date-time","description":"Account creation timestamp","example":"2024-01-01T00:00:00Z"},"firstLoginAt":{"type":"string","format":"date-time","description":"First login timestamp","example":"2024-01-01T00:00:00Z"},"lastLoginAt":{"type":"string","format":"date-time","description":"Last login timestamp","example":"2024-01-01T00:00:00Z"}}},"PublicUserProfile":{"type":"object","properties":{"id":{"type":"string","description":"User ID","example":"507f1f77bcf86cd799439011"},"firstName":{"type":"string","description":"User's first name","example":"John"},"lastName":{"type":"string","description":"User's last name","example":"Doe"},"picture":{"type":"string","description":"URL to user's profile picture","example":"https://example.com/profile.jpg"}}},"UpdateUserProfileRequest":{"type":"object","properties":{"firstName":{"type":"string","description":"User's first name","example":"John","minLength":1,"maxLength":50},"lastName":{"type":"string","description":"User's last name","example":"Doe","minLength":1,"maxLength":50},"phoneNumber":{"type":"string","description":"User's phone number","example":1234567890},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country of the user, independent of the address","example":"US"},"address":{"$ref":"#/components/schemas/UserAddress"}}},"UpdateProfilePicture":{"type":"object","properties":{"picture":{"type":"string","description":"URL to the new profile picture","example":"https://storage.googleapis.com/bucket/profile.jpg"}}},"DeleteAccountRequest":{"type":"object","properties":{"feedback":{"type":"string","description":"Optional feedback about reason for account deletion","example":"No longer needed","maxLength":1000}}},"RecentProjects":{"type":"array","items":{"$ref":"#/components/schemas/RecentProject"}},"MediaSettings":{"type":"object","properties":{"autoPlay":{"type":"boolean","description":"Auto-play media","example":true},"loopVideos":{"type":"boolean","description":"Loop videos","example":true},"mutePlayer":{"type":"boolean","description":"Mute player by default","example":false},"showControls":{"type":"boolean","description":"Show media controls","example":true},"preloadMode":{"type":"string","enum":["off","wifi","auto"],"description":"Whether the app loads upcoming items' media ahead of time. auto preloads on every connection but throttles itself on mobile data and data saver, wifi preloads only on Wi-Fi or other unmetered connections, off never preloads. Defaults to auto.","example":"auto"}}},"NotificationPreference":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/NotificationType"},"methods":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryMethod"},"description":"Delivery methods","example":["PUSH","EMAIL"]},"enabled":{"type":"boolean","description":"Whether notification is enabled","example":true}}},"Settings":{"type":"object","properties":{"theme":{"type":"string","description":"UI theme","example":"dark"},"autoDarkMode":{"type":"boolean","description":"Automatically switch to dark mode based on system preference","example":true},"mediaSettings":{"$ref":"#/components/schemas/MediaSettings"},"notificationPreferences":{"type":"array","items":{"$ref":"#/components/schemas/NotificationPreference"}},"notifySelfActions":{"type":"boolean","description":"Whether to notify the user about actions they themselves performed (excludes API key, OAuth and MCP activity, which always notifies). Defaults to false.","example":false},"autoGridLandscape":{"type":"boolean","description":"Whether the item queue auto-switches to grid view in landscape orientation (mobile/tablet) or on desktop-sized screens","example":true},"seenHints":{"type":"object","additionalProperties":{"type":"string","format":"date-time"},"description":"One-off UI hints the user has already seen, keyed by hint id, valued by when it was first seen","example":{"carousel-navigation":"2026-09-29T10:00:00Z"}}}},"SeenHintsResponse":{"type":"object","properties":{"seenHints":{"type":"object","additionalProperties":{"type":"string","format":"date-time"},"example":{"carousel-navigation":"2026-09-29T10:00:00Z"}}}},"UpdateSettingsRequest":{"type":"object","properties":{"theme":{"type":"string","description":"UI theme preference","example":"dark"},"autoDarkMode":{"type":"boolean","description":"Automatically switch to dark mode based on system preference","example":true},"mediaSettings":{"$ref":"#/components/schemas/MediaSettings"},"notificationPreferences":{"type":"array","items":{"$ref":"#/components/schemas/NotificationPreference"}},"notifySelfActions":{"type":"boolean","description":"Whether to notify the user about actions they themselves performed (excludes API key, OAuth and MCP activity, which always notifies). Defaults to false.","example":false},"autoGridLandscape":{"type":"boolean","description":"Whether the item queue auto-switches to grid view in landscape orientation (mobile/tablet) or on desktop-sized screens","example":true}}},"ProjectMember":{"type":"object","properties":{"role":{"$ref":"#/components/schemas/ProjectRole"},"user":{"$ref":"#/components/schemas/PublicUserProfile","description":"Populated user details for the member"}}},"ProjectTrigger":{"type":"object","properties":{"id":{"type":"string","description":"Trigger ID","example":"507f1f77bcf86cd799439011"},"name":{"type":"string","description":"Display name for the trigger","example":"Content Published"},"event":{"type":"string","description":"Event name sent in webhook payload","example":"content_published"}}},"Project":{"type":"object","properties":{"id":{"type":"string","description":"Project ID","example":"507f1f77bcf86cd799439011"},"name":{"type":"string","description":"Project name","example":"My Project"},"description":{"type":"string","description":"Project description","example":"A sample project for content approval"},"members":{"type":"array","items":{"$ref":"#/components/schemas/ProjectMember"}},"isArchived":{"type":"boolean","description":"Whether the project is archived","example":false},"isStarred":{"type":"boolean","description":"Whether the project is starred","example":true},"totalItems":{"type":"integer","description":"Total number of items in the project","example":42},"pendingItems":{"type":"integer","description":"Number of pending items","example":5},"approvedItems":{"type":"integer","description":"Number of approved items","example":30},"lastItemPostedAt":{"type":"string","format":"date-time","description":"Timestamp of last item posted","example":"2024-01-15T10:30:00Z"},"triggers":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTrigger"}},"createdAt":{"type":"string","format":"date-time","description":"Project creation timestamp","example":"2024-01-01T00:00:00Z"},"updatedAt":{"type":"string","format":"date-time","description":"Project last update timestamp","example":"2024-01-15T10:30:00Z"},"mediaUsage":{"type":"object","description":"Only present on the single-project detail read (not the list). usedBytes counts this project's pending, uploaded, and in-flight-import media — informational; the full quota + limit breakdown is at GET /v1/projects/{projectId}/media/usage.","properties":{"usedBytes":{"type":"integer","example":1048576}}}}},"CreateProjectRequest":{"type":"object","required":["name","description"],"properties":{"name":{"type":"string","description":"Project name","example":"My New Project","minLength":1,"maxLength":100},"description":{"type":"string","description":"Project description","example":"A project for managing content approvals","minLength":1,"maxLength":500}}},"UpdateProjectRequest":{"type":"object","properties":{"name":{"type":"string","description":"Project name","example":"Updated Project Name","minLength":1,"maxLength":100},"description":{"type":"string","description":"Project description","example":"Updated project description","minLength":1,"maxLength":500}}},"UpdateProjectSettingsRequest":{"type":"object","properties":{"isArchived":{"type":"boolean","description":"Archive status","example":false},"isStarred":{"type":"boolean","description":"Starred status","example":true}}},"AddProjectMemberRequest":{"type":"object","required":["userId","role"],"properties":{"userId":{"type":"string","description":"Member's user ID","example":"507f1f77bcf86cd799439011"},"role":{"$ref":"#/components/schemas/ProjectRole"}}},"UpdateProjectMemberRequest":{"type":"object","required":["role"],"properties":{"role":{"$ref":"#/components/schemas/ProjectRole"}}},"CreateTriggerRequest":{"type":"object","required":["name","event"],"properties":{"name":{"type":"string","description":"Display name for the manual trigger","example":"Content Published","minLength":1,"maxLength":100},"event":{"type":"string","description":"Event name to be sent in the webhook payload","example":"content_published","pattern":"^[a-z0-9_]+$"}}},"UpdateTriggerRequest":{"type":"object","properties":{"name":{"type":"string","description":"Display name for the manual trigger","example":"Content Published","minLength":1,"maxLength":100},"event":{"type":"string","description":"Event name to be sent in the webhook payload","example":"content_published","pattern":"^[a-z0-9_]+$"}}},"ProjectList":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"},"favorites":{"type":"array","description":"The caller's starred, non-archived projects. Only present on the unfiltered first page.","items":{"$ref":"#/components/schemas/Project"}}}},"ToggleArchived":{"type":"object","properties":{"archived":{"type":"boolean","description":"New archived status","example":true}}},"ToggleStarred":{"type":"object","properties":{"starred":{"type":"boolean","description":"New starred status","example":true}}},"RunTriggerRequest":{"type":"object","properties":{"payload":{"type":"object","additionalProperties":true,"description":"Additional data to include in the webhook payload","example":{"context":"Manual QA run","initiatedBy":"qa-team"}}}},"RunTrigger":{"type":"object","properties":{"message":{"type":"string","description":"Result message","example":"Trigger executed successfully"},"trigger":{"type":"object","properties":{"id":{"type":"string","description":"Trigger identifier","example":"507f1f77bcf86cd799439011"},"name":{"type":"string","description":"Trigger name","example":"Content Published"},"event":{"type":"string","description":"Event dispatched to the webhook","example":"content_published"}}},"payload":{"type":"object","additionalProperties":true,"description":"Payload delivered to the webhook"},"executedAt":{"type":"string","format":"date-time","description":"Trigger execution timestamp","example":"2024-03-20T09:15:00Z"}}},"NotificationType":{"type":"string","enum":["ITEM_CREATED","ITEM_UPDATED","ITEM_DELETED","ITEM_APPROVED","ITEM_REJECTED","ITEM_CHANGE_REQUESTED","ITEM_PROCESSED","PROJECT_CREATED","PROJECT_UPDATED","PROJECT_DELETED"],"description":"Type of notification","example":"ITEM_CREATED"},"DeliveryMethod":{"type":"string","enum":["PUSH","EMAIL"],"description":"Notification delivery method","example":"PUSH"},"Notification":{"type":"object","properties":{"id":{"type":"string","description":"Notification ID","example":"507f1f77bcf86cd799439011"},"userId":{"type":"string","description":"User ID","example":"507f1f77bcf86cd799439012"},"type":{"$ref":"#/components/schemas/NotificationType"},"title":{"type":"string","description":"Notification title","example":"New Item Created"},"body":{"type":"string","description":"Notification body","example":"John Doe created a new item"},"data":{"type":"object","additionalProperties":true,"description":"Additional notification data","example":{"itemId":"507f1f77bcf86cd799439012","projectId":"507f1f77bcf86cd799439013"}},"read":{"type":"boolean","description":"Whether notification has been read","example":false},"readAt":{"type":"string","format":"date-time","description":"When notification was read","example":"2024-01-15T10:30:00Z"},"deliveredVia":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryMethod"},"description":"Delivery methods used","example":["PUSH"]},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp","example":"2024-01-15T10:30:00Z"}}},"NotificationList":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"UnreadCount":{"type":"object","properties":{"count":{"type":"integer","description":"Number of unread notifications","example":5}}},"RegisterDeviceRequest":{"type":"object","required":["deviceToken","deviceType","deviceName"],"properties":{"deviceToken":{"type":"string","description":"Device push notification token","example":"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"},"deviceType":{"type":"string","enum":["ios","android","web"],"description":"Device platform","example":"ios"},"deviceName":{"type":"string","description":"Device name","example":"iPhone 13"}}},"MarkAsRead":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether operation was successful","example":true}}},"MediaDescriptor":{"type":"object","description":"The standalone media API's shared shape — single lookup, project listing, and the upload-ticket/confirm responses. `url`/`urlExpiresAt` are null until `uploaded`.\n","properties":{"id":{"type":"string","example":"66f1f77bcf86cd799439099"},"fileName":{"type":"string","example":"chart.png"},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","example":245678},"url":{"type":"string","nullable":true,"description":"Signed URL for the bytes, once uploaded. Do not cache or persist it."},"urlExpiresAt":{"type":"string","format":"date-time","nullable":true,"description":"Absolute expiry for `url`. Every row in one response shares the same value."},"projectId":{"type":"string","example":"507f1f77bcf86cd799439012"},"status":{"type":"string","enum":["upload_pending","import_pending","uploaded","delete_pending","deleted"]},"createdAt":{"type":"string","format":"date-time"},"uploadedAt":{"type":"string","format":"date-time","nullable":true},"uploadedBy":{"type":"string","description":"The user who uploaded the bytes — provenance only","not the entity billed for them.":null},"itemIds":{"type":"array","items":{"type":"string"},"description":"Items and item versions this object is attached to. Reusable across them within the project."},"etag":{"type":"string","nullable":true,"description":"Opaque storage fingerprint — not a portable MD5 guarantee."},"md5":{"type":"string","nullable":true,"description":"Base64 MD5","present only when the client declared one at ticket time.":null},"importError":{"type":"string","nullable":true,"description":"Sanitized terminal failure detail for a URL import, retained for diagnosis."},"importErrorCode":{"type":"string","nullable":true,"description":"Stable machine-readable URL-import failure code; use this instead of parsing importError."},"importFailedAt":{"type":"string","format":"date-time","nullable":true}}},"MediaListPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MediaDescriptor"}},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next page, or null when there are no more rows"},"hasMore":{"type":"boolean"}}},"ItemVersion":{"type":"object","properties":{"id":{"type":"string","description":"Version ID","example":"507f1f77bcf86cd799439011"},"itemId":{"type":"string","description":"Item ID","example":"507f1f77bcf86cd799439012"},"projectId":{"type":"string","description":"Project ID","example":"507f1f77bcf86cd799439013"},"version":{"type":"integer","description":"Version number","example":2},"title":{"type":"string","description":"Item title at this version","example":"Updated Title"},"description":{"type":"string","description":"Item description at this version","example":"Updated description"},"content":{"$ref":"#/components/schemas/ItemContent"},"media":{"type":"array","description":"This version's own frozen snapshot of attached media — not affected by later versions' attachments.","items":{"$ref":"#/components/schemas/AttachedMedia"}},"metadata":{"type":"object","additionalProperties":true,"description":"Custom metadata at this version","example":{"author":"John Doe"}},"status":{"type":"string","enum":["pending","approved","rejected","change_requested"],"description":"Status at this version","example":"pending"},"decisions":{"type":"array","items":{"$ref":"#/components/schemas/ItemDecision"}},"createdBy":{"type":"string","description":"User who created this version","example":"507f1f77bcf86cd799439014"},"actorName":{"type":"string","description":"Display name of who submitted this version (user, API key, or OAuth client name)","example":"Claude"},"createdAt":{"type":"string","format":"date-time","description":"When this version was created / submitted (v1 == item creation)","example":"2024-01-15T10:30:00Z"}}},"CreateItemVersionRequest":{"type":"object","properties":{"title":{"type":"string","description":"Updated item title","example":"New Version Title","maxLength":200},"description":{"type":"string","description":"Updated item description","example":"New version description","maxLength":1000},"content":{"$ref":"#/components/schemas/ItemContent"},"media":{"type":"array","description":"Media ids to attach to the new version. Omit to carry over the previous version's media exactly, including when `content` is also changing — media is never inferred from a content change. Pass `[]` to explicitly detach everything, or a new list to replace it — any ref embedded in *this request's* `content` is included automatically regardless of this field, but a ref only present in the *previous* (unchanged, carried-over) content is not rescanned, so `[]` reliably detaches even if old content still mentions something.\n","items":{"type":"string"},"example":["66f1f77bcf86cd799439099"]},"metadata":{"type":"object","additionalProperties":true,"description":"Updated item metadata","example":{"author":"Jane Doe","version":"2.0"}}}},"ItemVersionList":{"type":"object","properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/ItemVersion"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"ItemContent":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/ContentType"},"data":{"type":"string","description":"For text/html: the content itself, which may embed one or more canonical `media://<id>` refs inline — e.g. markdown `![alt](media://66f1f77bcf86cd799439099)`. For image/video/audio: a single media ref, or an external https URL. For carousel: JSON `{\"items\": [{\"type\": \"image\"|\"video\", \"url\": \"<media ref or https URL>\", \"caption\"?: \"...\", \"alt\"?: \"...\"}]}` with 1–20 items (caption/alt at most 500 characters). On create/version-create it may also be sent as that object, or as a bare array of items; it is always stored and returned as the JSON string. Refs are **not** substituted for a live URL here unless the request opted in with `resolveMedia=true` — see `media[]` for why.\n","example":"This is sample content"}}},"AttachedMedia":{"type":"object","description":"One file attached to an item or item version — either referenced inline in `content.data`, or attached explicitly via `media` on create/version-create without necessarily appearing in the content body at all.\n","properties":{"id":{"type":"string","description":"Media id","example":"66f1f77bcf86cd799439099"},"fileName":{"type":"string","description":"Original filename, if the uploader gave one","example":"chart.png"},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","description":"Size in bytes","example":245678},"url":{"type":"string","description":"Signed URL for the bytes, always present. Expires at `urlExpiresAt`; do not cache or persist it — refetch the containing item/list to refresh.","example":"https://abc123.r2.cloudflarestorage.com/swipeflow-media/...&X-Amz-Signature=..."},"urlExpiresAt":{"type":"string","format":"date-time","description":"Absolute expiry for `url`. Every media entry in one response shares the same value.","example":"2024-01-15T11:30:00Z"}}},"ItemDecision":{"type":"object","properties":{"userId":{"type":"string","description":"User who made the decision","example":"507f1f77bcf86cd799439011"},"decision":{"$ref":"#/components/schemas/DecisionType"},"comment":{"type":"string","description":"Optional comment for the decision","example":"Looks good!"},"timestamp":{"type":"string","format":"date-time","description":"When the decision was made","example":"2024-01-15T10:30:00Z"},"targetVersion":{"type":"integer","description":"Target version for change request (only present when decision is 'change_requested')","example":1,"minimum":1},"actorName":{"type":"string","description":"Display name of who made the decision — the user, or the API key / OAuth client name when acting on their behalf","example":"Zapier"}}},"Item":{"type":"object","properties":{"id":{"type":"string","description":"Item ID","example":"507f1f77bcf86cd799439011"},"projectId":{"type":"string","description":"Project ID","example":"507f1f77bcf86cd799439012"},"title":{"type":"string","description":"Item title","example":"New Blog Post"},"description":{"type":"string","description":"Item description","example":"A blog post about API design"},"content":{"$ref":"#/components/schemas/ItemContent"},"media":{"type":"array","description":"Media currently attached to this item — always a superset of whatever `content.data` embeds, plus anything attached without being inline. Reflects the *current* state only, same as `content`; an `ItemVersion`'s own `media` is that version's frozen snapshot instead.\n","items":{"$ref":"#/components/schemas/AttachedMedia"}},"metadata":{"type":"object","additionalProperties":true,"description":"Custom metadata","example":{"author":"John Doe","category":"Technology"}},"status":{"$ref":"#/components/schemas/ItemStatus"},"decisions":{"type":"array","items":{"$ref":"#/components/schemas/ItemDecision"}},"processed":{"allOf":[{"$ref":"#/components/schemas/ItemProcessed"}],"nullable":true,"description":"Set once the requester has confirmed it acted on the decision (see PUT /:itemId/processed). Only ever present on 'approved'/'rejected' items — null everywhere else, including for a 'change_requested' item, which closes out via a new version instead of this."},"createdBy":{"type":"string","description":"User who created the item","example":"507f1f77bcf86cd799439013"},"actorName":{"type":"string","description":"Display name of who authored the current version (user, API key, or OAuth client name)","example":"Claude"},"version":{"type":"integer","description":"Version number","example":1},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp","example":"2024-01-15T10:30:00Z"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp","example":"2024-01-15T10:30:00Z"},"expiresAt":{"type":"string","format":"date-time","description":"Expiration timestamp (optional)","example":"2024-02-15T10:30:00Z"},"versions":{"type":"array","description":"Array of all versions (only if includeVersions=true)","items":{"$ref":"#/components/schemas/ItemVersion"}},"idempotencyKey":{"type":"string","description":"The idempotency key the item was created with, if any","example":"run-42-review-1"}}},"CreateItemRequest":{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"Item title","example":"New Content Item","minLength":1,"maxLength":200},"description":{"type":"string","description":"Item description","example":"Description of the content item","maxLength":1000},"content":{"$ref":"#/components/schemas/ItemContent"},"media":{"type":"array","description":"Media ids to attach (from `POST .../media-uploads/:id/confirm`). Not inferred from `content` — any ref actually embedded in `content.data` is included automatically regardless of this field, but an attachment that isn't embedded inline only exists because it's listed here.\n","items":{"type":"string"},"example":["66f1f77bcf86cd799439099"]},"metadata":{"type":"object","additionalProperties":true,"description":"Custom metadata","example":{"author":"John Doe","tags":["tech","api"]}},"idempotencyKey":{"type":"string","description":"Client-supplied key for safe retries, scoped to the project. Replaying a create request with the same key returns the original item rather than creating a duplicate. Must contain a non-whitespace character.","example":"run-42-review-1","minLength":1,"maxLength":255,"pattern":"^.*\\S.*$"}}},"ItemProcessed":{"type":"object","description":"Requester-side reconciliation record — proof the decision was actually acted on, not just delivered (by webhook or by reading it), and the durable backstop for finding anything a missed or unretried webhook delivery left stranded.","properties":{"processedAt":{"type":"string","format":"date-time","description":"When the item was marked processed","example":"2024-01-15T11:00:00Z"},"processedBy":{"type":"string","description":"User who marked the item processed","example":"507f1f77bcf86cd799439013"},"action":{"type":"string","description":"Optional free-text note on what was done with the decision","example":"Published to CMS"},"actorName":{"type":"string","description":"Display name of who marked it processed (user, API key, or OAuth client name)","example":"Claude"}}},"ProcessItemRequest":{"type":"object","properties":{"action":{"type":"string","description":"Optional free-text note on what was done with the decision","example":"Published to CMS","maxLength":200}}},"ItemStatusSnapshot":{"type":"object","description":"Slim status projection of an item — status, version, and the latest decision only, without content or metadata. The cheap alternative to fetching the full item when polling.","properties":{"id":{"type":"string","description":"Item ID","example":"507f1f77bcf86cd799439011"},"status":{"$ref":"#/components/schemas/ItemStatus"},"version":{"type":"integer","description":"Version number","example":1},"decision":{"allOf":[{"$ref":"#/components/schemas/ItemDecision"}],"nullable":true,"description":"The most recent decision, or null if none has been made yet"}}},"ItemStatusSnapshotList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemStatusSnapshot"}}}},"UpdateItemDecisionRequest":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["approved","rejected","change_requested"],"description":"Decision status","example":"approved"},"comment":{"type":"string","description":"Comment for the decision (required when decision is 'change_requested')","example":"Please update the introduction section","maxLength":1000},"targetVersion":{"type":"integer","description":"Target version for change request (only valid when decision is 'change_requested'). If omitted, applies to latest version.","example":1,"minimum":1}}},"ItemList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"ItemStatus":{"type":"string","enum":["pending","approved","rejected","change_requested"],"description":"Status of an item in the approval workflow","example":"pending"},"ContentType":{"type":"string","enum":["text","html","image","video","audio","carousel"],"description":"Type of content in an item. `carousel` holds up to 20 image/video slides — see ItemContent.data","example":"text"},"DecisionType":{"type":"string","enum":["approved","rejected","change_requested"],"description":"Type of decision made on an item","example":"approved"},"ProjectRole":{"type":"string","enum":["owner","admin","editor","viewer"],"description":"Role of a member in a project:\n- owner: Project creator, cannot be removed\n- admin: Can manage members and settings\n- editor: Can create and edit items\n- viewer: Read-only access\n","example":"editor"},"ActivityType":{"type":"string","enum":["created","updated","approved","rejected","change_requested","deleted","commented"],"description":"Type of activity performed on an item","example":"created"},"AuditEventType":{"type":"string","enum":["ITEM_CREATED","ITEM_APPROVED","ITEM_REJECTED","ITEM_CHANGE_REQUESTED","ITEM_VERSION_CREATED","ITEM_DELETED","PROJECT_CREATED","PROJECT_UPDATED","PROJECT_DELETED","PROJECT_MEMBER_ADDED","PROJECT_MEMBER_ROLE_CHANGED","PROJECT_MEMBER_REMOVED","WEBHOOK_CREATED","WEBHOOK_UPDATED","WEBHOOK_DELETED","WEBHOOK_SECRET_ROTATED","WEBHOOK_REACTIVATED","WEBHOOK_DELIVERY_REPLAYED","API_KEY_CREATED","API_KEY_UPDATED","API_KEY_REVOKED","COMMENT_ADDED","COMMENT_EDITED","COMMENT_DELETED","USER_PROFILE_UPDATED","USER_ROLE_CHANGED","USER_DELETED","SETTINGS_UPDATED","MEMBERSHIP_CHANGED","USER_LOGIN","USER_LOGIN_FAILED"],"description":"Type of a recorded audit event. COMMENT_* and USER_LOGIN* values are reserved and not emitted yet.\n","example":"ITEM_APPROVED"},"WebhookType":{"type":"string","enum":["user","dynamic"],"description":"Type of webhook","example":"user"},"NotificationPriority":{"type":"string","enum":["low","medium","high","urgent"],"description":"Priority level of a notification","example":"medium"},"MembershipTier":{"type":"string","enum":["free","pro","team"],"description":"User membership tier","example":"free"},"AuthProvider":{"type":"string","enum":["email","google","github","apple"],"description":"Authentication provider","example":"email"},"PaginationMeta":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of items","example":100},"page":{"type":"integer","description":"Current page number","example":1},"pages":{"type":"integer","description":"Total number of pages","example":10},"limit":{"type":"integer","description":"Items per page","example":10}}},"ErrorResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Error message","example":"Resource not found"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}}}},"SuccessResponse":{"type":"object","properties":{"message":{"type":"string","description":"Success message","example":"Operation completed successfully"}}},"AuditEventActor":{"type":"object","properties":{"actorType":{"type":"string","enum":["user","api_key","oauth_client","system"],"example":"user"},"userId":{"type":"string","nullable":true,"description":"Account the action ran under, when there is one","example":"507f1f77bcf86cd799439011"},"name":{"type":"string","description":"Snapshot of the acting party's display name","example":"Jane Doe"},"email":{"type":"string","nullable":true,"example":"jane@example.com"}}},"AuditEventTarget":{"type":"object","properties":{"type":{"type":"string","enum":["item","item_version","project","webhook","api_key","member","comment"],"example":"item"},"id":{"type":"string","example":"507f1f77bcf86cd799439011"},"displayName":{"type":"string","nullable":true,"example":"Q3 launch announcement"}}},"AuditEvent":{"type":"object","properties":{"id":{"type":"string","example":"507f1f77bcf86cd799439011"},"type":{"$ref":"#/components/schemas/AuditEventType"},"actor":{"$ref":"#/components/schemas/AuditEventActor"},"projectId":{"type":"string","nullable":true,"example":"507f1f77bcf86cd799439011"},"subjectUserId":{"type":"string","nullable":true,"description":"The user this event is about, when distinct from the actor","example":"507f1f77bcf86cd799439011"},"target":{"$ref":"#/components/schemas/AuditEventTarget"},"metadata":{"type":"object","additionalProperties":true,"nullable":true,"description":"Event-specific detail. Never contains secrets."},"relatedProjectIds":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Projects an API-key event is relevant to, even though the event has no `projectId`: the key's explicit permission scope, or — for an unscoped key — the owner's project memberships at the time of the event."},"context":{"type":"object","nullable":true,"description":"Request context. Included for the caller's own feed and the admin feed; on a project feed only for a platform admin or the project's OWNER/ADMIN.","properties":{"ip":{"type":"string"},"userAgent":{"type":"string"},"requestId":{"type":"string"},"clientType":{"type":"string","enum":["web","ios","android","api","oauth","mcp"],"description":"Which surface the request came through."}}},"createdAt":{"type":"string","format":"date-time","description":"When the event was recorded, in UTC (ISO 8601, e.g. `2024-01-15T10:30:00.000Z`). Millisecond precision.","example":"2024-01-15T10:30:00.000Z"}}},"AuditEventPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditEvent"}},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next page, or null when there are no more events","example":"MjAyNC0wMS0xNVQxMDozMDowMC4wMDBafDUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMQ"},"hasMore":{"type":"boolean","example":true}}},"APIKeyPermissions":{"type":"object","properties":{"read":{"type":"boolean","description":"Read permission","example":true},"write":{"type":"boolean","description":"Write permission","example":false},"projectIds":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Allowed project IDs (null means all projects)","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"]}}},"APIKey":{"type":"object","properties":{"id":{"type":"string","description":"API key ID","example":"507f1f77bcf86cd799439011"},"name":{"type":"string","description":"API key name","example":"Production API Key"},"maskedKey":{"type":"string","description":"Masked API key (only first and last 3 characters visible)","example":"abc....xyz"},"permissions":{"$ref":"#/components/schemas/APIKeyPermissions"},"lastUsed":{"type":"string","format":"date-time","description":"Last usage timestamp","example":"2024-01-15T10:30:00Z"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp","example":"2024-01-01T00:00:00Z"}}},"CreateAPIKeyRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name for the API key","example":"My API Key","minLength":1,"maxLength":100},"permissions":{"$ref":"#/components/schemas/APIKeyPermissions"}}},"UpdateAPIKeyRequest":{"type":"object","description":"Partial update — supply only the fields to change.","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"permissions":{"$ref":"#/components/schemas/APIKeyPermissions"}}},"CreateAPIKey":{"type":"object","properties":{"id":{"type":"string","description":"API key ID","example":"507f1f77bcf86cd799439011"},"key":{"type":"string","description":"The generated API key (only shown once)","example":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},"name":{"type":"string","description":"API key name","example":"My API Key"},"permissions":{"$ref":"#/components/schemas/APIKeyPermissions"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp","example":"2024-01-01T00:00:00Z"}}},"APIKeyList":{"type":"array","items":{"$ref":"#/components/schemas/APIKey"}},"DashboardAnalytics":{"type":"object","properties":{"pendingApprovals":{"type":"integer","description":"Number of pending approvals","example":25},"trends":{"type":"object","description":"Trend data for various metrics","properties":{"pendingApprovals":{"type":"object","additionalProperties":{"type":"string"}},"approvalRate":{"type":"object","additionalProperties":{"type":"string"}},"completionRate":{"type":"object","additionalProperties":{"type":"string"}},"responseTime":{"type":"object","additionalProperties":{"type":"string"}},"dailyNewItems":{"type":"object","additionalProperties":{"type":"string"}}}},"averageResponseTime":{"type":"number","description":"Average response time in hours","example":24.5},"approvalRate":{"type":"integer","description":"Approval rate percentage","example":85},"completionRate":{"type":"integer","description":"Completion rate percentage","example":75},"dailyNewItems":{"type":"integer","description":"Number of new items created today","example":5},"recentActivity":{"type":"array","items":{"type":"object","properties":{"itemId":{"type":"string","description":"Item ID"},"projectId":{"type":"string","description":"Project ID"},"projectName":{"type":"string","description":"Project name","example":"My Project"},"title":{"type":"string","description":"Item title","example":"New Blog Post"},"status":{"$ref":"#/components/schemas/ItemStatus"},"timestamp":{"type":"string","format":"date-time","description":"Activity timestamp","example":"2024-01-15T10:30:00Z"},"activityType":{"$ref":"#/components/schemas/ActivityType"},"actor":{"type":"string","description":"User who performed the action","example":"user@example.com"},"comment":{"type":"string","description":"Optional comment","example":"Looks good"}}}},"projectStatus":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Project name","example":"My Project"},"progress":{"type":"integer","description":"Project progress percentage","example":75}}}}}}}},"tags":[{"name":"Users","description":"User profile and settings"},{"name":"Projects","description":"Project management endpoints"},{"name":"Project Members","description":"Project member management"},{"name":"Project Triggers","description":"Project trigger management"},{"name":"Project Webhooks","description":"Project webhook management"},{"name":"Items","description":"Item management"},{"name":"Item Versions","description":"Item versions management"},{"name":"Analytics","description":"Analytics and reporting"},{"name":"Settings","description":"Application settings and preferences"},{"name":"API Keys","description":"API key management"},{"name":"Media","description":"Media uploads (direct-to-storage), quotas, and retrieval. Durable content references are opaque media://<id> ids, resolved only against the containing item/version's own media[]."}],"paths":{"/v1/projects/{projectId}/webhooks":{"post":{"operationId":"createWebhook","tags":["Project Webhooks"],"summary":"Create webhook","description":"Create a new webhook for the project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"201":{"description":"Webhook created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"operationId":"listWebhooks","tags":["Project Webhooks"],"summary":"List webhooks","description":"Get all webhooks for the project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"List of webhooks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}":{"put":{"operationId":"updateWebhook","tags":["Project Webhooks"],"summary":"Update webhook","description":"Update an existing webhook","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"},"description":"Webhook ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"Webhook updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteWebhook","tags":["Project Webhooks"],"summary":"Delete webhook","description":"Delete an existing webhook","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"},"description":"Webhook ID"}],"responses":{"204":{"description":"Webhook deleted successfully"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/logs":{"get":{"operationId":"getWebhookLogs","tags":["Project Webhooks"],"summary":"Get webhook logs","description":"Paginated delivery logs for a specific webhook, newest-first.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"},"description":"Webhook ID"},{"in":"query","name":"status","schema":{"type":"string","enum":["success","failed"]},"description":"Restrict to successful or failed deliveries"},{"in":"query","name":"eventType","schema":{"type":"string"},"description":"Restrict to a single event type"},{"in":"query","name":"deliveryId","schema":{"type":"string"},"description":"Restrict to the attempts of a single delivery"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Case-insensitive text match against event type, error and response body/snippet"},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Page of webhook delivery logs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookLogPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/test":{"post":{"operationId":"testWebhook","tags":["Project Webhooks"],"summary":"Test webhook","description":"Queues one signed `test` request to the webhook's URL. It is sent once, without retries, and doesn't affect the webhook's health or suspension. Poll the returned test for the result; it also appears in the webhook's logs.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"},"description":"Webhook ID"}],"responses":{"202":{"description":"Test queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTest"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/tests/{testId}":{"get":{"operationId":"getWebhookTest","tags":["Project Webhooks"],"summary":"Get a webhook test","description":"The state of a queued test, and the receiver's response once it has been sent.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}},{"in":"path","name":"testId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook test","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTest"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project, webhook or test not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/rotate-secret":{"post":{"operationId":"rotateWebhookSecret","tags":["Project Webhooks"],"summary":"Rotate webhook secret","description":"Rotates in a new signing secret. The outgoing secret keeps signing alongside it (`X-Swipeflow-Signature-Previous`) for the grace period, so receivers can move over without a dropped-signature window.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateWebhookSecretRequest"}}}},"responses":{"200":{"description":"Secret rotated. The new secret is included once and never returned again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateWebhookSecretResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/reactivate":{"post":{"operationId":"reactivateWebhook","tags":["Project Webhooks"],"summary":"Reactivate a suspended webhook","description":"Clears a failure-rate suspension and re-enqueues any deliveries that were held while suspended. Always client/owner-driven — never automatic.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook reactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/cursor":{"get":{"operationId":"getWebhookCursor","tags":["Project Webhooks"],"summary":"Get the webhook's reconciliation cursor","description":"The event-id watermark a receiver can persist and resume from — everything at or before it is settled for this webhook.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCursor"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/events":{"get":{"operationId":"listWebhookReconciliationEvents","tags":["Project Webhooks"],"summary":"List events for reconciliation","description":"Events of the webhook's subscribed types, oldest-first, so a receiver that fell behind (or was suspended) can ask \"what did I miss\".","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}},{"in":"query","name":"after","schema":{"type":"string"},"description":"Public event id to list strictly after (exclusive). Typically the webhook's cursor."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Page of events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconciliationEventPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/deliveries":{"get":{"operationId":"listWebhookDeliveries","tags":["Project Webhooks"],"summary":"List a webhook's deliveries","description":"Paginated delivery history, newest-first.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}},{"in":"query","name":"status","style":"form","explode":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryStatus"}},"description":"One or more statuses (repeat the param, or pass a comma-separated list)"},{"in":"query","name":"eventType","schema":{"type":"string"}},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Page of deliveries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/deliveries/replay":{"post":{"operationId":"replayWebhookDeliveries","tags":["Project Webhooks"],"summary":"Bulk-replay terminal deliveries","description":"Re-queues terminal deliveries (default status `dead`) as new delivery rows, capped at 200 per call.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayDeliveriesRequest"}}}},"responses":{"200":{"description":"Replay result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayDeliveriesResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/deliveries/{deliveryId}":{"get":{"operationId":"getWebhookDelivery","tags":["Project Webhooks"],"summary":"Get a delivery","description":"A single delivery, including its full attempt timeline.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}},{"in":"path","name":"deliveryId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Delivery detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryDetail"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project, webhook or delivery not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/deliveries/{deliveryId}/replay":{"post":{"operationId":"replayWebhookDelivery","tags":["Project Webhooks"],"summary":"Replay a delivery","description":"Re-queues the delivery as a new row (`replayOf` the original), leaving the original's own history untouched.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}},{"in":"path","name":"deliveryId","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"The new (replay) delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"400":{"description":"The original event has expired and can no longer be replayed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project, webhook or delivery not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhooks/{webhookId}/backfill":{"post":{"operationId":"backfillWebhookDeliveries","tags":["Project Webhooks"],"summary":"Backfill deliveries for a range of events","description":"Creates and enqueues deliveries for events in range not already covered. Safe to re-run — already-covered events are left alone. Capped at 500 events per call.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"path","name":"webhookId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillRequest"}}}},"responses":{"200":{"description":"Backfill result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/users/{userIdOrMe}/profile":{"get":{"operationId":"getUserProfile","tags":["Users"],"parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me' to refer to the authenticated user."}],"summary":"Get user profile","description":"Retrieve the current user's profile information","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"operationId":"updateUserProfile","tags":["Users"],"parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me' to refer to the authenticated user."}],"summary":"Update user profile","description":"Update the current user's profile information","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserProfileRequest"}}}},"responses":{"200":{"description":"Profile updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteUserAccount","tags":["Users"],"parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me' to refer to the authenticated user."}],"summary":"Delete user account","description":"Permanently delete the user account and all associated data","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccountRequest"}}}},"responses":{"200":{"description":"Account successfully deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/users/{userIdOrMe}/storage-use":{"get":{"operationId":"getUserStorageUse","tags":["Users"],"summary":"Combined storage allowance and usage across every owned project","description":"`limitBytes`/`plan`/`seats` are the user's combined plan quota (free allowance, or pro allowance × seats). `usedBytes` is the total across every project this user owns. `media` breaks that total down per project — other consumers of the same quota may join this response later.\n","parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me'. Any other id is admin-only."}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"limitBytes":{"type":"integer"},"plan":{"type":"string","enum":["free","pro"]},"seats":{"type":"integer"},"usedBytes":{"type":"integer"},"media":{"type":"object","properties":{"usedBytes":{"type":"integer"},"projects":{"type":"array","items":{"type":"object","properties":{"projectId":{"type":"string"},"usedBytes":{"type":"integer"}}}}}}}}}}},"403":{"description":"Forbidden — only the user themself or an admin may view this"}}}},"/v1/users/{userIdOrMe}/profile-picture":{"put":{"operationId":"updateUserProfilePicture","tags":["Users"],"parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me' to refer to the authenticated user."}],"summary":"Update profile picture","description":"Upload a new profile picture","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"picture":{"type":"string","format":"binary","description":"Profile picture file (max 5MB, image files only - will be converted to PNG)"}}}}}},"responses":{"200":{"description":"Profile picture updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfilePicture"}}}},"400":{"description":"Invalid file type or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/users/{userIdOrMe}/recent-projects":{"get":{"operationId":"listRecentProjects","tags":["Users"],"parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me' to refer to the authenticated user."}],"summary":"Get recent projects","description":"Retrieve the current user's recently accessed projects","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of recent projects","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecentProjects"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/users/{userIdOrMe}/audit-events":{"get":{"operationId":"listUserAuditEvents","tags":["Users"],"summary":"List a user's audit events","description":"Paginated, newest-first feed of the user's own activity — every audit event where\nthey are the actor or the subject — across all projects and account-level events\n(profile, role, settings, API-key lifecycle). Use `me` or your own user id for your\npersonal feed. See `GET /v1/projects/{projectId}/audit-events` for a project's feed.\n","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"userIdOrMe","required":true,"schema":{"type":"string"},"description":"The user ID or the string 'me' to refer to the authenticated user."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Opaque cursor from a previous response's nextCursor"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"in":"query","name":"type","schema":{"type":"string"},"description":"Comma-separated AuditEventType values to filter by"},{"in":"query","name":"projectId","schema":{"type":"string"},"description":"Narrow the feed to a single project"},{"in":"query","name":"from","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"to","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"A page of audit events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventPage"}}}},"400":{"description":"Invalid query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden — non-admin requesting another user's feed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/triggers/{triggerId}/run":{"post":{"operationId":"runTrigger","tags":["Project Triggers"],"summary":"Run a project trigger","description":"Executes a manual trigger defined in the project's trigger configuration and sends the associated webhook.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"triggerId","required":true,"schema":{"type":"string"},"description":"Trigger ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunTriggerRequest"}}}},"responses":{"200":{"description":"Trigger executed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Webhook event triggered successfully"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or trigger not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/triggers":{"get":{"operationId":"listTriggers","tags":["Project Triggers"],"summary":"Get manual triggers","description":"Get all manual triggers for a project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"List of manual triggers","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTrigger"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createTrigger","tags":["Project Triggers"],"summary":"Create manual trigger","description":"Create a new manual trigger for a project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTriggerRequest"}}}},"responses":{"201":{"description":"Trigger created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTrigger"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/triggers/{triggerId}":{"put":{"operationId":"updateTrigger","tags":["Project Triggers"],"summary":"Update manual trigger","description":"Update an existing manual trigger","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"triggerId","required":true,"schema":{"type":"string"},"description":"Manual trigger ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTriggerRequest"}}}},"responses":{"200":{"description":"Trigger updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTrigger"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or trigger not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteTrigger","tags":["Project Triggers"],"summary":"Delete trigger","description":"Delete an existing manual trigger","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"triggerId","required":true,"schema":{"type":"string"},"description":"Manual trigger ID"}],"responses":{"204":{"description":"Manual trigger deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Project or trigger not found"}}}},"/v1/settings":{"get":{"operationId":"getUserSettings","tags":["Settings"],"summary":"Get user settings","description":"Retrieve the current user's settings","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"User settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Settings"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"operationId":"updateUserSettings","tags":["Settings"],"summary":"Update user settings","description":"Update the current user's settings","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSettingsRequest"}}}},"responses":{"200":{"description":"Settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Settings"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/settings/hints/{hintId}":{"put":{"operationId":"markHintSeen","tags":["Settings"],"summary":"Mark a UI hint as seen","description":"Records that the current user has seen a one-off UI hint (e.g. a first-time gesture animation). Idempotent — the first-seen time is kept.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"hintId","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":64},"example":"carousel-navigation"}],"responses":{"200":{"description":"The user's seen hints","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeenHintsResponse"}}}},"400":{"description":"Invalid hint id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/settings/hints":{"delete":{"operationId":"resetSeenHints","tags":["Settings"],"summary":"Reset seen UI hints","description":"Forgets every hint the current user has seen, so each plays again.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"The (now empty) seen hints","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeenHintsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects":{"get":{"operationId":"listProjects","tags":["Projects"],"summary":"Get all projects","description":"Retrieve a list of all projects for the authenticated user with pagination, search, and filtering options.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term to filter projects by name or description"},{"in":"query","name":"sort","description":"Sort projects by name, created date, updated date, total items, or pending items","schema":{"type":"string","enum":["name","createdAt","updatedAt","totalItems","pendingItems"],"default":"updatedAt"}},{"in":"query","name":"fields","schema":{"type":"string"},"description":"Comma-separated list of fields to include (or exclude, starting with '-') in the response. Inclusion and exclusion of fields cannot be mixed."},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1},"description":"Page number for pagination"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Number of items per page"},{"in":"query","name":"status","schema":{"type":"string","enum":["active","archived"]},"description":"Filter projects by status"},{"in":"query","name":"starred","schema":{"type":"boolean"},"description":"Filter projects by starred status"},{"in":"query","name":"includeAll","schema":{"type":"boolean","default":false},"description":"Include all projects (admin only - has no effect for non-admin users)"},{"in":"query","name":"memberUserId","schema":{"type":"string"},"description":"Admin only - restrict the listing to projects this user is a member of"},{"in":"query","name":"ownerUserId","schema":{"type":"string"},"description":"Admin only - restrict the listing to projects this user owns"},{"in":"query","name":"createdFrom","schema":{"type":"string"},"description":"ISO date/datetime lower bound on project creation"},{"in":"query","name":"createdTo","schema":{"type":"string"},"description":"ISO date/datetime upper bound on project creation (bare date = inclusive)"},{"in":"query","name":"ownerStatus","schema":{"type":"string","enum":["orphaned"]},"description":"Admin only - restrict the listing to projects whose OWNER member's user account no longer exists"}],"responses":{"200":{"description":"List of projects with pagination information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createProject","tags":["Projects"],"summary":"Create a new project","description":"Create a new project for the authenticated user","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectRequest"}}}},"responses":{"201":{"description":"Project created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}":{"get":{"operationId":"getProject","tags":["Projects"],"summary":"Get a specific project","description":"Retrieve details of a specific project by ID","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Project details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"operationId":"updateProject","tags":["Projects"],"summary":"Update a project","description":"Update an existing project's details","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectRequest"}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteProject","tags":["Projects"],"summary":"Delete a project","description":"Delete an existing project and all its associated items","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"204":{"description":"Project deleted successfully"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/resolve":{"post":{"operationId":"resolveProject","tags":["Projects"],"summary":"Resolve or create a project by name","description":"Looks up a project the caller belongs to with a matching name (case-insensitive) and returns it; if none exists, creates one. Lets a caller name a project without first checking whether it exists. Not a strict guarantee under concurrent calls with a brand-new name — see resolveOrCreateProject in ProjectService.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectRequest"}}}},"responses":{"200":{"description":"An existing project matched by name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"201":{"description":"No match existed; a new project was created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/settings":{"patch":{"operationId":"updateProjectSettings","tags":["Projects"],"summary":"Update project settings","description":"Update specific settings for a project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectSettingsRequest"}}}},"responses":{"200":{"description":"Settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/toggle-archived":{"post":{"operationId":"toggleProjectArchived","tags":["Projects"],"summary":"Toggle project archived status","description":"Archive or unarchive a project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Project archived status toggled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleArchived"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/toggle-starred":{"post":{"operationId":"toggleProjectStarred","tags":["Projects"],"summary":"Toggle project starred status","description":"Star or unstar a project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Project starred status toggled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleStarred"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/members":{"post":{"operationId":"addProjectMember","tags":["Project Members"],"summary":"Add project member","description":"Add a new member to the project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddProjectMemberRequest"}}}},"responses":{"201":{"description":"Member added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Member already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/members/{memberId}":{"put":{"operationId":"updateProjectMember","tags":["Project Members"],"summary":"Update project member","description":"Update a member's role in the project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"memberId","required":true,"schema":{"type":"string"},"description":"Member ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectMemberRequest"}}}},"responses":{"200":{"description":"Member updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"removeProjectMember","tags":["Project Members"],"summary":"Remove project member","description":"Remove a member from the project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"memberId","required":true,"schema":{"type":"string"},"description":"Member ID"}],"responses":{"204":{"description":"Member removed successfully"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/webhook-logs":{"get":{"operationId":"listProjectWebhookLogs","tags":["Project Webhooks"],"summary":"Get project webhook logs","description":"Paginated webhook delivery logs across the project, newest-first.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"query","name":"status","schema":{"type":"string","enum":["success","failed"]},"description":"Restrict to successful or failed deliveries"},{"in":"query","name":"eventType","schema":{"type":"string"},"description":"Restrict to a single event type"},{"in":"query","name":"deliveryId","schema":{"type":"string"},"description":"Restrict to the attempts of a single delivery"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Case-insensitive text match against event type, error and response body/snippet"},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Page of webhook delivery logs for the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookLogPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/next-item":{"get":{"operationId":"getNextItem","tags":["Items"],"summary":"Get next pending item","description":"Get the next pending item for review in the project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Next pending item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or no pending items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/add-test-items":{"post":{"operationId":"addTestItems","tags":["Items"],"summary":"Add test items","description":"Add sample test items to a project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"201":{"description":"Test items added successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Project not found"}}}},"/v1/notifications":{"get":{"operationId":"listNotifications","summary":"Get notifications for the current user, paginated newest first","tags":["Notifications"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"1-indexed page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Page size. The caller knows another page exists when the response length equals this value."}],"responses":{"200":{"description":"List of notifications","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteAllNotifications","summary":"Delete all notifications for the current user","tags":["Notifications"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All notifications deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/notifications/unread-count":{"get":{"operationId":"getUnreadNotificationCount","summary":"Get unread notifications count","tags":["Notifications"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Number of unread notifications","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnreadCount"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/notifications/{notificationId}/read":{"put":{"operationId":"markNotificationRead","summary":"Mark a notification as read","tags":["Notifications"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"notificationId","required":true,"schema":{"type":"string"},"description":"ID of the notification to mark as read"}],"responses":{"200":{"description":"Notification marked as read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Notification not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/notifications/read-all":{"put":{"operationId":"markAllNotificationsRead","summary":"Mark all notifications as read","tags":["Notifications"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All notifications marked as read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/notifications/devices":{"post":{"operationId":"registerNotificationDevice","summary":"Register a device for push notifications","tags":["Notifications"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeviceRequest"}}}},"responses":{"201":{"description":"Device registered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsRead"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/notifications/devices/{token}":{"delete":{"operationId":"removeNotificationDevice","summary":"Remove device registration","tags":["Notifications"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"},"description":"Device push notification token"}],"responses":{"200":{"description":"Device registration removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Device registration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/notifications/{notificationId}":{"delete":{"operationId":"deleteNotification","summary":"Delete a specific notification","tags":["Notifications"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"notificationId","required":true,"schema":{"type":"string"},"description":"ID of the notification to delete"}],"responses":{"200":{"description":"Notification deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Notification not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/media-uploads/{id}/confirm":{"post":{"operationId":"confirmMediaUpload","tags":["Media"],"summary":"Confirm a completed direct upload (step 2 of 2)","description":"Call after the direct-to-storage `PUT` completes. `HEAD`s the object, requires its actual size to equal the declared size, persists the opaque storage ETag, and moves the media to `uploaded`. Idempotent. A size mismatch fails completion, releases the reservation, and schedules the object for deletion. Returns the media record with a short-lived `contentUrl`.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Confirmed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaDescriptor"}}}},"403":{"description":"Not allowed"},"404":{"description":"Not found"},"409":{"description":"No uploaded object found yet, size mismatch, or media not in upload_pending"}}}},"/v1/media/{id}/content":{"get":{"operationId":"getMediaContent","tags":["Media"],"summary":"Redirect to a signed URL for the bytes","description":"302 to a signed storage URL, for header-authenticated callers holding a durable `media://<id>` reference. In-content references are resolved against the containing item/version's own `media[]` when it is read and normally do not hit this endpoint.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to the signed URL"},"404":{"description":"Not found"}}}},"/v1/media/{id}":{"get":{"operationId":"getMedia","tags":["Media"],"summary":"Media metadata","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"description":"Returns the common descriptor plus lifecycle data — status, timestamps, `itemIds`, `etag`, and optional `md5`. `url`/`urlExpiresAt` are only non-null once `uploaded`.\n","responses":{"200":{"description":"Media metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaDescriptor"}}}},"404":{"description":"Not found"}}},"delete":{"operationId":"deleteMedia","tags":["Media"],"summary":"Delete a media object","description":"Moves the media to `delete_pending` and frees its bytes from the project's quota immediately; the sweep removes the stored object and leaves a `deleted` tombstone. Allowed for the uploader or an OWNER/ADMIN of the media's project. Works only while unattached (`itemIds` empty) — attached media is immutable and removed only with its project.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deletion scheduled"},"403":{"description":"Not allowed"},"404":{"description":"Not found"},"409":{"description":"Media is attached and cannot be deleted while attached"}}}},"/v1/projects/{projectId}/media-uploads":{"post":{"operationId":"createMediaUpload","tags":["Media"],"summary":"Request a media upload ticket","description":"Media is uploaded directly to storage, not through this API, in two steps. Step 1 (this call): send the filename, exact byte size, and MIME type; the response contains a presigned, exact-size, write-once `PUT` target (`upload.url` + `upload.headers`) that the client sends the raw bytes to with those exact headers, e.g. `curl -X PUT \"$url\" $(for k in ...; do echo -H \"$k: ...\"; done) --data-binary @path`. Step 2: `POST /v1/media-uploads/{id}/confirm` once the upload completes. From there, using the media is ordinary item usage, not a further upload step: attach it to an item and/or embed its `ref` inside the item's markdown/HTML content — refs are swapped for short-lived signed URLs when the item is read. The declared `fileSize` is reserved against the account's media quota immediately; an unconfirmed ticket is reclaimed after it expires.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","fileSize","mimeType"],"properties":{"filename":{"type":"string"},"fileSize":{"type":"integer","description":"Exact size in bytes."},"mimeType":{"type":"string"},"md5":{"type":"string","description":"Optional base64 MD5, validated by storage during the PUT."}}}}}},"responses":{"201":{"description":"Upload ticket issued (status `upload_pending`)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MediaDescriptor"},{"type":"object","properties":{"upload":{"type":"object","properties":{"url":{"type":"string"},"method":{"type":"string","enum":["PUT"]},"headers":{"type":"object","additionalProperties":{"type":"string"}},"expiresAt":{"type":"string","format":"date-time"}}}}}]}}}},"400":{"description":"Missing or invalid fields"},"403":{"description":"A VIEWER may read project media but not create it"},"413":{"description":"File exceeds the per-file limit or would exceed the storage quota"}}}},"/v1/projects/{projectId}/media/usage":{"get":{"operationId":"getProjectMediaUsage","tags":["Media"],"summary":"Project media storage usage and limit","description":"Always project-scoped (`docs/media/DESIGN.md` §5.2) — `usedBytes` is charged to the project regardless of which member uploads, and includes bytes reserved by pending upload tickets and in-flight URL imports. `limitBytes`/`plan`/`seats` derive from the project OWNER's plan.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"usedBytes":{"type":"integer"},"limitBytes":{"type":"integer"},"plan":{"type":"string","enum":["free","pro"]},"seats":{"type":"integer"}}}}}},"404":{"description":"Project not found or insufficient permissions"}}}},"/v1/projects/{projectId}/media":{"get":{"operationId":"listProjectMedia","tags":["Media"],"summary":"List project media","description":"Cursor-paginated, newest first. Excludes `deleted` tombstones unless `status` explicitly asks for them. Every row's signed URL shares one absolute expiry.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Opaque cursor from a previous response's nextCursor"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"in":"query","name":"status","schema":{"type":"string","enum":["upload_pending","import_pending","uploaded","delete_pending","deleted"]}},{"in":"query","name":"attached","schema":{"type":"boolean"},"description":"Filter to media with (true) or without (false) any itemIds"}],"responses":{"200":{"description":"A page of project media","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaListPage"}}}},"400":{"description":"Invalid status/limit/cursor"},"404":{"description":"Project not found or insufficient permissions"}}}},"/v1/projects/{projectId}/media/import-url":{"post":{"operationId":"importMediaFromUrl","tags":["Media"],"summary":"Import media from a URL","description":"Downloads a file from the supplied HTTP or HTTPS URL and stores it in the project's media library. The import runs asynchronously. A successful request returns `202` with a media descriptor whose status is `import_pending`; use `GET /v1/media/{id}` to check when it becomes `uploaded`. File-size and storage limits for the project apply. The source URL is used only for the import and is not stored or returned.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"HTTP or HTTPS URL of the file to import."},"fileName":{"type":"string","description":"Optional filename to use for the imported media."}}}}}},"responses":{"202":{"description":"Import started (status import_pending)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaDescriptor"}}}},"400":{"description":"Missing or invalid url"},"403":{"description":"A VIEWER may read project media but not create it"},"404":{"description":"Project not found or insufficient permissions"},"413":{"description":"The import exceeds the applicable file-size or storage limit"},"503":{"description":"URL imports are unavailable in this environment"}}}},"/v1/projects/{projectId}/items":{"post":{"operationId":"createItem","tags":["Items"],"summary":"Create a new item","description":"Create a new item in a specific project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateItemRequest"}}}},"responses":{"201":{"description":"Item created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"operationId":"listItems","tags":["Items"],"summary":"Get all items","description":"Retrieve all items for a specific project","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","approved","rejected","change_requested"]},"description":"Filter items by status"},{"in":"query","name":"processed","schema":{"type":"boolean"},"description":"Filter by requester-side reconciliation state (see PUT /:itemId/processed). false surfaces every item still outstanding — pending, change_requested, and any approved/rejected item not yet marked processed — the durable backstop for anything a missed webhook delivery left stranded. true shows only what's been closed out. Omit to ignore this entirely.\n"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term applied to item title or description"},{"in":"query","name":"sortBy","schema":{"type":"string","enum":["createdAt","title","status","updatedAt"]},"description":"Field used to sort results"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Sort direction"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":10},"description":"Items per page (max 100)"},{"in":"query","name":"resolveMedia","schema":{"type":"boolean","default":false},"description":"See GET /v1/projects/{projectId}/items/{itemId}'s resolveMedia — applied to every item in the page."}],"responses":{"200":{"description":"List of items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/status":{"get":{"operationId":"getItemStatuses","tags":["Items"],"summary":"Get slim status for multiple items","description":"Batch variant of the single-item status endpoint — one round trip for N items instead of N. Ids that don't exist or aren't in this project are silently omitted from the result rather than causing an error.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"query","name":"ids","required":true,"schema":{"type":"string"},"description":"Comma-separated item IDs (maximum 100 per request)","example":"507f1f77bcf86cd799439011,507f1f77bcf86cd799439012"}],"responses":{"200":{"description":"Status of the requested items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemStatusSnapshotList"}}}},"400":{"description":"Missing, malformed, or excessive ids","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/{itemId}":{"get":{"operationId":"getItem","tags":["Items"],"summary":"Get a specific item","description":"Retrieve details of a specific item, optionally including all revisions","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"},{"in":"query","name":"includeVersions","schema":{"type":"boolean","default":false},"description":"Include all versions with full content (including current version)"},{"in":"query","name":"resolveMedia","schema":{"type":"boolean","default":false},"description":"When true, content.data has any embedded media://<id> ref substituted for a signed URL. media[] itself always carries a signed url/urlExpiresAt regardless of this flag. Default is the stable view: content.data keeps its durable media://<id> ref rather than a URL that will eventually expire.\n"}],"responses":{"200":{"description":"Item details (with optional versions array)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteItem","tags":["Items"],"summary":"Delete an item","description":"Delete a specific item. Only pending items with no decisions and no prior versions can be deleted (admins are exempt) — a decided or revised item is audit history and is retained.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"}],"responses":{"204":{"description":"Item deleted successfully"},"400":{"description":"Item has decisions or prior versions and cannot be deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/{itemId}/status":{"get":{"operationId":"getItemStatus","tags":["Items"],"summary":"Get slim status for an item","description":"Returns only status, version, and the latest decision — not content or metadata. The cheap alternative to GET /:itemId when polling for a decision.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"}],"responses":{"200":{"description":"Item status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemStatusSnapshot"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/{itemId}/decision":{"put":{"operationId":"updateItemDecision","tags":["Items"],"summary":"Update item decision","description":"Update the decision status of an item. When decision is 'change_requested', a comment is required.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateItemDecisionRequest"}}}},"responses":{"200":{"description":"Decision updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"400":{"description":"Bad request (e.g., comment required for 'change_requested' decision)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/{itemId}/processed":{"put":{"operationId":"markItemProcessed","tags":["Items"],"summary":"Mark an item's decision as processed","description":"Records that the requester has finished acting on a decision — the durable pull-side backstop for reconciliation when a webhook was never delivered (no retries exist yet) or was delivered but not fully handled on the receiving end. Only valid once, and only from 'approved' or 'rejected'. A 'change_requested' item closes itself out when POST .../versions creates a new round instead — there's nothing to explicitly process until then. Reading a decision (GET /:itemId, GET /:itemId/status, or GET / with a status filter) never marks it processed on its own; this is the only thing that does, precisely so a caller that merely fetched a decision but hasn't finished acting on it is not mistaken for one that has.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessItemRequest"}}}},"responses":{"200":{"description":"Item marked processed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"400":{"description":"Item isn't in a terminal state yet, or was already processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/{itemId}/versions":{"post":{"operationId":"createItemVersion","tags":["Item Versions"],"summary":"Create a new version of an item","description":"Creates a new version of an existing item. Only allowed when item status is 'pending' or 'change_requested'.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateItemVersionRequest"}}}},"responses":{"200":{"description":"Version created successfully, returns updated item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Item"}}}},"400":{"description":"Cannot create version for item with current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"operationId":"listItemVersions","tags":["Item Versions"],"summary":"Get all versions for an item","description":"Retrieve all versions for a specific item with pagination","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":10,"maximum":100},"description":"Items per page"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Sort order by version number"}],"responses":{"200":{"description":"List of versions with pagination info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemVersionList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project or item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/projects/{projectId}/items/{itemId}/versions/{version}":{"get":{"operationId":"getItemVersion","tags":["Item Versions"],"summary":"Get a specific version","description":"Retrieve a specific version by its version number. Version 1 always returns the original/current item.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"},"description":"Item ID"},{"in":"path","name":"version","required":true,"schema":{"type":"integer"},"description":"Version number (1, 2, 3, etc.)"}],"responses":{"200":{"description":"Version details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemVersion"}}}},"400":{"description":"Invalid version number","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project, item, or version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/billing":{"get":{"operationId":"getBilling","tags":["Billing"],"summary":"Current user's billing summary","description":"Plan, subscription status, seats, renewal/cancel date, payment method, and the next invoice.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Billing summary"}}}},"/v1/billing/invoices":{"get":{"operationId":"listBillingInvoices","tags":["Billing"],"summary":"Recent invoices / transactions","security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":12}}],"responses":{"200":{"description":"A list of invoices"}}}},"/v1/projects/{projectId}/audit-events":{"get":{"operationId":"listProjectAuditEvents","tags":["Projects"],"summary":"List project audit events","description":"Paginated, newest-first activity feed for the project (epic #276). A platform admin\nor a project OWNER/ADMIN sees every event. A regular member sees all non-sensitive\nevents plus any sensitive event (membership, webhook, API-key changes) they\nperformed or are the subject of.\n","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project ID"},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Opaque cursor from a previous response's nextCursor"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Page size"},{"in":"query","name":"type","schema":{"type":"string"},"description":"Comma-separated list of AuditEventType values to filter by"},{"in":"query","name":"actor","schema":{"type":"string"},"description":"Filter by the acting user's ID"},{"in":"query","name":"subject","schema":{"type":"string"},"description":"Filter by the ID of the user the event is about"},{"in":"query","name":"itemId","schema":{"type":"string"},"description":"Filter by target ID (alias of targetId)"},{"in":"query","name":"targetId","schema":{"type":"string"},"description":"Filter by target ID"},{"in":"query","name":"from","schema":{"type":"string","format":"date-time"},"description":"Only events at or after this time"},{"in":"query","name":"to","schema":{"type":"string","format":"date-time"},"description":"Only events at or before this time"}],"responses":{"200":{"description":"A page of audit events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventPage"}}}},"400":{"description":"Invalid query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/api-keys":{"get":{"operationId":"listApiKeys","tags":["API Keys"],"summary":"List API keys","description":"Retrieve a list of API keys for the authenticated user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"A list of API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createApiKey","tags":["API Keys"],"summary":"Create new API key","description":"Generate a new API key for the authenticated user","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyRequest"}}}},"responses":{"201":{"description":"API key created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKey"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/api-keys/{keyId}":{"patch":{"operationId":"updateApiKey","tags":["API Keys"],"summary":"Update API key","description":"Update an API key's name and/or permissions (partial update).","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"keyId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAPIKeyRequest"}}}},"responses":{"200":{"description":"The updated API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKey"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteApiKey","tags":["API Keys"],"summary":"Delete API key","description":"Delete an existing API key","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"keyId","required":true,"schema":{"type":"string"},"description":"ID of the API key to delete"}],"responses":{"204":{"description":"API key deleted successfully"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/analytics/dashboard":{"get":{"operationId":"getDashboardAnalytics","tags":["Analytics"],"summary":"Get dashboard analytics","description":"Retrieve analytics data for the dashboard view","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"in":"query","name":"projectId","schema":{"type":"string"},"required":false,"description":"Scope the analytics (including recent activity) to a single project"}],"responses":{"200":{"description":"Dashboard analytics data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardAnalytics"}}}},"400":{"description":"Invalid projectId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}