.gitbay/wiki/API.org

185f434bf23123c61a922129270c2db4cfebd493
gitbay/.gitbay/wiki/API.org rendered · source · history · blame · raw

190 lines · 7292 bytes

  1#+title: gitbay API and webhooks
  2
  3* The JSON API
  4
  5One endpoint fronts the entire command registry — everything the SSH
  6control plane can do, current and future, with identical semantics.
  7Disabled by default; the instance must set:
  8
  9#+begin_src toml
 10[api]
 11enabled = true
 12#+end_src
 13
 14** Tokens
 15
 16Tokens are minted over SSH and only over SSH — an API token can never
 17create further credentials.
 18
 19#+begin_src sh
 20gitbay auth token create --name ci [--scope full|read] [--ttl 30d]
 21gitbay auth token list
 22gitbay auth token revoke ci
 23#+end_src
 24
 25The token (prefix =gb_=, shown exactly once) is presented as
 26=Authorization: Bearer gb_...=. Only a hash is stored server-side.
 27Scope =read= permits list/show/log/diff-style commands and refuses
 28anything that modifies state. =--ttl= takes Go durations or a day
 29suffix (=30d=); expired, revoked, and unknown tokens all answer the
 30same 401.
 31
 32** POST /api/v1/cmd
 33
 34Request body:
 35#+begin_src json
 36{"argv": ["issue", "create", "you/project", "--title", "from CI"],
 37 "stdin": "optional body for --file - style input"}
 38#+end_src
 39
 40=argv= is real argv — no shell, no quoting rules. The response is the
 41command's own JSON envelope with =exit_code= added, plus =stderr= when
 42the command wrote diagnostics:
 43
 44#+begin_src json
 45{"protocol_version": 1, "exit_code": 0, "data": {"number": 7}}
 46#+end_src
 47
 48HTTP status maps the exit code: 0→200, 2→400, 3→404, 4→403, else 500.
 49Commands that emit raw text rather than an envelope (=help=, =mr diff=)
 50come wrapped as ={"output": "..."}=. Git transport commands and the
 51token commands are refused by name.
 52
 53#+begin_src sh
 54curl -s -H "Authorization: Bearer $TOKEN" \
 55     -d '{"argv":["whoami"]}' https://gitbay.org/api/v1/cmd
 56
 57printf '{"argv":["issue","create","you/project","--title","t","--file","-"],"stdin":"body\n"}' |
 58curl -s -H "Authorization: Bearer $TOKEN" -d @- https://gitbay.org/api/v1/cmd
 59#+end_src
 60
 61** GET /api/v1/read
 62
 63The conditional half: the same registry over GET, admitting only
 64commands the registry marks read-only, so a GET structurally cannot
 65mutate. Responses carry an =ETag=; =If-None-Match= answers 304.
 66
 67#+begin_src sh
 68curl -s -H "Authorization: Bearer $TOKEN" \
 69     "https://gitbay.org/api/v1/read?argv=repo&argv=show&argv=you/project"
 70#+end_src
 71
 72** The dashboard read
 73
 74=dashboard= returns the account aggregate — pinned repositories, open
 75merge requests, assigned issues, recent builds — in one call:
 76
 77#+begin_src sh
 78curl -s -H "Authorization: Bearer $TOKEN" \
 79     "https://gitbay.org/api/v1/read?argv=dashboard"
 80#+end_src
 81
 82For an instance admin the response also carries ={"server": {"commit":
 83"<sha>"}}=, the build the daemon is running, so the deployed commit is
 84readable without the journal. The key is absent for everyone else: the
 85exact build a host runs narrows down which known issues apply to it.
 86
 87** Cursor pagination
 88
 89=issue list=, =mr list=, =repo list=, and =feed= accept =--limit <n>=
 90(1–200) and =--cursor <c>=. With either flag present the =data=
 91envelope becomes ={"items": [...], "next": "..."}=; =next= is an
 92opaque cursor for the following page, absent on the last one. Without
 93the flags the response stays the complete bare array; a bare =feed=
 94returns the newest 50 events.
 95
 96* Commit statuses (CI reporting)
 97
 98CI reports results through the same command surface (over SSH or the
 99JSON API with a full-scope token; reporting requires write access):
100
101#+begin_src sh
102gitbay status set <owner/name> <sha> --context build --state pending
103gitbay status set <owner/name> <sha> --context build --state success --url https://ci.example/run/1
104gitbay status list <owner/name> <sha> --json    # {"combined": "...", "statuses": [...]}
105#+end_src
106
107One row per (commit, context): re-reporting updates in place. States:
108=pending=, =success=, =failure=, =error=; the combined state is the
109worst of them. Statuses appear on commit pages, MR pages, and
110=mr show=, each with =updated_at=; a =ci/<job>= status also carries
111=duration=, read from the build behind it, once that build has
112finished. With =repo settings require-checks <repo> on=, every status
113on the MR head must be green, and a head something was going to report
114on must carry some: a =.gitbay/ci.yml= with a job a push runs, or a
115repository that has recorded a status before, which is what reporting
116from outside through =status set= looks like. A repository where
117nothing has ever reported merges. Each
118report also emits a =status= event to webhooks.
119
120* Webhooks
121
122Per-repository outbound POSTs for repository events. Managed by repo
123admins:
124
125#+begin_src sh
126gitbay webhook add <url> --secret s3cret [--events push,issue.created]  # default *
127gitbay webhook list
128gitbay webhook deliveries [--limit 50]     # status, attempts, last error
129gitbay webhook redeliver <delivery-id>     # requeue, including dead letters
130gitbay webhook remove <id>
131#+end_src
132
133** Events
134
135Every event this forge records, and so every name =--events= may take.
136The server holds the same list as =control.EventKinds=, and a test
137fails if the code emits something not listed or lists something it never
138emits — so this is the whole set, not a sample. =webhook add= refuses a
139name that is not one of them, since a subscription to a typo would
140silently never fire.
141
142- repository: =push= (ref, old, new, forced, deleted), =repo.archived=,
143  =repo.unarchived=, =repo.imported= (from)
144- issues: =issue.created=, =issue.edited=, =issue.commented=,
145  =issue.closed=, =issue.open=, =issue.labeled= (labels),
146  =issue.assigned= (assignees), =issue.milestoned= (milestone)
147- merge requests: =mr.created=, =mr.edited=, =mr.commented=,
148  =mr.reviewed= (verdict), =mr.draft= (draft), =mr.retargeted= (from,
149  to), =mr.milestoned= (milestone), =mr.merged= (number, sha),
150  =mr.closed=
151- releases: =release.created= (tag), =release.deleted= (tag)
152- CI: =status=, =build.success=, =build.failure=, =build.cancelled=
153
154Every payload carries =number= where it names an issue or merge request.
155
156There is deliberately no =repo.deleted=. Both =events.repo_id= and
157=webhooks.repo_id= cascade from =repos=, so recording one would delete
158it — and every webhook that could have received it — in the same
159statement. A repository's deletion is visible in the audit log.
160
161** Delivery
162
163Each event POSTs one JSON body:
164
165#+begin_src json
166{"event": "push",
167 "repo": "you/project",
168 "actor": "alice",
169 "created_at": "2026-08-24T01:00:00.000Z",
170 "data": {"ref": "refs/heads/main", "old": "...", "new": "...",
171          "forced": false, "deleted": false}}
172#+end_src
173
174Headers: =X-Gitbay-Event=, =X-Gitbay-Delivery= (id), and — when the hook
175has a secret — =X-Gitbay-Signature-256: sha256=<hex hmac>= over the raw
176body. Verify before trusting:
177
178#+begin_src python
179import hmac, hashlib
180expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
181ok = hmac.compare_digest(expected, request.headers["X-Gitbay-Signature-256"])
182#+end_src
183
184A 2xx within 10 seconds is success. Anything else retries with
185exponential backoff (30s base, doubling) and dead-letters after five
186attempts; =webhook deliveries= shows the trail and =redeliver= revives a
187dead letter. Redirects are never followed, and targets resolving to
188loopback/private/link-local addresses are refused both at registration
189and again at connect time, unless the instance sets
190=[webhooks] allow_local=.