.gitbay/wiki/API.org

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

221 lines · 9207 bytes

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