Begin claiming a GitHub organization's slug by proof
POST/api/orgs/claims
beginOrganizationClaim
The first half of the org claim (`plans/github-app-login-and-org-claim-2026-09.md`, Track C1). `slug` must be a GitHub **organisation**'s login: `GET /users/{slug}` is asked, and a handle that is a GitHub user (`400`) or is not on GitHub at all (`400` — create the org directly, nothing reserves it) is refused, as is a slug that is already a workspace here (`409 already_exists`). Every begin then runs the live admin proof — the same `GET /user/memberships/orgs/{org}` read the completion runs, under the caller's own GitHub App user token — before anything else is asked of GitHub. The answer is tagged on `next`. **`install`**: a single-use, 30-minute consent nonce is minted that remembers the slug, and `install_url` carries it to GitHub's App install page pinned to that organisation (`installations/new/permissions?target_id=…`). Open it in a browser — do not fetch it. **`complete`**: the caller has just proved they administer the organisation, and the App is *already installed* on it (from github.com, or after an earlier consent expired) — which this route reads, under the App's own credential, only behind a passing proof. GitHub's page for an installed App is its Configure page, which never sends the browser back, so no nonce is minted and nothing is installed: complete the claim with `POST /api/orgs/claims/{installation_id}` straight away. Anyone the proof does not admit — a member, a stranger, an admin under an App the organisation has not granted *Members: read* — gets `install` for an installed organisation exactly as for one without the App, with no installation read made; and a proof that fails for a reason that is no verdict (GitHub rejecting the caller's token, `401 unauthenticated`; rate-limiting it, `502 upstream`) fails the same way whether or not the App is installed. So this route never says which organisations installed the App, and what it spends on GitHub is fixed by the caller, not by the organisation: one membership read for everyone, the App-JWT installation read only for a proven admin — all bounded by its per-account rate limit. Two refusals the callback would make are made here instead, before the browser leaves — and before GitHub is asked anything, since both are facts about the caller's own account: `github_identity_required` (403, this account has no linked GitHub identity) and `github_app_login_required` (403, the GitHub link is a classic OAuth-App login, which cannot prove an organisation — reconnect GitHub). Installing the App there brings the browser back through the App installation callback, which proves the caller administers the organisation and sends them to `/new/organization/claim?installation_id=…`. **Nothing is created by this route or by the callback**; the workspace is created by `POST /api/orgs/claims/{installation_id}`, after the proof is run again. **Browser sessions only.** A PAT or workspace token is refused at the gate: the flow ends in a workspace the caller owns and a durable App installation tied to their account, neither of which an attenuated token may mint. 404 when this deployment has no GitHub App configured.
Requires authentication using a session cookie — see tokens and scopes.
curl
curl -X POST 'https://neosource.dev/api/orgs/claims' \
-b 'ns_session=$NEOSOURCE_SESSION' \
-H 'Content-Type: application/json' \
-d '{"slug":"string"}'fetch
fetch("https://neosource.dev/api/orgs/claims", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify({"slug":"string"}),
});Request bodyrequired
application/json
Responses
200Where to send the browser
application/json
Standard errors
Bodies documented once for the whole API — see standard errors.
- 400Bad Request — one of: invalid_input
- 401Authentication required
- 403Forbidden — one of: forbidden, github_app_login_required, github_app_permission_missing, github_identity_required, not_an_org_admin, org_installations_unsupported
- 404Not Found — one of: not_found
- 409Conflict — one of: already_exists, conflict, not_an_org_installation
- 429Rate limited — retry after the `Retry-After` header
- 500Internal server error
- 502Bad Gateway — one of: upstream
- 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
- 504Gateway timeout — the request exceeded the server's handling budget