--- openapi: 3.1.0 info: title: WIP API version: 1.4.0 description: | The WIP REST API lets you read and write todos, projects, comments, and reactions. ## Access All endpoints require an active subscription. Accounts without access receive a 401 response with an upgrade URL. ## Expanding responses Some responses can include related records inline on request, similar to Stripe's `expand`. Pass `expand` as a query parameter with a comma-separated list (`?expand=reactions`) or the repeated array form (`?expand[]=reactions`). Expandable fields are omitted by default to keep base payloads small. See each endpoint's parameters for the values it supports. ## API keys API keys can be **read-write** (default) or **read-only**. Read-only keys can fetch data but cannot create, update, or delete resources (403 Forbidden). OAuth 2.0 access tokens are an alternative to API keys for third-party applications. license: name: MIT url: https://opensource.org/licenses/MIT servers: - url: https://api.wip.co security: - ApiKeyHeader: [] - ApiKeyQuery: [] - OAuth2: [] tags: - name: Todos description: Operations for managing todos - name: Users description: Operations for managing users - name: Projects description: Operations for managing projects - name: Uploads description: Operations for managing file uploads - name: Viewer description: Operations for the currently authenticated user - name: Reactions description: Operations for managing reactions (likes) on resources - name: Comments description: Operations for managing comments on resources - name: Webhooks description: Operations for managing webhook subscriptions - name: Moderation description: Operations for reporting content and blocking users - name: Following description: Operations for following and unfollowing users and projects - name: Account description: Operations for managing the authenticated user's account - name: Notifications description: Operations for the authenticated user's notifications (official WIP apps) - name: Messages description: Operations for direct messages (requires the privileged `chat` scope) paths: "/v1/todos": post: tags: - Todos summary: Create a completed todo operationId: createTodo requestBody: required: true content: application/json: schema: type: object properties: body: type: string description: The content of the todo attachments: type: array items: type: string description: List of attachment signed IDs required: - body responses: '201': description: Created todo content: application/json: schema: "$ref": "#/components/schemas/Todo" '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/todos/{todo_id}": get: tags: - Todos summary: Return a single (completed) todo operationId: getTodo parameters: - "$ref": "#/components/parameters/TodoIdParam" - "$ref": "#/components/parameters/ExpandReactionsParam" responses: '200': description: A single (completed) todo content: application/json: schema: "$ref": "#/components/schemas/Todo" '404': description: Not found patch: tags: - Todos summary: Update a todo description: Update the body text or attachments for one of your own todos. Attachments are additive; use remove_attachment_ids to delete existing attachments from the todo. operationId: updateTodo parameters: - "$ref": "#/components/parameters/TodoIdParam" requestBody: required: true content: application/json: schema: type: object properties: body: type: string description: The new content of the todo attachments: type: array items: type: string description: List of attachment signed IDs to add to the todo remove_attachment_ids: type: array items: type: string description: List of existing todo attachment IDs to remove responses: '200': description: Todo updated successfully content: application/json: schema: "$ref": "#/components/schemas/Todo" '403': description: Forbidden — read-only API key, or you can only update your own todos content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Todo not found '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many todo updates content: application/json: schema: "$ref": "#/components/schemas/Error" delete: tags: - Todos summary: Delete a todo description: | Permanently delete your own todo, including its comments, reactions, and attachments. This cannot be undone. You can only delete todos you created. Deletions are rate limited. operationId: deleteTodo parameters: - "$ref": "#/components/parameters/TodoIdParam" responses: '204': description: Todo deleted successfully '403': description: Forbidden — read-only API key, or you can only delete your own todos content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Todo not found '429': description: Rate limited — too many todo deletions content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/todos/{todo_id}/deletion_preview": get: tags: - Todos summary: Preview the streak impact of deleting a todo description: | Read-only preview of what deleting one of your own todos would do to your streak. Call this before DELETE /v1/todos/{todo_id} and ask the user for confirmation when `safe_to_delete` is false. Deleting the only todo on a past day can retroactively empty that day and break the current streak; streak freezes do not apply retroactively. The response contains facts rather than display strings, so clients compose their own confirmation message. Possible `impact` values: - `none` — the day stays covered by another todo or a used streak freeze - `empties_today` — this is the only todo today (day still in progress) - `empties_past_day` — only todo on a past day outside the current streak - `breaks_current_streak` — only todo on a closed day within the current streak; deleting it breaks the streak operationId: previewTodoDeletion parameters: - "$ref": "#/components/parameters/TodoIdParam" responses: '200': description: Deletion impact preview content: application/json: schema: type: object properties: impact: type: string enum: - none - empties_today - empties_past_day - breaks_current_streak description: What deleting this todo would do to the streak safe_to_delete: type: boolean description: True when the deletion has no streak impact date: type: string format: date nullable: true description: The local day this todo counts towards current_streak: type: integer description: The creator's current streak in days '403': description: Forbidden — you can only preview deletion of your own todos content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Todo not found "/v1/todos/{todo_id}/comments": get: tags: - Todos - Comments summary: Return all comments for a todo operationId: getTodoComments parameters: - "$ref": "#/components/parameters/TodoIdParam" - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" responses: '200': description: A list of comments for the todo content: application/json: schema: "$ref": "#/components/schemas/PaginatedComments" '404': description: Todo not found "/v1/users/{username}": get: tags: - Users summary: Return a single user operationId: getUser parameters: - "$ref": "#/components/parameters/UsernameParam" responses: '200': description: A single user content: application/json: schema: "$ref": "#/components/schemas/User" '404': description: Not found "/v1/users/{username}/projects": get: tags: - Users - Projects summary: Return all projects for a user operationId: getUserProjects parameters: - "$ref": "#/components/parameters/UsernameParam" - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" responses: '200': description: A list of projects content: application/json: schema: "$ref": "#/components/schemas/PaginatedProjects" '404': description: Not found "/v1/users/{username}/todos": get: tags: - Users - Todos summary: Return all (completed) todos for a user operationId: getUserTodos parameters: - "$ref": "#/components/parameters/UsernameParam" - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" - "$ref": "#/components/parameters/SinceParam" - "$ref": "#/components/parameters/UntilParam" - "$ref": "#/components/parameters/QueryParam" - "$ref": "#/components/parameters/ExpandReactionsParam" responses: '200': description: A list of (completed) todos content: application/json: schema: "$ref": "#/components/schemas/PaginatedTodos" '400': description: Invalid date format for since or until parameter content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Not found "/v1/projects/{project_id}": get: tags: - Projects summary: Return a single project operationId: getProject parameters: - "$ref": "#/components/parameters/ProjectIdParam" responses: '200': description: A single project content: application/json: schema: "$ref": "#/components/schemas/Project" '404': description: Not found "/v1/projects/{project_id}/todos": get: tags: - Projects - Todos summary: Return all (completed) todos for a project operationId: getProjectTodos parameters: - "$ref": "#/components/parameters/ProjectIdParam" - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" - "$ref": "#/components/parameters/SinceParam" - "$ref": "#/components/parameters/UntilParam" - "$ref": "#/components/parameters/QueryParam" - "$ref": "#/components/parameters/ExpandReactionsParam" responses: '200': description: A list of (completed) todos content: application/json: schema: "$ref": "#/components/schemas/PaginatedTodos" '400': description: Invalid date format for since or until parameter content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Not found "/v1/uploads": post: tags: - Uploads summary: Request pre-signed URL for direct file upload description: | Get a pre-signed URL and credentials to upload a file directly to storage (R2/S3). Steps: 1. Call this endpoint for upload credentials 2. Upload file to returned `url` with PUT request, including provided `headers` Use returned `signed_id` to attach file to other resources. operationId: createUpload requestBody: required: true content: application/json: schema: type: object properties: filename: type: string description: Name of the file to upload byte_size: type: integer description: Size of the file in bytes checksum: type: string description: MD5 checksum of the file content_type: type: string description: MIME type of the file required: - filename - byte_size - checksum - content_type responses: '200': description: | Upload credentials generated successfully. Use this response to perform the actual file upload to storage. content: application/json: schema: "$ref": "#/components/schemas/Upload" '400': description: Invalid input "/v1/reactions": post: tags: - Reactions summary: Create a reaction on a resource description: | Add a reaction (like) to a todo or comment. If the user has already reacted with the same type, the existing reaction is returned. operationId: createReaction requestBody: required: true content: application/json: schema: type: object properties: reactable_type: type: string enum: - Todo - Comment description: The type of resource to react to reactable_id: type: string description: The ID of the resource to react to (`todo_` or `comm_` prefix ID). required: - reactable_type - reactable_id responses: '201': description: Reaction created successfully content: application/json: schema: "$ref": "#/components/schemas/Reaction" '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/reactions/{reaction_id}": delete: tags: - Reactions summary: Delete a reaction description: Remove your reaction from a resource. You can only delete your own reactions. operationId: deleteReaction parameters: - "$ref": "#/components/parameters/ReactionIdParam" responses: '204': description: Reaction deleted successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Reaction not found "/v1/comments": post: tags: - Comments summary: Create a comment on a resource description: Add a comment to a todo. operationId: createComment requestBody: required: true content: application/json: schema: type: object properties: commentable_type: type: string enum: - Todo description: The type of resource to comment on commentable_id: type: string description: The ID of the resource to comment on (`todo_` prefix ID). body: type: string description: The content of the comment. Optional when attachments are provided. attachments: type: array items: type: string description: List of attachment signed IDs required: - commentable_type - commentable_id responses: '201': description: Comment created successfully content: application/json: schema: "$ref": "#/components/schemas/Comment" '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/comments/{comment_id}": patch: tags: - Comments summary: Update a comment description: Update the body text or attachments for one of your own comments. You can only update comments you created. Attachments are additive; use remove_attachment_ids to delete existing attachments from the comment. operationId: updateComment parameters: - "$ref": "#/components/parameters/CommentIdParam" requestBody: required: true content: application/json: schema: type: object properties: body: type: string description: The new content of the comment attachments: type: array items: type: string description: List of attachment signed IDs to add to the comment remove_attachment_ids: type: array items: type: string description: List of existing comment attachment IDs to remove responses: '200': description: Comment updated successfully content: application/json: schema: "$ref": "#/components/schemas/Comment" '403': description: Forbidden — read-only API key, or you can only update your own comments content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Comment not found '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many comment updates content: application/json: schema: "$ref": "#/components/schemas/Error" delete: tags: - Comments summary: Delete a comment description: | Permanently delete your own comment, including its replies, reactions, and attachments. This cannot be undone. You can only delete comments you created. Deletions are rate limited. operationId: deleteComment parameters: - "$ref": "#/components/parameters/CommentIdParam" responses: '204': description: Comment deleted successfully '403': description: Forbidden — read-only API key, or you can only delete your own comments content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Comment not found '429': description: Rate limited — too many comment deletions content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/reports": post: tags: - Moderation summary: Report a resource description: | Report a todo, comment, or user for review by the WIP team. Used to flag objectionable content or abusive behavior. operationId: createReport requestBody: required: true content: application/json: schema: type: object properties: reportable_type: type: string enum: - Todo - Comment - User description: The type of resource to report reportable_id: type: string description: The ID of the resource to report (`todo_`, `comm_`, or `user_` prefix ID). required: - reportable_type - reportable_id responses: '201': description: Report created successfully content: application/json: schema: "$ref": "#/components/schemas/Report" '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/users/{user_id}/mute": post: tags: - Moderation summary: Block (mute) a user description: | Block a user so their todos and comments no longer appear in your timeline. Idempotent — blocking an already-blocked user succeeds. operationId: muteUser parameters: - "$ref": "#/components/parameters/UserIdParam" responses: '204': description: User blocked successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: User not found delete: tags: - Moderation summary: Unblock (unmute) a user description: Remove a block you previously placed on a user. operationId: unmuteUser parameters: - "$ref": "#/components/parameters/UserIdParam" responses: '204': description: User unblocked successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: User not found "/v1/users/{user_id}/follow": post: tags: - Following summary: Follow a user description: | Follow a user so their activity appears in your following timeline. Idempotent — following an already-followed user succeeds. operationId: followUser parameters: - "$ref": "#/components/parameters/UserIdParam" responses: '204': description: User followed successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: User not found delete: tags: - Following summary: Unfollow a user description: Stop following a user you previously followed. Idempotent. operationId: unfollowUser parameters: - "$ref": "#/components/parameters/UserIdParam" responses: '204': description: User unfollowed successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: User not found "/v1/projects/{project_id}/follow": post: tags: - Following summary: Follow a project description: | Follow a project so its activity appears in your following timeline. Idempotent — following an already-followed project succeeds. operationId: followProject parameters: - "$ref": "#/components/parameters/ProjectIdParam" responses: '204': description: Project followed successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Project not found delete: tags: - Following summary: Unfollow a project description: Stop following a project you previously followed. Idempotent. operationId: unfollowProject parameters: - "$ref": "#/components/parameters/ProjectIdParam" responses: '204': description: Project unfollowed successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Project not found "/v1/account": delete: tags: - Account summary: Delete the authenticated user's account description: | Schedule the authenticated user's account for permanent deletion. The request is reviewed by the WIP team before the account and its data are removed. operationId: deleteAccount responses: '204': description: Account deletion requested successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/webhooks/subscriptions": get: tags: - Webhooks summary: List webhook subscriptions description: Return all webhook subscriptions belonging to the authenticated user. operationId: getWebhookSubscriptions responses: '200': description: A list of webhook subscriptions content: application/json: schema: type: array items: "$ref": "#/components/schemas/WebhookSubscription" post: tags: - Webhooks summary: Create a webhook subscription description: | Subscribe to an event. We'll send a POST request to the given URL whenever the event occurs. Optionally scope the subscription to specific projects. operationId: createWebhookSubscription requestBody: required: true content: application/json: schema: type: object properties: url: type: string description: The URL we'll send the POST request to. event_type: type: string enum: - new_todo description: The event to subscribe to. project_ids: type: array description: 'Optional `proj_` prefix IDs to scope the subscription to. Leave empty to receive events from all projects. ' items: type: string required: - url - event_type responses: '201': description: Webhook subscription created successfully content: application/json: schema: "$ref": "#/components/schemas/WebhookSubscription" '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/webhooks/subscriptions/{subscription_id}": patch: tags: - Webhooks summary: Update a webhook subscription description: Update one of your webhook subscriptions. operationId: updateWebhookSubscription parameters: - "$ref": "#/components/parameters/SubscriptionIdParam" requestBody: required: true content: application/json: schema: type: object properties: url: type: string description: The URL we'll send the POST request to. event_type: type: string enum: - new_todo description: The event to subscribe to. project_ids: type: array description: "`proj_` prefix IDs to scope the subscription to. Pass an empty array to receive events from all projects.\n" items: type: string responses: '200': description: Webhook subscription updated successfully content: application/json: schema: "$ref": "#/components/schemas/WebhookSubscription" '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Webhook subscription not found '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" delete: tags: - Webhooks summary: Delete a webhook subscription description: Delete one of your webhook subscriptions. operationId: deleteWebhookSubscription parameters: - "$ref": "#/components/parameters/SubscriptionIdParam" responses: '204': description: Webhook subscription deleted successfully '403': description: Forbidden — read-only API key content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Webhook subscription not found "/v1/users/me": get: tags: - Viewer - Users summary: Return the authenticated user's details operationId: getAuthenticatedUser responses: '200': description: The authenticated user's details content: application/json: schema: "$ref": "#/components/schemas/User" '404': description: Not found patch: tags: - Viewer - Users summary: Update the authenticated user description: Update settings on your own user record. Setting `time_zone` clears any previously stored `dismissed_time_zone`. operationId: updateAuthenticatedUser requestBody: required: true content: application/json: schema: type: object properties: time_zone: type: string description: IANA time zone identifier (e.g. `Europe/Amsterdam`) dismissed_time_zone: type: string description: IANA time zone identifier the user declined to switch to, so clients stop suggesting it. responses: '200': description: User updated successfully content: application/json: schema: "$ref": "#/components/schemas/User" '403': description: Forbidden — read-only API key or missing OAuth write scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Not found '422': description: Invalid input content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/users/me/todos": get: tags: - Viewer - Todos summary: Return all (completed) todos for the authenticated user operationId: getAuthenticatedUserTodos parameters: - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" - "$ref": "#/components/parameters/SinceParam" - "$ref": "#/components/parameters/UntilParam" - "$ref": "#/components/parameters/QueryParam" - "$ref": "#/components/parameters/ExpandReactionsParam" responses: '200': description: A list of (completed) todos for the authenticated user content: application/json: schema: "$ref": "#/components/schemas/PaginatedTodos" '400': description: Invalid date format for since or until parameter content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Not found "/v1/users/me/projects": get: tags: - Viewer - Projects summary: Return all projects for the authenticated user operationId: getAuthenticatedUserProjects parameters: - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" responses: '200': description: A list of projects for the authenticated user content: application/json: schema: "$ref": "#/components/schemas/PaginatedProjects" '404': description: Not found "/v1/notifications": get: tags: - Notifications summary: List the authenticated user's notifications operationId: getNotifications description: Returns the authenticated user's notifications, newest first. Requires OAuth authentication with the `notifications` scope — API keys are rejected with 403. security: - OAuth2: - notifications parameters: - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" - name: unread in: query required: false schema: type: boolean description: When true, only unread notifications are returned. responses: '200': description: A paginated list of notifications content: application/json: schema: "$ref": "#/components/schemas/PaginatedNotifications" '403': description: Missing OAuth authentication or the `notifications` scope "/v1/search/todos": get: tags: - Todos summary: Search todos operationId: searchTodos description: Full-text search across all todos visible to the authenticated user, newest first. Currently limited to official WIP apps. Results are capped to the 100 most recent matches; pagination works as elsewhere but ends at that cap. Supports prefix matching and negation (e.g. `feature -bug`). security: - OAuth2: - public parameters: - name: query in: query required: true schema: type: string - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" - "$ref": "#/components/parameters/ExpandReactionsParam" responses: '200': description: Matching todos, newest first content: application/json: schema: "$ref": "#/components/schemas/PaginatedTodos" '400': description: Missing query '403': description: Not an official WIP app "/v1/search/users": get: tags: - Users summary: Search users operationId: searchUsers description: 'Username and name autocomplete for @mentions, personalized to the authenticated user: people they mention most often rank first, and an empty (or omitted) query returns just those frequent mentions. Returns at most 10 users; not paginated. Currently limited to official WIP apps.' security: - OAuth2: - public parameters: - name: query in: query required: false schema: type: string responses: '200': description: Matching users, most relevant first content: application/json: schema: "$ref": "#/components/schemas/PaginatedUsers" '403': description: Not an official WIP app "/v1/devices": post: tags: - Notifications summary: Register a device for push notifications operationId: registerDevice description: Registers an APNs device token for the authenticated user. Idempotent; a token previously registered to another account moves to the current user. Official WIP apps only. security: - OAuth2: - notifications requestBody: required: true content: application/json: schema: type: object required: - token - platform properties: token: type: string description: Hex-encoded APNs device token. platform: type: string enum: - ios - macos apns_environment: type: string enum: - production - sandbox default: production responses: '201': description: Device registered content: application/json: schema: "$ref": "#/components/schemas/DeviceToken" '403': description: Missing OAuth scope or not an official WIP app '422': description: Invalid token or platform "/v1/devices/{token}": delete: tags: - Notifications summary: Unregister a device operationId: unregisterDevice description: Removes an APNs device token (e.g. on sign-out). Succeeds even when the token is unknown. Official WIP apps only. security: - OAuth2: - notifications parameters: - name: token in: path required: true schema: type: string responses: '204': description: Device unregistered '403': description: Missing OAuth scope or not an official WIP app "/v1/notification_preferences": get: tags: - Notifications summary: List notification preferences operationId: getNotificationPreferences description: The authenticated user's per-type notification preferences. Apps manage the `mobile_push` channel; email/web push stay managed on the website. Official WIP apps only. security: - OAuth2: - notifications responses: '200': description: Notification preferences content: application/json: schema: type: object required: - data properties: data: type: array items: "$ref": "#/components/schemas/NotificationPreference" '403': description: Missing OAuth scope or not an official WIP app "/v1/notification_preferences/{identifier}": patch: tags: - Notifications summary: Update a notification preference operationId: updateNotificationPreference description: Updates the mobile push setting for one notification type. Official WIP apps only. security: - OAuth2: - notifications parameters: - name: identifier in: path required: true schema: type: string description: The notification type identifier, e.g. "comment_todo_create". requestBody: required: true content: application/json: schema: type: object properties: mobile_push_enabled: type: boolean responses: '200': description: Updated preference content: application/json: schema: "$ref": "#/components/schemas/NotificationPreference" '403': description: Missing OAuth scope or not an official WIP app '404': description: Unknown notification type "/v1/notifications/reading": post: tags: - Notifications summary: Mark all notifications as read operationId: markNotificationsAsRead description: Marks all of the authenticated user's unread notifications as read and resets the unread count. Requires OAuth authentication with the `notifications` scope and is limited to official WIP apps. Also available at the legacy path `/v1/notifications/mark_as_read`. security: - OAuth2: - notifications responses: '204': description: All notifications marked as read '403': description: Missing OAuth authentication, the `notifications` scope, or the client is not an official WIP app "/v1/conversations": get: tags: - Messages summary: List the authenticated user's conversations operationId: getConversations description: Returns the user's direct-message conversations ordered by most recent activity. Conversations without any messages yet are excluded. Requires OAuth authentication with the `chat` scope, which is only granted to approved applications. security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" - name: unread in: query required: false schema: type: boolean description: When true, only conversations with unread messages are returned. responses: '200': description: A paginated list of conversations content: application/json: schema: "$ref": "#/components/schemas/PaginatedConversations" '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: The `starting_after` cursor conversation was not found or cannot anchor a page content: application/json: schema: "$ref": "#/components/schemas/Error" post: tags: - Messages summary: Find or create a conversation with a user operationId: createConversation description: Returns the existing conversation with the given user, or creates a new (empty) one. Creations are rate limited. security: - OAuth2: - chat requestBody: required: true content: application/json: schema: type: object properties: user: type: string description: Username or user ID (`user_...`) to converse with required: - user responses: '200': description: The existing conversation with this user content: application/json: schema: "$ref": "#/components/schemas/Conversation" '201': description: A newly created conversation content: application/json: schema: "$ref": "#/components/schemas/Conversation" '400': description: Missing `user` parameter content: application/json: schema: "$ref": "#/components/schemas/Error" '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: User not found content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input, e.g. starting a conversation with yourself content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many new conversations content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/conversations/reading": post: tags: - Messages summary: Mark all conversations as read operationId: markAllConversationsAsRead description: Marks every conversation with unread messages as read and resets the unread conversations count. security: - OAuth2: - chat responses: '204': description: All conversations marked as read '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many requests content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/conversations/{conversation_id}": get: tags: - Messages summary: Return a single conversation operationId: getConversation security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/ConversationIdParam" responses: '200': description: A single conversation content: application/json: schema: "$ref": "#/components/schemas/Conversation" '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Conversation not found (or you are not a participant) content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/conversations/{conversation_id}/messages": get: tags: - Messages summary: List messages in a conversation operationId: getConversationMessages description: Returns the conversation's messages, newest first. security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/ConversationIdParam" - "$ref": "#/components/parameters/LimitParam" - "$ref": "#/components/parameters/StartingAfterParam" responses: '200': description: A paginated list of messages content: application/json: schema: "$ref": "#/components/schemas/PaginatedMessages" '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Conversation not found (or you are not a participant) content: application/json: schema: "$ref": "#/components/schemas/Error" post: tags: - Messages summary: Send a message operationId: createConversationMessage description: 'Sends a message with a text body, an attachment (signed ID from `/v1/uploads`), or both. Pass a `client_message_id` UUID to make retries idempotent: resending the same UUID returns the original message (200) instead of creating a duplicate (201). Sending is rate limited, and conversations the other participant hasn''t replied to yet are capped: at most 3 messages per unanswered conversation, and at most 5 distinct unanswered conversations per 24 hours.' security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/ConversationIdParam" requestBody: required: true content: application/json: schema: type: object properties: body: type: - string - 'null' description: The message text (optional when an attachment is provided) attachment: type: string description: Signed ID of an uploaded image or video (see `/v1/uploads`) client_message_id: type: string description: Client-generated UUID for idempotent retries (rejected with 422 when not a UUID) responses: '200': description: The previously created message with the same `client_message_id` content: application/json: schema: "$ref": "#/components/schemas/Message" '201': description: The created message content: application/json: schema: "$ref": "#/components/schemas/Message" '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Conversation not found (or you are not a participant) content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Invalid input, e.g. neither body nor attachment content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many messages, or the unanswered-conversation caps were reached content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/conversations/{conversation_id}/reading": post: tags: - Messages summary: Mark a conversation as read operationId: markConversationAsRead security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/ConversationIdParam" responses: '204': description: Conversation marked as read '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Conversation not found (or you are not a participant) content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many requests content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/messages/{message_id}/reactions": post: tags: - Messages summary: React to a message operationId: createMessageReaction description: Adds an emoji reaction to a message. Only the quick-set emoji are allowed. Reacting with an emoji you already used is a no-op. security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/MessageIdParam" requestBody: required: true content: application/json: schema: type: object properties: content: type: string description: "One of the quick-set emoji: \U0001F44D ❤️ \U0001F602 \U0001F525 \U0001F64F" required: - content responses: '201': description: The reaction content: application/json: schema: "$ref": "#/components/schemas/MessageReaction" '400': description: Missing `content` parameter content: application/json: schema: "$ref": "#/components/schemas/Error" '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Message not found (or you are not a participant) content: application/json: schema: "$ref": "#/components/schemas/Error" '422': description: Emoji not in the quick set content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many reactions content: application/json: schema: "$ref": "#/components/schemas/Error" "/v1/messages/{message_id}/reactions/{content}": delete: tags: - Messages summary: Remove your reaction from a message operationId: deleteMessageReaction security: - OAuth2: - chat parameters: - "$ref": "#/components/parameters/MessageIdParam" - name: content in: path required: true schema: type: string description: The emoji to remove (URL-encoded) responses: '204': description: Reaction removed (also returned when there was none) '403': description: Missing OAuth authentication or the `chat` scope content: application/json: schema: "$ref": "#/components/schemas/Error" '404': description: Message not found (or you are not a participant) content: application/json: schema: "$ref": "#/components/schemas/Error" '429': description: Rate limited — too many reactions content: application/json: schema: "$ref": "#/components/schemas/Error" components: securitySchemes: ApiKeyHeader: type: http scheme: bearer description: 'Pass your API key as a Bearer token in the Authorization header. API keys start with `wip_sk_`. Bearer tokens without that prefix are treated as OAuth 2.0 access tokens. If both a query `api_key` and an Authorization header are provided, the query parameter takes precedence. ' ApiKeyQuery: type: apiKey in: query name: api_key description: 'Pass your API key as a query parameter. Prefer the Authorization header where possible — query parameters can be captured in server logs and browser history. ' OAuth2: type: oauth2 description: 'OAuth 2.0 access token for third-party applications. Pass as `Authorization: Bearer `. Tokens from the OAuth flow do not use the `wip_sk_` API key prefix. ' flows: authorizationCode: authorizationUrl: https://wip.co/oauth/authorize tokenUrl: https://wip.co/oauth/token scopes: public: View your public profile and activity write: Create, update, and delete your todos, comments, and reactions billing: View your subscription status and billing management options notifications: View your notifications and mark them as read chat: Read and send your direct messages parameters: LimitParam: name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 25 description: 'Number of items to return (max 100). `per_page` is accepted as an alias. Values above 100 are clamped to 100. ' StartingAfterParam: name: starting_after in: query required: false schema: type: string description: 'Cursor for pagination. Pass the `after` value from a previous response to fetch the next page of items. `after` is accepted as an alias for this parameter. Composes with `query`, `since`, and `until`. ' QueryParam: name: query in: query required: false schema: type: string description: 'Full-text search on todo body. Supports prefix matching and negation (e.g. `feature -bug`). Results are ordered by `created_at` descending so they paginate with `starting_after` / `after` and compose with `since` / `until` date filters. ' ExpandReactionsParam: name: expand in: query required: false style: form explode: true schema: type: array items: type: string enum: - reactions description: 'Related records to include inline. Pass `reactions` to add a `reactions` array to each todo — a bounded preview (up to 5) of the most recent reactions, newest first, each with its `reactor`. Use `reactions_count` for the full total. Accepts a comma-separated list (`?expand=reactions`) or the repeated form (`?expand[]=reactions`). ' SinceParam: name: since in: query required: false schema: type: string description: 'Filter todos created since this date. Supports Unix timestamp in seconds (1704067200) or milliseconds (1704067200000), YYYY (2024), YYYY-MM (2024-01), YYYY-MM-DD (2024-01-15), or ISO 8601 datetime (2024-01-15T12:00:00Z). Composes with `query` and cursor pagination (`starting_after` / `after`). ' UntilParam: name: until in: query required: false schema: type: string description: 'Filter todos created until this date. Supports Unix timestamp in seconds (1704067200) or milliseconds (1704067200000), YYYY (2024), YYYY-MM (2024-01), YYYY-MM-DD (2024-01-15), or ISO 8601 datetime (2024-01-15T12:00:00Z). Composes with `query` and cursor pagination (`starting_after` / `after`). Walk `until` backwards with `query` to enumerate matching todos by date. ' TodoIdParam: name: todo_id in: path required: true schema: type: string description: Todo ID (`todo_` prefix ID or numeric database ID). ConversationIdParam: name: conversation_id in: path required: true schema: type: string description: Conversation ID (`conversation_` prefix ID). MessageIdParam: name: message_id in: path required: true schema: type: string description: Message ID (`message_` prefix ID). UsernameParam: name: username in: path required: true schema: type: string description: User identifier (username, `user_` prefix ID, or `me` for the authenticated user). ProjectIdParam: name: project_id in: path required: true schema: type: string description: Project identifier (`proj_` prefix ID or slug). CommentIdParam: name: comment_id in: path required: true schema: type: string description: Comment ID (`comm_` prefix ID). ReactionIdParam: name: reaction_id in: path required: true schema: type: string description: Reaction ID (`reac_` prefix ID). SubscriptionIdParam: name: subscription_id in: path required: true schema: type: string description: Webhook subscription ID (`hook_` prefix ID). UserIdParam: name: user_id in: path required: true schema: type: string description: User identifier (username, `user_` prefix ID, or `me` for the authenticated user). schemas: Error: type: object required: - error properties: error: type: string Todo: type: object required: - id - created_at - updated_at - body - url - attachments - creator - creator_id - projects - mentioned_projects - mentioned_users - reactions_count - comments_count - viewer properties: id: type: string created_at: type: string format: date-time updated_at: type: string format: date-time body: type: string url: type: string attachments: type: array items: "$ref": "#/components/schemas/Attachment" creator: "$ref": "#/components/schemas/User" creator_id: type: string deprecated: true description: 'Deprecated: Use creator.id instead' user_id: type: string deprecated: true description: 'Deprecated: Use creator.id instead' projects: type: array description: 'The project this todo belongs to (at most one): the first project mentioned in the body that the author is an accepted maker of.' items: "$ref": "#/components/schemas/Project" mentioned_projects: type: array description: 'All projects referenced in `body` via #hashtag, resolved server-side so clients can render them as links with logos. Unlike `projects`, this includes projects the author isn''t a maker of. Projects the authenticated user can''t see are omitted.' items: "$ref": "#/components/schemas/MentionedProject" mentioned_users: type: array description: All users referenced in `body` via @username, resolved server-side so clients can render them as links with avatars. items: "$ref": "#/components/schemas/MentionedUser" reactions_count: type: integer description: Number of reactions on this todo reactions: type: array description: 'A bounded preview (up to 5) of the most recent reactions, newest first, each with its reactor. Only present when the request includes `expand=reactions`. Use `reactions_count` for the full total. ' items: "$ref": "#/components/schemas/Reaction" comments_count: type: integer description: Number of comments on this todo viewer: type: object description: Data specific to the authenticated user required: - reactions - comments properties: reactions: type: array description: The authenticated user's reactions on this todo (empty array if none) items: type: object properties: id: type: string comments: type: array description: The authenticated user's comments on this todo (empty array if none) items: type: object properties: id: type: string Attachment: type: object required: - id - created_at - updated_at - url - mime_type - filename properties: id: type: string created_at: type: string format: date-time updated_at: type: string format: date-time url: type: string mime_type: type: string filename: type: string width: type: - integer - 'null' description: Pixel width of the media, when known. Use together with height to reserve layout space before the media loads. height: type: - integer - 'null' description: Pixel height of the media, when known. aspect_ratio: type: - number - 'null' description: Width divided by height, when known. Project: type: object required: - id - slug - name - pitch - description - created_at - updated_at - hashtag - website_url - github_url - privacy - archived - url - logo - header - header_color - owner - makers - followers_count - following properties: id: type: string slug: type: - 'null' - string name: type: string pitch: type: - 'null' - string description: type: - 'null' - string created_at: type: string format: date-time updated_at: type: string format: date-time hashtag: type: - 'null' - string website_url: type: - 'null' - string github_url: type: - 'null' - string description: Link to the project's GitHub repository, for open source projects. Null when the project has not set one. privacy: type: string enum: - public - protected - private archived: type: boolean url: type: string logo: type: object properties: small: type: - 'null' - string medium: type: - 'null' - string large: type: - 'null' - string header: type: - 'null' - object description: Banner/header image at several sizes (3:1 aspect ratio, WebP). Null when the project has no header image, in which case clients can fall back to `header_color`. properties: small: type: string medium: type: string large: type: string header_color: type: string description: Solid background color for the header/banner. Always present; derived from the header image when available, otherwise a fallback color. owner: "$ref": "#/components/schemas/User" makers: type: array items: "$ref": "#/components/schemas/User" followers_count: type: integer description: Number of users following this project. following: type: boolean description: Whether the authenticated user follows this project. Always `false` for unauthenticated requests. Notification: type: object description: A notification for the authenticated user, rendered server-side into ready-to-display strings so clients don't need to know every notification type. required: - id - type - created_at - read - emoji - title - text - url - actor properties: id: type: string type: type: string description: Machine-readable kind, e.g. "comment_todo_create", "reaction_todo_create", "mention_todo_create", "follow_user_create". created_at: type: string format: date-time read: type: boolean emoji: type: - 'null' - string description: "Emoji representing the notification kind (e.g. \U0001F4AC, ❤️, \U0001F440)." title: type: - 'null' - string description: Plain-text summary line, e.g. "@marc commented on your todo". text: type: - 'null' - string description: Supporting text, e.g. the todo body or comment excerpt. url: type: - 'null' - string description: Web URL of the notification's target. todo_id: type: - 'null' - string description: ID of the todo the notification points at, when applicable, so native clients can navigate in-app. actor: oneOf: - "$ref": "#/components/schemas/MentionedUser" - type: 'null' description: The user who triggered the notification. PaginatedNotifications: type: object required: - data - has_more - total_count - unread_count properties: data: type: array items: "$ref": "#/components/schemas/Notification" has_more: type: boolean total_count: type: integer unread_count: type: integer description: Total unread notifications for the authenticated user, for client badges. after: type: - 'null' - string DeviceToken: type: object required: - token - platform - apns_environment - created_at properties: token: type: string platform: type: string enum: - ios - macos apns_environment: type: string enum: - production - sandbox created_at: type: string format: date-time NotificationPreference: type: object required: - identifier - name - description - mobile_push_enabled properties: identifier: type: string description: Notification type identifier, e.g. "comment_todo_create". name: type: string description: type: - 'null' - string mobile_push_enabled: type: boolean MentionedProject: type: object description: 'Slim representation of a project mentioned via #hashtag in a body of text. Lighter than Project: enough to link the hashtag and render a small logo, without nested owner/makers or viewer-relative attributes.' required: - id - slug - name - hashtag - url - logo properties: id: type: string slug: type: - 'null' - string name: type: string hashtag: type: - 'null' - string url: type: string logo: type: object properties: small: type: - 'null' - string medium: type: - 'null' - string large: type: - 'null' - string MentionedUser: type: object description: 'Slim representation of a user mentioned via @username in a body of text. Lighter than User: enough to link the mention and render a small avatar, without counts or viewer-relative attributes.' required: - id - username - first_name - last_name - url - avatar properties: id: type: string username: type: string first_name: type: - 'null' - string last_name: type: - 'null' - string url: type: string avatar: type: object properties: small: type: - 'null' - string medium: type: - 'null' - string large: type: - 'null' - string User: type: object required: - id - username - streak - created_at - updated_at - privacy - protected - first_name - last_name - bio - location - website_url - todos_count - time_zone - url - avatar - header - header_color - best_streak - streaking - followers_count - following - follows_you - pro properties: id: type: string username: type: string streak: type: integer created_at: type: string format: date-time updated_at: type: string format: date-time privacy: type: string enum: - public - protected - private protected: type: boolean deprecated: true description: 'Deprecated: use `privacy` instead. Returns `true` whenever `privacy` is not `public` (i.e. the account is `protected` or `private`).' first_name: type: - 'null' - string last_name: type: - 'null' - string bio: type: - 'null' - string description: The user's profile bio/description. location: type: - 'null' - string description: The user's self-reported location. website_url: type: - 'null' - string description: The user's personal website URL (e.g. blog or portfolio). Null when the user has not set one. todos_count: type: integer time_zone: type: string dismissed_time_zone: type: - 'null' - string description: Time zone the user declined to switch to (via the "change your time zone?" prompt). Only present on your own user record. url: type: string avatar: type: object properties: small: type: string medium: type: string large: type: string header: type: - 'null' - object description: Profile banner/header image at several sizes (3:1 aspect ratio, WebP). Null when the user has no header image, in which case clients can fall back to `header_color`. properties: small: type: string medium: type: string large: type: string header_color: type: string description: Solid background color for the profile header/banner. Always present; derived from the header image when available, otherwise a fallback color. best_streak: type: integer streaking: type: boolean followers_count: type: integer description: Number of users following this user. following: type: boolean description: Whether the authenticated user follows this user. Always `false` for unauthenticated requests. follows_you: type: boolean description: Whether this user follows the authenticated user. Always `false` for unauthenticated requests. pro: type: boolean description: Whether the user currently has a PRO subscription. Conversation: type: object required: - id - created_at - updated_at - unread - user - last_message properties: id: type: string created_at: type: string format: date-time updated_at: type: string format: date-time unread: type: boolean description: Whether the conversation has messages the user hasn't read yet user: "$ref": "#/components/schemas/User" description: The other participant last_message: oneOf: - "$ref": "#/components/schemas/Message" - type: 'null' description: The most recent message, or null for a conversation without messages Message: type: object required: - id - conversation_id - body - client_message_id - created_at - updated_at - creator - attachment - reactions properties: id: type: string conversation_id: type: string body: type: string description: The message text. Empty for attachment-only messages. client_message_id: type: - 'null' - string description: Client-generated UUID used for idempotent sending, when provided created_at: type: string format: date-time updated_at: type: string format: date-time creator: "$ref": "#/components/schemas/User" attachment: oneOf: - "$ref": "#/components/schemas/MessageAttachment" - type: 'null' description: The attached image or video, or null reactions: type: array items: "$ref": "#/components/schemas/MessageReaction" MessageAttachment: type: object required: - url - mime_type - filename - byte_size properties: url: type: string mime_type: type: string filename: type: string byte_size: type: integer description: File size in bytes width: type: - integer - 'null' description: Pixel width of the media, when known height: type: - integer - 'null' description: Pixel height of the media, when known MessageReaction: type: object required: - content - created_at - user properties: content: type: string description: The emoji created_at: type: string format: date-time user: "$ref": "#/components/schemas/User" PaginatedConversations: type: object required: - data - has_more - total_count - unread_count properties: data: type: array items: "$ref": "#/components/schemas/Conversation" has_more: type: boolean total_count: type: integer unread_count: type: integer description: 'Number of the authenticated user''s conversations with unread messages (across all pages), for badge rendering. ' after: type: - 'null' - string description: 'Cursor for the next page. Pass as `starting_after` to fetch more results. Null when there are no more items. ' PaginatedMessages: type: object required: - data - has_more - total_count properties: data: type: array items: "$ref": "#/components/schemas/Message" has_more: type: boolean total_count: type: integer after: type: - 'null' - string description: 'Cursor for the next page. Pass as `starting_after` to fetch more results. Null when there are no more items. ' PaginatedTodos: type: object required: - data - has_more - total_count properties: data: type: array items: "$ref": "#/components/schemas/Todo" has_more: type: boolean total_count: type: integer after: type: - 'null' - string description: 'Cursor for the next page. Pass as `starting_after` (or the `after` alias) to fetch more results. Null when there are no more items. ' PaginatedProjects: type: object required: - data - has_more - total_count properties: data: type: array items: "$ref": "#/components/schemas/Project" has_more: type: boolean total_count: type: integer after: type: - 'null' - string description: 'Cursor for the next page. Pass as `starting_after` to fetch more results. Null when there are no more items. ' PaginatedUsers: type: object required: - data - has_more - total_count properties: data: type: array items: "$ref": "#/components/schemas/User" has_more: type: boolean total_count: type: integer after: type: - 'null' - string description: 'Cursor for the next page. Pass as `starting_after` to fetch more results. Null when there are no more items. ' Upload: type: object description: | Contains credentials and information needed to perform a direct file upload to the storage provider. properties: filename: type: string description: Original filename byte_size: type: integer description: Size of the file in bytes checksum: type: string description: MD5 checksum of the file content_type: type: string description: MIME type of the file url: type: string description: Pre-signed URL where the file should be uploaded to key: type: string description: Storage key/path for the file signed_id: type: string description: Token to reference this file when attaching it to other resources method: type: string description: HTTP method to use for the upload (always "PUT") headers: type: object additionalProperties: type: string description: Headers that must be included in the upload request Reaction: type: object required: - id - created_at - reactable_type - reactable_id - reactor properties: id: type: string created_at: type: string format: date-time reactable_type: type: string description: The type of resource this reaction is on (Todo, Comment) reactable_id: type: string description: The ID of the resource this reaction is on reactor: "$ref": "#/components/schemas/User" Report: type: object required: - id - created_at - reportable_type - reportable_id properties: id: type: string description: Report ID (`rep_` prefix ID). created_at: type: string format: date-time reportable_type: type: string description: The type of resource reported (Todo, Comment, User) reportable_id: type: string description: The ID of the resource reported WebhookSubscription: type: object required: - id - url - event_type - project_ids - created_at - updated_at properties: id: type: string description: Webhook subscription ID (`hook_` prefix ID). url: type: string description: The URL we send the POST request to. event_type: type: string enum: - new_todo description: The event this subscription listens for. project_ids: type: array description: "`proj_` prefix IDs the subscription is scoped to. Empty means all projects.\n" items: type: string created_at: type: string format: date-time updated_at: type: string format: date-time Comment: type: object required: - id - body - created_at - updated_at - url - attachments - reactions_count - viewer - creator - mentioned_projects - mentioned_users properties: id: type: string body: type: string attachments: type: array items: "$ref": "#/components/schemas/Attachment" created_at: type: string format: date-time updated_at: type: string format: date-time url: type: string reactions_count: type: integer description: Number of reactions on this comment viewer: type: object description: Data specific to the authenticated user required: - reactions properties: reactions: type: array description: The authenticated user's reactions on this comment (empty array if none) items: type: object properties: id: type: string creator: "$ref": "#/components/schemas/User" mentioned_projects: type: array description: 'All projects referenced in `body` via #hashtag, resolved server-side. Projects the authenticated user can''t see are omitted.' items: "$ref": "#/components/schemas/MentionedProject" mentioned_users: type: array description: All users referenced in `body` via @username, resolved server-side. items: "$ref": "#/components/schemas/MentionedUser" PaginatedComments: type: object required: - data - has_more - total_count properties: data: type: array items: "$ref": "#/components/schemas/Comment" has_more: type: boolean total_count: type: integer after: type: - 'null' - string description: 'Cursor for the next page. Pass as `starting_after` to fetch more results. Null when there are no more items. '