REST API compatibility
The GitHub-compatible REST API at /api/v3: auth, pagination, the endpoint matrix and differences from GitHub.
PushStack serves a GitHub-compatible REST API, so scripts, Octokit and
gh-style tools can work against it by changing the base URL. It mirrors
GitHub Enterprise's layout.
- Base URL:
https://git.nandan.fyi/api/v3 - Code:
apps/web/src/routes/api/v3.$.ts(the single route) →apps/web/src/server/api/index.ts(dispatcher and route table) → one module per resource inapps/web/src/server/api/. Handlers call the same plain server modules the server functions use (repository-lookup.ts,repo-access.ts,git-read.ts,repository-create.ts), never thecreateServerFns.
To add an endpoint: write a handler (ctx: ApiContext) => Promise<Response> in
the resource's module, add a row to ROUTES in index.ts, add a behavior test
in apps/web/src/server/__tests__/api.test.ts, and add a row to the matrix below.
Authentication
Personal access tokens (pst_…, created in Settings) in the Authorization
header, exactly as on GitHub:
Authorization: token pst_…
Authorization: Bearer pst_…- Anonymous requests may read public repositories.
- A malformed, unknown or expired token is
401 Bad credentialson every route, even where an anonymous request would succeed. - A private repository the caller can't read answers
404 Not Found, never 403, so it doesn't reveal that it exists. - A token missing a needed scope gets
403 Resource not accessible by personal access token(writes needrepo:write). - Session cookies and
Basiccredentials are not accepted here.
Responses and errors
JSON in snake_case, timestamps ISO-8601 UTC (2026-10-19T12:00:00Z). Every
response carries Content-Type: application/json; charset=utf-8 and
X-GitHub-Media-Type: github.v3; format=json.
Every response (errors included) carries X-GitHub-Request-Id, the same id
that tags the request in server logs and Sentry.
Errors follow GitHub's body:
{ "message": "Not Found", "documentation_url": "https://docs.github.com/rest", "status": "404" }Validation failures are 422 with errors: [{ resource, field, code }]
(missing_field, invalid, custom with a message). A request body that
isn't a JSON object is 400 Problems parsing JSON; a body over 1 MiB is 413.
Pagination
List endpoints take per_page (default 30, max 100) and page (from 1);
invalid values fall back to the defaults. The Link header carries
rel="prev", "next", "last" and "first" (other query parameters are
kept) and is absent when everything fits on one page. Items deeper than
10,000 are unreachable: such a page is an empty list.
Compatibility matrix
| Endpoint | Status | Notes |
|---|---|---|
GET /user | supported | |
GET /users/{username} | supported | |
GET /user/repos | supported | visibility, affiliation (owner, collaborator), type, sort, direction |
POST /user/repos | supported | name, description, private, auto_init, gitignore_template, license_template (templates apply only with auto_init, an unknown template is 422); other fields ignored |
GET /users/{username}/repos | supported | public repositories only; sort, direction |
GET /repos/{owner}/{repo} | supported | |
GET /repos/{owner}/{repo}/branches | supported | sorted by name |
GET /repos/{owner}/{repo}/branches/{branch} | supported | branch names may contain / |
GET /repos/{owner}/{repo}/tags | supported | version order |
| everything else | not implemented | 404 Not Found |
Differences from GitHub
- User
idis a string, not an integer (PushStack's user ids are text). Repositoryidis an integer.node_idis omitted. avatar_urlisnullwhen the user has no image (GitHub always has one).emailon a user is alwaysnull, and theemailof a commit author or committer is always the placeholdernoreply@<host>: web commits carry the account's sign-in email, which is never exposed. Names and dates are real.pushed_atis omitted andsort=pushedsorts byupdated_at.- Repository objects carry the fields PushStack has an equivalent for:
id, name, full_name, private, owner, html_url, description, fork, url, clone_url, created_at, updated_at, default_branch, visibility, archived, disabled, stargazers_count, watchers_count, forks_countand (when authenticated, on repo and/user/repos)permissions.forkis alwaysfalse,forks_count0. Everything else (topics,language,size,license,ssh_url, …) is omitted. GET /users/{username}/reposignorestype; there are no organizations, soorganization_memberaffiliation matches nothing and every owner is a user.- Commit
author/committer(account links) in a branch response arenull; commits aren't linked to accounts by email.commit.url/html_urlpoint at endpoints/pages that aren't implemented yet. - Branch
protectedis alwaysfalse; there is no branch protection. - Tags have no
zipball_url/tarball_url. descriptionis limited to 500 characters (GitHub: 350 on creation, so anything valid there is valid here); repository names follow PushStack's rule (letters, digits,.,-,_) and an invalid name is a422rather than being rewritten to hyphens.- Repository lookups are cached per serverless instance for a few seconds, so
right after
POST /user/reposanother instance may briefly answer404for the new repository. - No
X-RateLimit-*headers, no ETag/conditional requests, no CORS headers.