gitify icon indicating copy to clipboard operation
gitify copied to clipboard

GitHub Public API Limitations

Open setchy opened this issue 1 year ago • 1 comments

GitHub Public API Limitations

This issue collates all of the feature gaps within the public GitHub REST API and GitHub GraphQL API

[!NOTE] See GitHub API Changelog for new feature announcements See GitHub Public Roadmap for planned upcoming features

[!IMPORTANT] Filter issues by github-api-limitation to see full backlog

GitHub REST API - Documentation

  • List Notifications endpoint /notifications
    • Filtering capabilities
      • No query parameter to filter notifications by inbox
      • No query parameter to filter notifications by saved
      • No query parameter to filter notifications by done
    • Response schema
      • No attribute in response indicating if a notification is done, saved or in the inbox.
      • Notifications of subject.type CheckSuite, RepositoryInvitation or WorkflowRun always have null for subject.url and subject.latest_comment_url
      • Notifications of subject.type Discussion return for subject.url / subject.latest_comment_url which doesn't point to the latest comment. We have had to implement a fix to find these ourselves. See #1583
    • Private repositories Notifications show in UI but not via this API
  • Notification
    • No endpoint to mark notification as unread (can only mark a notification as read)
    • No endpoint to find CheckSuite of WorkflowRun information by notification_id
    • No endpoint to save/unsave a notification
  • Authentication
    • No support for fine-grained tokens https://github.com/gitify-app/gitify/issues/1492

GitHub GraphQL API - Documentation

  • No query to fetch user notifications
  • No query to find CheckSuite of WorkflowRun information by notification_id

setchy avatar Mar 17 '24 11:03 setchy

While it's been a while since I was looking at it, this thread for adding support to view notifications to the gh CLI might have some pointers towards API workarounds, eg:

  • https://github.com/cli/cli/issues/659#issuecomment-1485939570
    • IIRC you cannot mark notifications as Done, just Read

    • A little light reverse engineering seems to show that the webpage makes a call to /notifications/beta/archive when I click the button

  • https://github.com/cli/cli/issues/659#issuecomment-2599592102
    • Seems like the Mobile app is using some graphQL API endpoints behind a feature flag

    • POST /graphql HTTP/2
      Host: api.github.com
      Content-Type: application/json
      Graphql-Features: merge_queue,private_profile_setting,project_next_field_configuration,issues_close_state,project_next_recent_connection,file_level_commenting,issue_types,sub_issues
      Accept: application/vnd.github.merge-info-preview+json,application/vnd.github.shadow-cat-preview+json,application/vnd.github.echo-preview+json,application/vnd.github.starfox-preview+json,application/vnd.github.doctor-strange-preview+json
      Apollographql-Client-Version: 1.192.0-152022212
      Authorization: Bearer REDACTED
      Accept-Encoding: gzip, deflate, br
      Accept-Language: en-GB;q=1.0, en-AU;q=0.9, ja-AU;q=0.8, fa-AU;q=0.7
      Content-Length: 4030
      User-Agent: GitHub/1.192.0 (com.github.stormbreaker.prod; build:152022212; iOS 18.2.1; iPhone15,3)
      X-Apollo-Operation-Type: query
      Apollographql-Client-Name: com.github.stormbreaker.prod-apollo-ios
      X-Apollo-Operation-Name: Inbox
      
      {
          "operationName": "Inbox",
          "query": "query Inbox($first: Int!, $after: String, $filterBy: NotificationThreadFilters, $query: String, $avatarSize: Int!) { viewer { __typename id ...WebNotificationsEnabledFragment notificationThreads( first: $first after: $after filterBy: $filterBy query: $query ) { __typename pageInfo { __typename hasNextPage endCursor } totalCount nodes { __typename ...NotificationThreadCommonFieldsFragment url subject { __typename ... on CheckSuite { ...InboxSubjectCheckSuiteFieldsFragment } ... on WorkflowRun { ...InboxSubjectWorkflowRunFieldsFragment } ... on Commit { ...InboxSubjectCommitFieldsFragment } ... on Gist { ...InboxSubjectGistFieldsFragment } ... on TeamDiscussion { ...InboxSubjectTeamDiscussionFieldsFragment } ... on Issue { ...InboxSubjectIssueFieldsFragment } ... on PullRequest { ...InboxSubjectPullRequestFieldsFragment } ... on Release { ...InboxSubjectReleaseFieldsFragment } ... on RepositoryVulnerabilityAlert { ...InboxSubjectRepositoryVulnerabilityAlertFieldsFragment } ... on RepositoryAdvisory { ...InboxSubjectRepositoryAdvisoryFieldsFragment } ...InboxSubjectDiscussionFieldsFragment ... on RepositoryDependabotAlertsThread { ...InboxSubjectRepositoryDependabotAlertsThreadFieldsFragment } ... on SecurityAdvisory { ...InboxSubjectSecurityAdvisoryFieldsFragment } } } } } }\nfragment AvatarFragment on Actor { __typename avatarUrl(size: $avatarSize) }\nfragment InboxSubjectCheckSuiteFieldsFragment on CheckSuite { __typename id url conclusion status }\nfragment InboxSubjectCommitFieldsFragment on Commit { __typename id abbreviatedOid url repository { __typename id owner { __typename id login } name } }\nfragment InboxSubjectDiscussionFieldsFragment on Discussion { __typename id url number discussionStateReason: stateReason answer { __typename id } category { __typename id isAnswerable } repository { __typename id isOrganizationDiscussionRepository } }\nfragment InboxSubjectGistFieldsFragment on Gist { __typename url id }\nfragment InboxSubjectIssueFieldsFragment on Issue { __typename id number issueState: state stateReason issueHtmlTitle: titleHTML url }\nfragment InboxSubjectPullRequestFieldsFragment on PullRequest { __typename id number pullRequestHtmlTitle: titleHTML url ...PullRequestStateIcon }\nfragment InboxSubjectReleaseFieldsFragment on Release { __typename id tagName url }\nfragment InboxSubjectRepositoryAdvisoryFieldsFragment on RepositoryAdvisory { __typename id url }\nfragment InboxSubjectRepositoryDependabotAlertsThreadFieldsFragment on RepositoryDependabotAlertsThread { __typename id notificationsPermalink }\nfragment InboxSubjectRepositoryVulnerabilityAlertFieldsFragment on RepositoryVulnerabilityAlert { __typename id permalink }\nfragment InboxSubjectSecurityAdvisoryFieldsFragment on SecurityAdvisory { __typename id ghsaId notificationsPermalink }\nfragment InboxSubjectTeamDiscussionFieldsFragment on TeamDiscussion { __typename url id }\nfragment InboxSubjectWorkflowRunFieldsFragment on WorkflowRun { __typename id url runNumber workflow { __typename id name } checkSuite { __typename id } }\nfragment ListSubscribableFragment on Subscribable { __typename id viewerSubscription viewerCanSubscribe }\nfragment NotificationThreadCommonFieldsFragment on NotificationThread { __typename id threadType title isUnread unreadItemsCount lastUpdatedAt subscriptionStatus summaryItemAuthor { __typename id ...AvatarFragment } summaryItemBody isArchived isSaved reason list { __typename ...ListSubscribableFragment ... on Repository { id owner { __typename id login } name } ... on User { id login userName: name } ... on Team { id organization { __typename id login } slug } ... on Organization { id login } } }\nfragment PullRequestStateIcon on PullRequest { __typename pullRequestState: state isDraft mergeQueueEntry { __typename id position } }\nfragment WebNotificationsEnabledFragment on User { __typename notificationSettings { __typename getsParticipatingWeb getsWatchingWeb } }",
          "variables":
          {
              "avatarSize": 48,
              "first": 30,
              "query": ""
          }
      }
      
    • And then if I click on a filter tab in the iOS app like 'unread' it changes query to is:unread; saved is is:saved, done is is:done, etc
    • Mark notification as 'undone':
      • POST /graphql HTTP/1.1
        Host: api.github.com
        GraphQL-Features: merge_queue,private_profile_setting,project_next_field_configuration,issues_close_state,project_next_recent_connection,file_level_commenting,issue_types,sub_issues
        Authorization: Bearer gho_REDACTED
        Accept: multipart/mixed;deferSpec=20220824,application/graphql-response+json,application/json,application/vnd.github.merge-info-preview+json,application/vnd.github.shadow-cat-preview+json,application/vnd.github.echo-preview+json,application/vnd.github.starfox-preview+json,application/vnd.github.doctor-strange-preview+json
        apollographql-client-version: 1.222.0-170680083
        Accept-Language: en-GB;q=1.0, en-AU;q=0.9, ja-AU;q=0.8, fa-AU;q=0.7
        Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8
        Content-Type: application/json
        Content-Length: 265
        User-Agent: GitHub/1.222.0 (com.github.stormbreaker.prod; build:170680083; iOS 18.6.0; iPhone15,3)
        X-APOLLO-OPERATION-TYPE: mutation
        apollographql-client-name: com.github.stormbreaker.prod-apollo-ios
        Connection: keep-alive
        X-APOLLO-OPERATION-NAME: MarkNotificationAsUndone
        
        {
          "operationName": "MarkNotificationAsUndone",
          "query": "mutation MarkNotificationAsUndone($notificationId: ID!) { markNotificationAsUndone(input: { id: $notificationId }) { __typename success } }",
          "variables": {
            "notificationId": "NT_kwDOAAuA47E4ODcxMjU0MzkzOjc1Mzg5MQ"
          }
        }
        
    • Save/unsave/view saved: https://github.com/gitify-app/gitify/issues/840#issuecomment-3208846701

Some of the private APIs used on https://github.com/notifications (though likely less useful as they use authenticity_token to prevent CSRF:

  • mark as read:
    • POST https://github.com/notifications/beta/mark
    • MIME Type: multipart/form-data
    • ------WebKitFormBoundaryLqH0emMzDeIcrXrh
      Content-Disposition: form-data; name="authenticity_token"
      
      REDACTED
      ------WebKitFormBoundaryLqH0emMzDeIcrXrh
      Content-Disposition: form-data; name="notification_ids[]"
      
      NT_kwDOAAuA47IxODEzREDACTED
      ------WebKitFormBoundaryLqH0emMzDeIcrXrh
      Content-Disposition: form-data; name="notification_ids[]"
      
      NT_kwDOAAuA47IxODM0REDACTED
      ------WebKitFormBoundaryLqH0emMzDeIcrXrh--
      
  • mark as unread:
    • POST https://github.com/notifications/beta/unmark
    • MIME Type: multipart/form-data
    • ------WebKitFormBoundaryBWyAgv8M6HPBkTAX
      Content-Disposition: form-data; name="authenticity_token"
      
      REDACTED
      ------WebKitFormBoundaryBWyAgv8M6HPBkTAX
      Content-Disposition: form-data; name="notification_ids[]"
      
      NT_kwDOAAuA47IxODEzREDACTED
      ------WebKitFormBoundaryBWyAgv8M6HPBkTAX
      Content-Disposition: form-data; name="notification_ids[]"
      
      NT_kwDOAAuA47IxODM0REDACTED
      ------WebKitFormBoundaryBWyAgv8M6HPBkTAX--
      
  • mark as done / archive:
    • POST https://github.com/notifications/beta/archive
    • MIME Type: multipart/form-data
    • ------WebKitFormBoundary1RpEegHaXAmtMNxM
      Content-Disposition: form-data; name="authenticity_token"
      
      REDACTED
      ------WebKitFormBoundary1RpEegHaXAmtMNxM
      Content-Disposition: form-data; name="notification_ids[]"
      
      NT_kwDOAAuA47IxODEzREDACTED
      ------WebKitFormBoundary1RpEegHaXAmtMNxM
      Content-Disposition: form-data; name="notification_ids[]"
      
      NT_kwDOAAuA47IxODM0REDACTED
      ------WebKitFormBoundary1RpEegHaXAmtMNxM--
      

0xdevalias avatar Aug 21 '25 01:08 0xdevalias