REST API, GraphQL API, gh api, Pagination, Rate Limits, and Automation Clients: Concepts, Architecture, and Mental Model
GitHub APIs are contracts, not shortcuts around authorization. Build a beginner-first mental model for REST resources, GraphQL connections, authentication, version headers, pagination, rate limits, conditional requests, retry boundaries, and observable automation.
Learning objectives
- Distinguish REST resources/endpoints from GraphQL schema, connections, queries, and mutations.
- Explain authentication versus authorization, REST API versions/media types, and resource ownership.
- Traverse complete REST and GraphQL result sets without silently accepting a first page.
- Interpret primary/secondary rate-limit evidence and use bounded backoff/conditional requests.
- Classify writes by idempotence and design observable duplicate-resistant mutations.
1. The problem: API automation needs more than a successful request
Earlier chapters used gh api to inspect workflows,
packages, security findings, environments, and attestations. Those
examples were deliberately narrow. A production client has to
preserve correctness when the collection spans many pages, when the
resource is private, when GitHub returns a throttling response, when
GraphQL returns partial data, or when a create request succeeds on
the server but the client loses the response.
Start with a durable rule:
HTTP success is not the same as complete business truth. A first-page 200 OK can still omit most
repositories. A GraphQL 200 can include an
errors array. A timed-out POST can still have created
the object. The client must model the hosted resource, authorization
boundary, traversal contract, and desired state before it mutates
anything.
2. Mental model: identity → contract → traversal → decision → mutation
flowchart TD
I["Automation identity"] --> A["Authentication"]
A --> Z["Authorization"]
Z --> R["REST resource + version"]
Z --> G["GraphQL schema + query"]
R --> P["Link/page traversal"]
G --> C["Connection/cursor traversal"]
P --> E["Complete normalized evidence"]
C --> E
E --> D["Policy / dry-run decision"]
D --> N{"Mutation needed?"}
N -->|no| O["No-op evidence"]
N -->|yes| M["Duplicate/idempotence guard"]
M --> W["Bounded write"]
W --> V["Read-after-write verification"]
R -. status/rate headers .-> B["Backoff controller"]
G -. errors/rateLimit .-> B
Authentication answers “who is calling?” Authorization answers “what may that identity do to this owner/resource?” REST and GraphQL then expose two different contracts over GitHub objects. Pagination is part of correctness, not a performance afterthought. Only a complete, error-checked input set should reach a mutation decision.
3. REST resources versus GraphQL schema
| Dimension | REST | GraphQL |
|---|---|---|
| Model |
Resource-oriented endpoints such as
GET /repos/OWNER/REPO/issues.
|
Typed schema queried through one GraphQL endpoint. |
| Fields | Endpoint defines response shape; parameters filter/sort where supported. | Client selects exactly the fields it needs. |
| Pagination |
Follow response Link relations; do not invent
page URLs.
|
Connections use opaque cursors and pageInfo.
|
| Versioning |
Date-versioned contract; this chapter uses
2026-03-10.
|
Schema/changelog/deprecation process; REST version header is not GraphQL versioning. |
| Errors | HTTP status + JSON body + headers. |
HTTP can be 200 while errors and partial
data coexist.
|
REST is often clearer for one resource or collection. GraphQL can reduce calls when a client needs related fields across several objects, but query depth, node counts, cost, and partial-error handling become part of the design.
4. Authentication, API versions, media types, and ownership
Current GitHub REST integrations should send
Accept: application/vnd.github+json and an explicit
supported X-GitHub-Api-Version. The course baseline is
2026-03-10. GitHub currently documents that a REST
request without the version header falls back to
2022-11-28; do not make that implicit fallback part of
a production contract.
gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2026-03-10" repos/OWNER/REPO --jq '{full_name,visibility,default_branch}'
gh api uses the authenticated GitHub CLI identity. If
you intentionally want to observe public unauthenticated behavior,
use a client that omits Authorization, such as curl; do
not manipulate or expose the CLI credential store.
404 Not Found when the caller cannot see it.
Verify host, owner/repository, authenticated identity, repository
selection, and documented permissions before concluding that the
resource is absent.
5. Pagination is a correctness requirement
Many REST list endpoints return a subset. GitHub documents a default
of 30 items for many endpoints and a maximum
per_page of 100 for most endpoints, but each endpoint
reference is authoritative. Follow the server-provided
Link header until rel="next" disappears.
gh api --paginate -H "X-GitHub-Api-Version: 2026-03-10" "repos/OWNER/REPO/issues?state=all&per_page=2" --jq '.[] | {number,title,state}'
GraphQL connections require first or
last from 1 through 100. Request
pageInfo { hasNextPage endCursor } and feed the
previous cursor into after.
gh api graphql --paginate expects an
$endCursor: String variable and the relevant
pageInfo fields.
6. Primary and secondary rate limits
On GitHub.com, current primary limits include 60 REST requests per hour for unauthenticated public-data requests and generally 5,000 REST requests per hour for an authenticated user. GraphQL generally provides 5,000 points per hour for a user. Rate budget is not authorization.
Secondary rate limits protect service reliability even when primary
budget remains. Current guidance discusses concurrency,
endpoint/query point pressure, CPU time, and mutation bursts. A REST
throttling response can be 403 or 429;
GraphQL may surface rate errors with HTTP 200 or 403. Honor
Retry-After; if
x-ratelimit-remaining reaches zero, wait until
x-ratelimit-reset; otherwise back off exponentially and
stop after a bounded number of attempts.
gh api -i -H "X-GitHub-Api-Version: 2026-03-10" rate_limit --silent
gh api graphql -f query='query { viewer { login } rateLimit { cost remaining resetAt } }'
7. Conditional requests, retries, and idempotence
Many REST responses include ETag, and many include
Last-Modified. An authenticated conditional GET that
returns 304 Not Modified does not count against the
primary REST rate limit. This is useful for low-frequency
reconciliation when an event is unavailable.
| Operation | Retry posture | Reason |
|---|---|---|
| GET/HEAD | Usually retryable after bounded transient/rate waits. | Read does not create duplicate state. |
| PUT to a documented desired state | Often idempotent, but verify endpoint semantics. | Repeated desired representation may converge. |
| POST create | Never blind-retry by default. Re-read for a durable marker first. | The server may have created the object before the client timed out. |
| PATCH | State/endpoint dependent; use preconditions where supported and verify after write. | Repeated state-sensitive changes can diverge. |
8. Read-only inspection before mutation
gh --version
gh auth status
gh api -i -H "X-GitHub-Api-Version: 2026-03-10" user --jq '{login,id}'
gh api -i -H "X-GitHub-Api-Version: 2026-03-10" rate_limit --silent
gh api graphql -f query='query { viewer { login } rateLimit { cost remaining resetAt } }'
Production logs should capture safe request evidence such as operation/endpoint, host, status, GitHub request ID, duration, page/item counts, and rate metadata. Never log bearer tokens, Authorization headers, whole private response bodies, or sensitive fields the client does not need.
9. Why this matters in DevOps
API clients become control-plane software: they inventory repositories, create issues/deployments, manage policy, and connect GitHub to other systems. A first-page bug can omit half an estate; a blind POST retry can duplicate incidents; an over-scoped identity can turn one parsing flaw into a broad compromise. Treat API automation like production software: explicit contracts, least privilege, complete traversal, bounded resource use, observable decisions, and verified state transitions.
Knowledge check
A REST issue-list response contains 30 items and no code checks
the Link header. What can you conclude about
completeness?
Nothing. Thirty may be only the first page. Follow
rel="next" until it disappears or use a documented
paginator.
Why does an authenticated 404 not prove a private repository is absent?
GitHub can conceal private resources behind 404 when the caller lacks visibility. Verify actor, host, owner/repository, repository selection, and permissions.
GraphQL returns HTTP 200 plus data and
errors. Is this a complete success?
No. GraphQL can return partial data with errors. Reject or explicitly classify partial results before making policy or mutation decisions.
A POST create request times out. What should precede a retry?
Read/search for a durable desired-state marker or endpoint-supported idempotency evidence; do not assume timeout means the server did nothing.
Why is per_page=100 not a substitute for
pagination?
Collections can exceed 100 and endpoint limits vary. Page size reduces calls; it does not prove you reached the end.
Summary
Correct GitHub API automation treats REST/GraphQL as permissioned, versioned, paginated, rate-limited contracts. The client names its host/identity/resource, retrieves every page, inspects errors/status/rate metadata, minimizes fields, uses conditional reads or events where appropriate, makes dry-run/desired-state decisions, and verifies the hosted result.
Official references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.