The convenience modules are thin wrappers over GhEx.REST that fill in the endpoint path. Each function returns the same {:ok, body, meta} / {:error, reason} shape as the core and passes opts through to Req (so :params, headers, and a Req.Test plug all work). They cover the common paths; for anything else, call GhEx.REST directly or follow the Extending gh_ex guide to build an application-owned resource module.

On the write wrappers (create, update, merge, and the like), the attrs argument is the sole request body: each sets :json to attrs, so a :json passed in opts is ignored.

The list_* functions return a single page. Each has a stream_* companion that auto-paginates into a lazy Stream of individual items, following Link: rel="next" (see the Pagination guide). The wrapped endpoints (Search, Actions runs/workflows/jobs, Checks) unwrap their array key for you.

# every open issue, not just the first page
client
|> GhEx.Issues.stream("elixir-lang", "elixir", params: [state: "open", per_page: 100])
|> Stream.map(& &1["number"])
|> Enum.to_list()

For a path without a stream_* wrapper, call GhEx.REST.stream/3 directly with the path and, for an object-wrapped response, the items: key.

Issues

GhEx.Issues — list, get, create, update, create or edit comments, mutate assignees, manage the repository label vocabulary, and add, remove, or replace labels on issues.

GhEx.Issues.list(client, "elixir-lang", "elixir", params: [state: "open"])
GhEx.Issues.create(client, "o", "r", %{title: "Bug", body: "..."})
GhEx.Issues.create_comment(client, "o", "r", 7, "thanks for the report")
GhEx.Issues.update_comment(client, "o", "r", comment_id, "build is green")
GhEx.Issues.add_assignees(client, "o", "r", 7, ["octocat"])
GhEx.Issues.remove_assignees(client, "o", "r", 7, ["hubot"])
GhEx.Issues.stream_labels(client, "o", "r") |> Enum.take(100)
GhEx.Issues.create_label(client, "o", "r", %{name: "priority:high", color: "d93f0b"})
GhEx.Issues.update_label(client, "o", "r", "priority:high", %{description: "Address next"})
GhEx.Issues.add_labels(client, "o", "r", 7, ["bug", "p1"])
GhEx.Issues.remove_label(client, "o", "r", 7, "needs triage")
GhEx.Issues.replace_all_labels(client, "o", "r", 7, ["confirmed", "p1"])

Pull requests

GhEx.PullRequests — list, get, create, update, synchronous and asynchronous merge, merge status, commits, raw diff/patch, files, review requests, reviews, and line-anchored review comments. GhEx.Stacks — list, get, create, add, and unstack pull request stacks. See the Stacked pull requests guide for the asynchronous merge flow and GraphQL/webhook fields.

GhEx.PullRequests.create(client, "o", "r", %{title: "Fix", head: "fix", base: "main"})
{:ok, merged?, _meta} = GhEx.PullRequests.is_merged(client, "o", "r", 42)
GhEx.PullRequests.list_commits(client, "o", "r", 42)
GhEx.PullRequests.list_files(client, "o", "r", 42)
GhEx.PullRequests.get_diff(client, "o", "r", 42)
GhEx.PullRequests.merge(client, "o", "r", 42, %{merge_method: "squash"})
GhEx.PullRequests.request_reviewers(client, "o", "r", 42, %{reviewers: ["octocat"]})
GhEx.PullRequests.create_review(client, "o", "r", 42, %{event: "APPROVE"})
GhEx.Stacks.create(client, "o", "r", %{pull_requests: [41, 42]})
GhEx.PullRequests.merge_async(client, "o", "r", 42, %{merge_action: "default"})

GhEx.PullRequests.create_comment(client, "o", "r", 42, %{
  body: "Please handle nil.", commit_id: sha, path: "lib/a.ex", line: 12, side: "RIGHT"
})

Repositories and contents

GhEx.Repositories — get, list (org/user), create, update, delete, collaborator authorization, commits, branches, and ETag-conditional event polling. GhEx.Contents — read and write files.

GhEx.Repositories.get(client, "elixir-lang", "elixir")
GhEx.Repositories.list_for_org(client, "elixir-lang", params: [type: "public"])

{:ok, is_collaborator, _meta} = GhEx.Repositories.is_collaborator(client, "o", "r", actor)
{:ok, permission, _meta} = GhEx.Repositories.get_collaborator_permission(client, "o", "r", actor)

{:ok, events, meta} = GhEx.Repositories.events(client, "o", "r")

case GhEx.Repositories.events(client, "o", "r", headers: [{"if-none-match", meta.etag}]) do
  {:ok, :not_modified, poll_meta} -> poll_meta.headers["x-poll-interval"]
  {:ok, new_events, poll_meta} -> {new_events, poll_meta.etag}
end

{:ok, file, _meta} = GhEx.Contents.get(client, "o", "r", "mix.exs", params: [ref: "main"])

GhEx.Contents.create_or_update_file(client, "o", "r", "NOTES.md", %{
  message: "add notes",
  content: Base.encode64("hello"),
  sha: file["sha"]
})

content is Base64-encoded, and updating an existing file needs its blob sha.

Commits

GhEx.Commits — get a commit, compare refs, find associated pull requests, and list or create commit comments. Repository-wide listing remains in GhEx.Repositories.list_commits/4 and GhEx.Repositories.stream_commits/4.

GhEx.Commits.get(client, "o", "r", sha)
GhEx.Commits.compare(client, "o", "r", "main", "feature/agent")
GhEx.Commits.list_pulls(client, "o", "r", sha)
GhEx.Commits.create_comment(client, "o", "r", sha, %{body: "Looks good"})

Git references

GhEx.Git — get, create, and delete branch or tag references. Read and delete helpers accept both heads/name and fully qualified refs/heads/name forms; create always sends GitHub the required fully qualified form.

GhEx.Git.get_ref(client, "o", "r", "heads/main")
GhEx.Git.create_ref(client, "o", "r", %{ref: "heads/release", sha: sha})
GhEx.Git.delete_ref(client, "o", "r", "refs/heads/release")

Releases

GhEx.Releases — list, get, create, update, delete, generate notes, and upload raw release assets to GitHub's dedicated upload endpoint.

GhEx.Releases.get_latest(client, "o", "r")

GhEx.Releases.create(client, "o", "r", %{
  tag_name: "v1.0.0",
  name: "v1.0.0",
  generate_release_notes: true
})

asset = %{
  name: "gh_ex.tar.gz",
  content_type: "application/gzip",
  data: File.read!("gh_ex.tar.gz"),
  label: "Linux archive"
}

GhEx.Releases.upload_asset(client, "o", "r", release_id, asset)

Actions

GhEx.Actions — workflows, runs, jobs, artifacts, logs, dispatch, deployment approvals, cancel, and rerun. A workflow is its numeric id or its file name ("ci.yml"). Artifact and run-log downloads return ZIP bytes; job-log downloads return plain-text bytes.

GhEx.Actions.list_workflows(client, "o", "r")
GhEx.Actions.dispatch_workflow(client, "o", "r", "ci.yml", %{ref: "main", inputs: %{env: "prod"}})
GhEx.Actions.list_runs(client, "o", "r", params: [branch: "main", status: "failure"])
GhEx.Actions.rerun(client, "o", "r", run_id)
GhEx.Actions.list_pending_deployments(client, "o", "r", run_id)
GhEx.Actions.review_pending_deployments(client, "o", "r", run_id, %{
  environment_ids: [environment_id],
  state: "approved",
  comment: "Ship it"
})
{:ok, zip_bytes, _meta} = GhEx.Actions.download_artifact(client, "o", "r", artifact_id, "zip")
{:ok, logs, _meta} = GhEx.Actions.download_job_logs(client, "o", "r", job_id)
GhEx.Actions.rerun_failed_jobs(client, "o", "r", run_id)

Deployments

GhEx.Deployments — deployment requests and their append-only status history. The deployment object identifies the ref and environment; automation waiting for completion should inspect the latest deployment status rather than polling the deployment object for a state change.

{:ok, deployment, _meta} =
  GhEx.Deployments.create(client, "o", "r", %{
    ref: "main",
    environment: "production",
    required_contexts: []
  })

GhEx.Deployments.create_status(client, "o", "r", deployment["id"], %{
  state: "in_progress",
  log_url: "https://deploys.example.test/42"
})

{:ok, statuses, _meta} = GhEx.Deployments.list_statuses(client, "o", "r", deployment["id"])
latest_status = List.first(statuses)

Activity

GhEx.Activity — repository, organization, user, and public event feeds plus repository starring. Each feed/list has a lazy stream companion. Watching stays separate from starring and is not part of this module.

GhEx.Activity.stream_repo_events(client, "o", "r") |> Enum.take(100)
GhEx.Activity.list_starred(client, "octocat")
{:ok, starred?, _meta} = GhEx.Activity.starred?(client, "o", "r")
GhEx.Activity.star(client, "o", "r")

Checks and statuses

GhEx.Checks — check runs, annotations, and run rerequests. GhEx.Statuses — commit statuses.

GhEx.Checks.create_run(client, "o", "r", %{name: "lint", head_sha: sha, status: "in_progress"})
GhEx.Checks.list_for_ref(client, "o", "r", sha)
{:ok, run_or_nil, _meta} = GhEx.Checks.find_run_for_ref(client, "o", "r", sha, "lint", app_id)
GhEx.Checks.stream_annotations(client, "o", "r", check_run_id) |> Enum.take(100)
GhEx.Checks.rerequest_run(client, "o", "r", check_run_id)

GhEx.Statuses.create(client, "o", "r", sha, %{state: "success", context: "ci/lint"})
GhEx.Statuses.get_combined(client, "o", "r", "main")

Security alerts

GhEx.CodeSecurity covers repository code-scanning, Dependabot, and secret-scanning alerts. The corresponding security feature must be available and enabled, and the token needs the matching repository alert permission. Updates require write permission.

GhEx.CodeSecurity.list_alerts(client, "o", "r", params: [state: "open"])
GhEx.CodeSecurity.stream_dependabot_alerts(client, "o", "r") |> Enum.take(100)
GhEx.CodeSecurity.get_secret_alert(client, "o", "r", alert_number, params: [hide_secret: true])
GhEx.CodeSecurity.update_alert(client, "o", "r", alert_number, %{
  state: "dismissed",
  dismissed_reason: "false positive"
})

GhEx.Search — repositories, code, issues_and_pull_requests, users, commits. The first argument is the q query; pass params: for sort and order.

GhEx.Search.repositories(client, "tetris language:elixir", params: [sort: "stars"])
GhEx.Search.issues_and_pull_requests(client, "repo:o/r is:open label:bug")

Users, organizations, and teams

GhEx.Users, GhEx.Organizations, GhEx.Teams.

GhEx.Users.get_authenticated(client)
GhEx.Users.get(client, "joshrotenberg")
GhEx.Organizations.list_members(client, "elixir-lang")
GhEx.Teams.list(client, "elixir-lang")

Gists

GhEx.Gists — list, get, create, update, delete.

GhEx.Gists.create(client, %{
  description: "example",
  public: false,
  files: %{"hello.txt" => %{content: "hi"}}
})

Webhooks

GhEx.Hooks manages repository webhook configurations. GhEx.Webhooks is the receiving side: verify a delivery signature and parse the payload. See the Webhooks guide for the full receiver pattern.

GhEx.Hooks.create(client, "o", "r", %{
  active: true,
  events: ["push", "pull_request"],
  config: %{url: "https://example.test/github", content_type: "json", secret: secret}
})

with :ok <- GhEx.Webhooks.verify(body, signature, secret),
     {:ok, payload} <- GhEx.Webhooks.parse(body) do
  handle(event_name, payload)
end