Overview
Polar is a self-hosted Git forge at {{.SiteURL}}. It serves standard Git over HTTPS (smart HTTP) plus a web UI for issues, pull requests, releases and repository settings. Everything the UI does is a plain HTML form POST — there is no separate REST API for repositories, so agents drive the same endpoints.
Authentication: device flow
Git over HTTPS uses the OAuth 2.0 Device Authorization Grant (RFC 8628): the client asks for a short user code, the human approves it in the browser once, and the client receives a personal access token.
- Request a device code from
POST /api/v1/device/codewith aclient_id. - Send the user to
verification_uri_complete(they must be signed in to Polar). - Poll
POST /api/v1/device/tokenwithdevice_codeeveryintervalseconds (default 5). While the grant is pending the endpoint answers429 {"error":"authorization_pending"}. - On approval you receive
{"access_token":"...","token_type":"bearer","username":"...","scope":"git"}.
# 1. Start a device grant (no browser session required for this step)
curl -sX POST {{.SiteURL}}/api/v1/device/code -d 'client_id=my-agent'
# {"device_code":"...","user_code":"WDJB-MJHT","verification_uri":"{{.SiteURL}}/login/device",
# "verification_uri_complete":"{{.SiteURL}}/login/device?user_code=WDJB-MJHT","expires_in":600,"interval":5}
# 2. Ask the user to open verification_uri_complete and approve.
# 3. Exchange the approved device code for a token
curl -sX POST {{.SiteURL}}/api/v1/device/token -d 'device_code=DEVICE_CODE'
# {"access_token":"...","token_type":"bearer","username":"rin","scope":"git"}
Use the token as the password for HTTPS Git with any username:
git clone https://USERNAME:TOKEN@HOST/OWNER/REPO.git
This is the same handoff a fresh git push triggers: Polar opens the approval page and the credential helper waits for it. For scripts, scripts/git-credential-polar.sh automates the flow and caches tokens in ~/.polar/credentials.json:
git config --global credential.helper '!sh /path/to/polar/scripts/git-credential-polar.sh'
Tokens are listed and revocable under Settings → Access tokens. Device codes are single-use and expire after expires_in seconds.
Git operations
- Clone / fetch / push:
https://HOST/OWNER/REPO.git— standard smart HTTP. - Branches and tags: push them normally; Polar stores refs and shows them in the UI.
- Public repositories: readable anonymously when anonymous read is enabled on the instance.
- Private repositories: every Git request needs credentials — unauthenticated requests get
401, while the web UI answers404so it does not leak existence. - Empty repositories: the first
git push -u origin maincreates the default branch.
Driving the web UI from a script
The UI is server-rendered HTML with plain forms, which makes it fully scriptable:
- Sign in by POSTing
/login(username,password) and keeping thepolar_sessioncookie. - Every non-GET request needs the session CSRF token: read the hidden
_csrfinput from any page and send it as the_csrfform field or theX-CSRF-Tokenheader. - File uploads are
multipart/form-data.
Note: Personal access tokens (including those issued by the device flow) only authenticate Git over HTTPS. Issues, pull requests, releases and other UI actions require a browser session from password login — a polar_... token cannot replace the session cookie.
# Close an issue (state: open|closed)
curl -b cookies.txt -X POST https://HOST/OWNER/REPO/issues/1/state \
-H "X-CSRF-Token: $CSRF" -d 'state=closed'
# Comment on an issue
curl -b cookies.txt -X POST https://HOST/OWNER/REPO/issues/1/comments \
-H "X-CSRF-Token: $CSRF" -d 'body=Fixed in the latest build.'
# Open a pull request (head_branch must already be pushed)
curl -b cookies.txt -X POST https://HOST/OWNER/REPO/pulls/new \
-H "X-CSRF-Token: $CSRF" \
-d 'title=Rework uploads' -d 'body=Release notes...' \
-d 'base_branch=main' -d 'head_branch=feature/uploads'
# Review and merge a pull request
curl -b cookies.txt -X POST https://HOST/OWNER/REPO/pulls/4/review \
-H "X-CSRF-Token: $CSRF" -d 'state=approved' -d 'body=LGTM'
curl -b cookies.txt -X POST https://HOST/OWNER/REPO/pulls/4/merge \
-H "X-CSRF-Token: $CSRF"
Other form endpoints: POST /OWNER/REPO/issues/new (title, body), POST /OWNER/REPO/upload (files, ref, path, message), POST /OWNER/REPO/settings/collaborators/add, POST /OWNER/REPO/fork.
Releases and artifacts
Releases group a git tag, notes and uploaded files. Publishing one is a single multipart POST:
curl -b cookies.txt -X POST https://HOST/OWNER/REPO/releases/new \
-H "X-CSRF-Token: $CSRF" \
-F 'tag_name=v1.2.0' -F 'target_ref=main' \
-F 'title=v1.2.0' -F 'body=Release notes...' -F 'is_prerelease=true' \
-F 'assets=@dist/app-linux-amd64' -F 'assets=@dist/app-windows-amd64.exe'
- The tag is created automatically if missing, pointing at
target_ref. - Up to 20 assets per release, 100 MB per file. Filenames are sanitized; assets can be deleted individually from the release card.
- Asset downloads support HTTP
Range:GET /OWNER/REPO/releases/<id>/assets/<assetID>/download. - Tag archives are always available:
GET /OWNER/REPO/archive/v1.2.0.zipor.tar.gz. - Raw file contents:
GET /OWNER/REPO/raw/<ref>/<path>.
Working agreements
- Prefer branches and pull requests over pushing straight to the default branch in shared repositories.
- Match the repository's commit message style; explain the change, not the process.
- Use tags for release points and attach artifacts with stable names (for example
polar-linux-amd64). - Treat tokens as secrets: never commit them, and revoke what you no longer need.
- Poll the device token endpoint no faster than the advertised
interval; back off on429.
Static documentation. Endpoint names match this instance; if a field name ever changes, read it from the corresponding HTML form.