docs/api.org
127 lines · 4423 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* Commit statuses (CI reporting)
62
63CI reports results through the same command surface (over SSH or the
64JSON API with a full-scope token; reporting requires write access):
65
66#+begin_src sh
67gitbay status set <owner/name> <sha> --context build --state pending
68gitbay status set <owner/name> <sha> --context build --state success --url https://ci.example/run/1
69gitbay status list <owner/name> <sha> --json # {"combined": "...", "statuses": [...]}
70#+end_src
71
72One row per (commit, context): re-reporting updates in place. States:
73=pending=, =success=, =failure=, =error=; the combined state is the
74worst of them. Statuses appear on commit pages, MR pages, and
75=mr show=. With =repo settings require-checks <repo> on=, merging
76requires the MR head to carry statuses and all of them green. Each
77report also emits a =status= event to webhooks.
78
79* Webhooks
80
81Per-repository outbound POSTs for repository events. Managed by repo
82admins:
83
84#+begin_src sh
85gitbay webhook add <url> --secret s3cret [--events push,issue.created] # default *
86gitbay webhook list
87gitbay webhook deliveries [--limit 50] # status, attempts, last error
88gitbay webhook redeliver <delivery-id> # requeue, including dead letters
89gitbay webhook remove <id>
90#+end_src
91
92** Events
93
94=push= (data: ref, old, new, forced, deleted), =issue.created=,
95=issue.commented=, =issue.closed=, =issue.open=, =mr.created=,
96=mr.merged= (data: number, sha), =repo.imported= (data: from).
97
98** Delivery
99
100Each event POSTs one JSON body:
101
102#+begin_src json
103{"event": "push",
104 "repo": "you/project",
105 "actor": "alice",
106 "created_at": "2026-08-24T01:00:00.000Z",
107 "data": {"ref": "refs/heads/main", "old": "...", "new": "...",
108 "forced": false, "deleted": false}}
109#+end_src
110
111Headers: =X-Gitbay-Event=, =X-Gitbay-Delivery= (id), and — when the hook
112has a secret — =X-Gitbay-Signature-256: sha256=<hex hmac>= over the raw
113body. Verify before trusting:
114
115#+begin_src python
116import hmac, hashlib
117expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
118ok = hmac.compare_digest(expected, request.headers["X-Gitbay-Signature-256"])
119#+end_src
120
121A 2xx within 10 seconds is success. Anything else retries with
122exponential backoff (30s base, doubling) and dead-letters after five
123attempts; =webhook deliveries= shows the trail and =redeliver= revives a
124dead letter. Redirects are never followed, and targets resolving to
125loopback/private/link-local addresses are refused both at registration
126and again at connect time, unless the instance sets
127=[webhooks] allow_local=.