krz/gitbay

A CLI-first git forge.

clone: git clone https://gitbay.org/krz/gitbay.git

repo-descriptions: docs/api.org · raw

  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* Webhooks
 62
 63Per-repository outbound POSTs for repository events. Managed by repo
 64admins:
 65
 66#+begin_src sh
 67gitbay webhook add <url> --secret s3cret [--events push,issue.created]  # default *
 68gitbay webhook list
 69gitbay webhook deliveries [--limit 50]     # status, attempts, last error
 70gitbay webhook redeliver <delivery-id>     # requeue, including dead letters
 71gitbay webhook remove <id>
 72#+end_src
 73
 74** Events
 75
 76=push= (data: ref, old, new, forced, deleted), =issue.created=,
 77=issue.commented=, =issue.closed=, =issue.open=, =mr.created=,
 78=mr.merged= (data: number, sha), =repo.imported= (data: from).
 79
 80** Delivery
 81
 82Each event POSTs one JSON body:
 83
 84#+begin_src json
 85{"event": "push",
 86 "repo": "you/project",
 87 "actor": "alice",
 88 "created_at": "2026-08-24T01:00:00.000Z",
 89 "data": {"ref": "refs/heads/main", "old": "...", "new": "...",
 90          "forced": false, "deleted": false}}
 91#+end_src
 92
 93Headers: =X-Gitbay-Event=, =X-Gitbay-Delivery= (id), and — when the hook
 94has a secret — =X-Gitbay-Signature-256: sha256=<hex hmac>= over the raw
 95body. Verify before trusting:
 96
 97#+begin_src python
 98import hmac, hashlib
 99expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
100ok = hmac.compare_digest(expected, request.headers["X-Gitbay-Signature-256"])
101#+end_src
102
103A 2xx within 10 seconds is success. Anything else retries with
104exponential backoff (30s base, doubling) and dead-letters after five
105attempts; =webhook deliveries= shows the trail and =redeliver= revives a
106dead letter. Redirects are never followed, and targets resolving to
107loopback/private/link-local addresses are refused both at registration
108and again at connect time, unless the instance sets
109=[webhooks] allow_local=.