Logo

깃허브의 REST API 호출 방법

깃허브(GitHub)는 개발자들이 소프트웨어 프로젝트를 관리하고 협업하는 데 필요한 다양한 기능을 제공하는 매우 인기있는 플랫폼인데요. 일상적인 개발을 할 때는 깃허브의 웹사이트를 통해서 대부분의 작업을 처리할 수 있지만 개발 과정을 자동화하거나 다른 개발 도구와 통합할 때는 프로그래밍적으로 접근해야 할 때가 있습니다.

이럴 때는 깃허브에서 제공하는 REST API를 사용하면 되는데요. 이번 포스팅에서는 터미널에서 curl로 깃허브의 REST API를 실제로 같이 호출해보면서 기본적인 API 사용 방법에 대해서 알아보겠습니다.

터미널에서 간단한 명령어를 입력하여 웹 페이지나 API 데이터를 요청하고 받을 수 있는 HTTP 클라이언트 도구인 curl에 대한 자세한 설명은 관련 포스팅을 참고 바랍니다.

시작하기

깃허브 REST API의 최상위 URL은 https://api.github.com 인데요. curl로 이 주소를 찔러보면 깃허브의 REST API에서 제공하는 주요 리소스(resource)에 대한 URL 목록을 얻을 수 있습니다.

$ curl https://api.github.com
{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "label_search_url": "https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}",
  "notifications_url": "https://api.github.com/notifications",
  "organization_url": "https://api.github.com/orgs/{org}",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_teams_url": "https://api.github.com/orgs/{org}/teams",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "topic_search_url": "https://api.github.com/search/topics?q={query}{&page,per_page}",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

이 중에서 대부분의 URL은 인증을 해야 정상적으로 데이터가 수신이 되는데요. 간혹 미인증 상태에서도 호출이 가능한 https://api.github.com/rate_limit와 같은 URL도 있습니다.

$ curl https://api.github.com/rate_limit
{
  "resources": {
    "core": {
      "limit": 60,
      "remaining": 58,
      "reset": 1687486063,
      "used": 2,
      "resource": "core"
    },
    "graphql": {
      "limit": 0,
      "remaining": 0,
      "reset": 1687487965,
      "used": 0,
      "resource": "graphql"
    },
    "integration_manifest": {
      "limit": 5000,
      "remaining": 5000,
      "reset": 1687487965,
      "used": 0,
      "resource": "integration_manifest"
    },
    "search": {
      "limit": 10,
      "remaining": 10,
      "reset": 1687484425,
      "used": 0,
      "resource": "search"
    }
  },
  "rate": {
    "limit": 60,
    "remaining": 58,
    "reset": 1687486063,
    "used": 2,
    "resource": "core"
  }
}

수신된 내용을 통해서 우리는 한 시간에 최대 60번 API 호출이 가능하고, 여태까지 2번 호출해서 58번 더 호출이 가능한 것을 알 수 있습니다.

인증

인터넷에 공개되어 있는 대부분의 API가 그러하듯 깃허브 REST API도 제대로 사용하려면 인증(authentication) 과정을 거처야하는데요. 인증을 하게 되면 시간 당 요청 가능한 횟수가 5000건으로 확 늘어나는 효과도 있기 때문에 가급적 인증을 하는 것이 유리하겠죠?

인증 방법은 상당히 간단한데요. Authorization 헤더에 Bearer 토큰으로 넘기가가면 하면 됩니다. GitHub에서는 여러 종류의 엑세스 토큰이 있지만 개인적으로 사용할 때는 깃허브 웹사이트에 들어가셔서 개인용 액세스 토큰(personal access token)을 발급받으셔야 합니다.

개인용 엑세스 토큰(personal access token)을 발급 받는 방법은 GitHub 공식 문서를 참고 바랍니다.

발급받은 엑세스 토큰을 보시면 굉장히 긴 문자열이라는 것을 알 수 있는데요. 매번 curl 명령어를 날릴 때 마다 넘기기에는 번거로울 수 있기 때문에 환경 변수로 저장해놓고 시작하겠습니다.

$ export GH_TOKEN=github_pat_11A...Jme

현재 인증되어 있는 사용자 정보를 확인할 수 있는 GET /user 엔드포인트를 한번 호출해볼까요?

$ curl https://api.github.com/user -H "Authorization: Bearer $GH_TOKEN"

그러면 깃허브에 등록되어 있는 본인의 사용자 정보가 수신이 될 것입니다.

{
  "login": "DaleSeo",
  "id": 5466341,
  "node_id": "MDQ6VXNlcjU0NjYzNDE=",
  "avatar_url": "https://avatars.githubusercontent.com/u/5466341?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/DaleSeo",
  "html_url": "https://github.com/DaleSeo",
  "followers_url": "https://api.github.com/users/DaleSeo/followers",
  "following_url": "https://api.github.com/users/DaleSeo/following{/other_user}",
  "gists_url": "https://api.github.com/users/DaleSeo/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/DaleSeo/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/DaleSeo/subscriptions",
  "organizations_url": "https://api.github.com/users/DaleSeo/orgs",
  "repos_url": "https://api.github.com/users/DaleSeo/repos",
  "events_url": "https://api.github.com/users/DaleSeo/events{/privacy}",
  "received_events_url": "https://api.github.com/users/DaleSeo/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Dale Seo",
  "company": null,
  "blog": "https://www.daleseo.com",
  "location": "Toronto, ON",
  "email": null,
  "hireable": true,
  "bio": null,
  "twitter_username": "DaleSeo",
  "public_repos": 155,
  "public_gists": 8,
  "followers": 281,
  "following": 16,
  "created_at": "2013-09-16T02:23:59Z",
  "updated_at": "2023-06-23T11:52:59Z"
}

저장소 생성

간단한 실습을 위해서 깃허브의 REST API로 Test001이라는 저장소(repository)를 하나 만들어볼까요?

저장소를 만들 때는 POST /user/repos 엔드포인트를 사용하는데요. 요청 바디(body)의 name 속성을 통해서 저장소 이름을 넘겨주면 됩니다.

$ curl -X POST https://api.github.com/user/repos \
  -H "Authorization: Bearer $GH_TOKEN" \
  -d '{"name":"Test001"}'

API가 응답한 결과를 보시면 html_url이라는 항목이 보이실텐데요. 이 URL을 브라우저에서 열어보시면 새로 만들어진 저장소를 확인하실 수 있으실 것 입니다.

{
  "id": 657603943,
  "node_id": "R_kgDOJzI9Zw",
  "name": "Test001",
  "full_name": "DaleSeo/Test001",
  "private": false,
  "html_url": "https://github.com/DaleSeo/Test001",
  "description": null,
  "fork": false,
  "url": "https://api.github.com/repos/DaleSeo/Test001",
  "forks_url": "https://api.github.com/repos/DaleSeo/Test001/forks"
  // 생략
}

만약에 403 상태 코드와 함께 다음과 같은 오류 메세지를 응답된다면 인증을 위해 사용하고 계신 엑세스 토큰에 Administration대한 읽기/쓰기 권한이 주어졌는지 확인해보시길 바랍니다.

{
  "message": "Resource not accessible by personal access token",
  "documentation_url": "https://docs.github.com/rest/reference/repos#create-a-repository-for-the-authenticated-user"
}

참고로 자신의 계정 아래가 아닌 자신이 속한 조직(organization) 아래에 저장소를 만들고 싶다면, POST /orgs/{org}/repos 엔드포인트를 대신 사용하면 됩니다.

$ curl -X POST https://api.github.com/orgs/DaleSchool/repos \
  -H "Authorization: Bearer $GH_TOKEN" \
  -d '{"name":"Test001"}'

본 포스팅에서는 이 조직에 만든 저장소를 상대로 계속해서 실습을 이어나가도록 하겠습니다.

이슈 추가

이제 실습 저장소에 새로운 이슈(issue)를 하나 만들어볼까요?

이슈를 추가할 때는 POST /repos/{owner}/{repo}/issues 엔드포인트를 사용하는데요, 요청 바디의 title 속성을 통해서 이슈의 제목을 명시합니다.

$ curl -X POST https://api.github.com/repos/DaleSchool/Test001/issues \
  -H "Authorization: Bearer $GH_TOKEN" \
  -d '{"title":"버그가 있어요! 🐛"}'

마찬가지로 응답 내용에서 html_url을 복사해서 브라우저에서 열어보시면 실습 저장소에 생성된 이슈 1번을 보실 수 있을 실 겁니다.

{
  "url": "https://api.github.com/repos/DaleSchool/Test001/issues/1",
  "repository_url": "https://api.github.com/repos/DaleSchool/Test001",
  "labels_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1/labels{/name}",
  "comments_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1/comments",
  "events_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1/events",
  "html_url": "https://github.com/DaleSchool/Test001/issues/1",
  "id": 1771420774,
  "node_id": "I_kwDOJzJu2M5plbxm",
  "number": 1,
  "title": "버그가 있어요! 🐛",
  "labels": [],
  "state": "open"
  // 생략
}

만약에 403 상태 코드와 함께 다음과 같은 오류 메세지를 응답된다면 인증을 위해 사용하고 계신 엑세스 토큰에 Issues대한 읽기/쓰기 권한이 주어졌는지 확인해보시길 바랍니다.

{
  "message": "Resource not accessible by personal access token",
  "documentation_url": "https://docs.github.com/rest/reference/repos#create-a-repository-for-the-authenticated-user"
}

이슈 수정

이슈에 본문을 추가하고 이슈를 닫으려면 어떻게 해야 할까요?

이슈를 수정할 때는 PATCH /repos/{owner}/{repo}/issues/{issue_number} 엔드포인트를 사용하면 됩니다.

$ curl -X PATCH https://api.github.com/repos/DaleSchool/Test001/issues/1 \
  -H "Authorization: Bearer $GH_TOKEN" \
  -d '{"body":"버그를 고쳤습니다. 😄","state":"closed"}'

이제 위에서 열어놨던 이슈 페이지를 브라우저를 새로 고침해보시면 해당 이슈에 본문이 추가되어 있고 닫힌 것을 보실 수 있을 실 거에요.

{
  "url": "https://api.github.com/repos/DaleSchool/Test001/issues/1",
  "repository_url": "https://api.github.com/repos/DaleSchool/Test001",
  "labels_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1/labels{/name}",
  "comments_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1/comments",
  "events_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1/events",
  "html_url": "https://github.com/DaleSchool/Test001/issues/1",
  "id": 1771420774,
  "node_id": "I_kwDOJzJu2M5plbxm",
  "number": 1,
  "title": "버그를 고쳤습니다. 😄",
  "user": {
    "login": "DaleSeo",
    "id": 5466341,
    "node_id": "MDQ6VXNlcjU0NjYzNDE=",
    "avatar_url": "https://avatars.githubusercontent.com/u/5466341?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/DaleSeo",
    "html_url": "https://github.com/DaleSeo",
    "followers_url": "https://api.github.com/users/DaleSeo/followers",
    "following_url": "https://api.github.com/users/DaleSeo/following{/other_user}",
    "gists_url": "https://api.github.com/users/DaleSeo/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/DaleSeo/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/DaleSeo/subscriptions",
    "organizations_url": "https://api.github.com/users/DaleSeo/orgs",
    "repos_url": "https://api.github.com/users/DaleSeo/repos",
    "events_url": "https://api.github.com/users/DaleSeo/events{/privacy}",
    "received_events_url": "https://api.github.com/users/DaleSeo/received_events",
    "type": "User",
    "site_admin": false
  },
  "labels": [],
  "state": "closed"
}

댓글 달기

이번에는 이슈에 댓글을 한 번 달아볼까요?

댓글을 추가할 때는 POST /repos/{owner}/{repo}/issues/{issue_number}/comments 엔드포인트를 사용합니다.

$ curl -X POST https://api.github.com/repos/DaleSchool/Test001/issues/1/comments \
  -H "Authorization: Bearer $GH_TOKEN" \
  -d '{"body":"감사합니다! ❤️"}'

다시 브라우저를 새로 고침해보시면 해당 이슈가 닫혀있는 것을 보실 수 있으실 거에요.

{
  "url": "https://api.github.com/repos/DaleSchool/Test001/issues/comments/1604272163",
  "html_url": "https://github.com/DaleSchool/Test001/issues/1#issuecomment-1604272163",
  "issue_url": "https://api.github.com/repos/DaleSchool/Test001/issues/1",
  "id": 1604272163,
  "node_id": "IC_kwDOJzJu2M5fn0Aj",
  "created_at": "2023-06-23T13:15:49Z",
  "updated_at": "2023-06-23T13:15:49Z",
  "author_association": "OWNER",
  "body": "감사합니다! ❤️",
  "reactions": {
    "url": "https://api.github.com/repos/DaleSchool/Test001/issues/comments/1604272163/reactions",
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
  }
  // 생략
}

저장소 삭제

마지막으로 실습 저장소를 정리하도록 하겠습니다.

저장소를 삭제할 때는 DELETE /repos/{owner}/{repo} 엔드포인트를 사용합니다.

$ curl -I -X DELETE https://api.github.com/repos/DaleSchool/Test001 \
  -H "Authorization: Bearer $GH_TOKEN"

이 엔드포인트의 응답에는 바디가 비어있기 때문에 curl 명령어의 -I 플래그를 사용하여 대신 응답 헤더(header)를 출력하였습니다. 아래와 같이 No Content를 나타내는 204 상태 코드가 수신되면 정상적으로 저장소가 삭제된 것입니다.

HTTP/2 204
server: GitHub.com
date: Fri, 23 Jun 2023 12:41:16 GMT
github-authentication-token-expiration: 2023-07-23 07:55:58 -0400
x-github-media-type: github.v3; format=json
x-github-api-version-selected: 2022-11-28
x-ratelimit-limit: 5000
x-ratelimit-remaining: 4978
x-ratelimit-reset: 1687524394
x-ratelimit-used: 22
x-ratelimit-resource: core
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
access-control-allow-origin: *
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
x-xss-protection: 0
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'
vary: Accept-Encoding, Accept, X-Requested-With
x-github-request-id: 606A:93E1:95D0AE:13240FA:649592EC

이제 다시 브라우저에서 실습 저장소 페이지를 열어보려고 하시면 페이지를 찾을 수 없다는 페이지가 뜰 것입니다.

마치면서

지금까지 깃허브의 REST API를 사용하여 실습 저장소를 만들고 이슈와 댓글을 조작해보았습니다. 이번 시간에 우리가 함께 호출해본 URL들은 사실 빙산의 일각일 뿐이며 깃허브는 엄청나게 방대한 REST API를 제공하고 있습니다. 하지만 본 포스팅에서 다룬 기본 사용법만 숙지하시면 다른 URL은 공식 문서를 참고하셔서 충분히 활용하실 수 있을실 거에요.

그럼 깃허브의 마스코트를 출력해주는 재미있는 엔드포인트를 호출하면서 마무리하도록 하겠습니다 😸

$ curl https://api.github.com/octocat


               MMM.           .MMM
               MMMMMMMMMMMMMMMMMMM
               MMMMMMMMMMMMMMMMMMM      ___________________________________
              MMMMMMMMMMMMMMMMMMMMM    |                                   |
             MMMMMMMMMMMMMMMMMMMMMMM   | Avoid administrative distraction. |
            MMMMMMMMMMMMMMMMMMMMMMMM   |_   _______________________________|
            MMMM::- -:::::::- -::MMMM    |/
             MM~:~ 00~:::::~ 00~:~MM
        .. MMMMM::.00:::+:::.00::MMMMM ..
              .MM::::: ._. :::::MM.
                 MMMM;:::::;MMMM
          -MM        MMMMMMM
          ^  M+     MMMMMMMMM
              MMMMMMM MM MM MM
                   MM MM MM MM
                   MM MM MM MM
                .~~MM~MM~MM~MM~~.
             ~~~~MM:~MM~~~MM~:MM~~~~
            ~~~~~~==~==~~~==~==~~~~~~
             ~~~~~~==~==~==~==~~~~~~
                 :~==~==~==~==~~