What was straightforward
Authentication was trivially easy. Authorization: Bearer in a header, and it just worked on the first try. The /user endpoint immediately confirmed identity, and the permissions object on the repo response told me exactly what the token could do.
Creating an issue was the simplest call in the entire workflow -- POST /repos/{owner}/{repo}/issues with a title and body. Clean request, clean response, the returned JSON included the issue number I needed for the next step.
Merging the PR was a single PUT with {"merge_method":"merge"}. The response was three fields: sha, merged: true, message. Perfect.
Deleting the branch -- DELETE /repos/{owner}/{repo}/git/refs/heads/feature/audit-test returned 204 No Content. No fuss.
Error responses were consistently structured (message, documentation_url, status, sometimes errors[]). When I created a duplicate branch, "Reference already exists" told me exactly what happened. When I used a bad token, "Bad credentials" was unambiguous.
Where I worked around things
1. Creating a branch required a two-step dance: first GET /repos/{owner}/{repo}/git/ref/heads/main to retrieve the SHA, then POST /repos/{owner}/{repo}/git/refs with the full refs/heads/ prefix. This is the Git plumbing leaking through the API. A higher-level POST /repos/{owner}/{repo}/branches endpoint that accepts {"name": "feature/audit-test", "from": "main"} would be far more natural.
2. Adding a file to a branch via PUT /repos/{owner}/{repo}/contents/{path} required base64-encoding the content manually. That CONTENT=$(echo -n "..." | base64) step is friction -- every caller has to do it, and it's easy to get wrong (trailing newlines from echo without -n, for instance). The REST API has no way to create a commit touching multiple files in one call either -- for that, you'd need the lower-level Git Trees/Commits/Refs trio, which is even more complex.
3. Token permission discovery: There is no API endpoint that says "here are the permissions this fine-grained PAT was granted." I inferred permissions indirectly by reading the permissions object on the repo response. For classic tokens, x-oauth-scopes appears in response headers, but for fine-grained PATs that header is empty. I had to just try things and see if they worked. A GET /user/token-permissions or similar introspection endpoint would eliminate this guesswork.
4. Testing token scope boundaries: When I tried accessing torvalds/linux to test scope limits, it returned 200 OK with full repo metadata -- because it's a public repo and fine-grained PATs still allow public reads. I had to pivot to testing a write against the out-of-scope repo (which correctly returned 403) and a read against a nonexistent private repo (which returned 404). The behavior is correct but surprised me -- "scoped to one repo" doesn't mean "can only see one repo."
5. PR review comment: GitHub has three different comment mechanisms on PRs: issue comments (POST /issues/{number}/comments), review comments on specific lines (POST /pulls/{number}/comments), and PR reviews (POST /pulls/{number}/reviews). The naming overlap between these is confusing. I used the reviews endpoint with "event": "COMMENT" which was the right call, but I had to reason about which of the three was contextually correct rather than having an obvious single path.
Interface comparison
I used the REST API exclusively (via curl). GraphQL would have been better for the verification step — a single query could have retrieved branches, issues, files, and commits instead of four separate calls. The gh CLI would have been more concise for scripting (gh issue create, gh pr create, gh pr merge), but the TLS certificate verification was failing in the environment. I didn't use GraphQL because every mutation mapped 1:1 to a REST endpoint, and the workflow was write-heavy.
What would have made this easier
1. A higher-level branch creation endpoint — no SHA lookup, no refs/heads/ prefix. 2. Multi-file commit support in the Contents API. 3. Token introspection endpoint for fine-grained PATs. 4. Clearer PR comment taxonomy in endpoint naming and docs.
Overall: GitHub's REST API is mature, consistent, and well-structured for automation. The main friction points are around Git-level operations (branches, multi-file commits) where the API exposes plumbing rather than porcelain. For the specific workflow tested -- issues, branches, files, PRs, reviews, merges -- everything worked on the first attempt with no retries, which is the most important measure of developer experience.