forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
4.3 KiB
1# forge JSON API
2
3A small JSON API under `/api/v1`. It is forge's own; it does not copy another forge's API.
4The examples use the base URL from `config/forge.toml`, `http://127.0.0.1:7660`.
5
6## Authentication
7
8Requests authenticate with a personal access token. Create one at **Settings → Tokens**
9(`/settings/tokens`). Send it either way:
10
11```
12Authorization: Bearer forge_...
13Authorization: token forge_...
14```
15
16- A token needs the `repo` scope. It is the only scope the token form offers. A token without it
17 gets `403`.
18- Session cookies are never read by this API. A browser that is logged in cannot call these
19 routes by itself, so CSRF does not apply. Without a token, a write gets `401`.
20- Anonymous reads of public repos need no token. Anonymous calls that cannot read a repo get
21 `401`, as git does, so a private repo looks the same as a missing one to an anonymous caller.
22 A signed-in user with no access to a repo gets `404`.
23- Failed authentication counts against the client IP. It shares the budget git authentication
24 uses, so after too many failures the API answers `429` with `Retry-After`.
25
26Errors are JSON: `{"error": "message"}`.
27
28| Status | Meaning |
29| --- | --- |
30| 400 | The body is not valid JSON. |
31| 401 | No token, an unknown or expired token, or an anonymous caller who cannot use the repo. |
32| 403 | The token lacks the `repo` scope, or the user has read-only access and tried to write. |
33| 404 | The repo or the commit does not exist, or the signed-in user cannot see the repo. |
34| 413 | The request body is larger than 16 KiB. |
35| 422 | A field has an invalid value, or `sha` is not a full commit id. |
36| 429 | Too many failed authentication attempts from this IP. |
37
38## Commit statuses
39
40A status reports the result of one check (for example a CI job) on one commit. Each commit can
41have one status per **context**. Reporting again for the same context replaces the earlier
42report, so the latest report wins.
43
44A status has these fields:
45
46| Field | Notes |
47| --- | --- |
48| `state` | `pending`, `success`, `failure` or `error`. |
49| `context` | Name of the check, 1 to 255 characters. Defaults to `default`. |
50| `description` | Optional, at most 1024 characters. |
51| `target_url` | Optional, `http` or `https` only, at most 2048 bytes. Shown as a link. |
52
53The combined state of a commit is worked out from every context:
54
55- any `failure` or `error` gives `failure`;
56- otherwise any `pending` gives `pending`;
57- otherwise `success`;
58- a commit with no statuses has `state: null`.
59
60### Report a status
61
62`POST /api/v1/repos/{owner}/{repo}/statuses/{sha}` needs a token with write access to the repo.
63`sha` is the full 40-character (or 64-character, SHA-256) commit id. Upper-case hex is accepted.
64
65```sh
66curl -X POST http://127.0.0.1:7660/api/v1/repos/alice/demo/statuses/$SHA \
67 -H "Authorization: Bearer $FORGE_TOKEN" \
68 -H "Content-Type: application/json" \
69 -d '{"state": "success", "context": "ci/build", "description": "Passed in 42s",
70 "target_url": "https://ci.example.com/runs/7"}'
71```
72
73Response, `201 Created`:
74
75```json
76{
77 "id": "01J...",
78 "sha": "0123...",
79 "context": "ci/build",
80 "state": "success",
81 "description": "Passed in 42s",
82 "target_url": "https://ci.example.com/runs/7",
83 "created_at": "2026-10-15T10:00:00Z",
84 "updated_at": "2026-10-15T10:00:00Z"
85}
86```
87
88Only `state` is required. A commit that is not in the repo gets `404`.
89
90### Read the statuses of a commit
91
92`GET /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses` needs no token for a public repo. A
93token is needed for a private one.
94
95```sh
96curl http://127.0.0.1:7660/api/v1/repos/alice/demo/commits/$SHA/statuses \
97 -H "Authorization: Bearer $FORGE_TOKEN"
98```
99
100Response, `200 OK`:
101
102```json
103{
104 "state": "failure",
105 "statuses": [
106 {
107 "id": "01J...",
108 "sha": "0123...",
109 "context": "ci/build",
110 "state": "failure",
111 "description": "2 tests failed",
112 "target_url": "https://ci.example.com/runs/8",
113 "created_at": "2026-10-15T10:05:00Z",
114 "updated_at": "2026-10-15T10:05:00Z"
115 },
116 {
117 "id": "01J...",
118 "sha": "0123...",
119 "context": "lint",
120 "state": "success",
121 "description": null,
122 "target_url": null,
123 "created_at": "2026-10-15T10:01:00Z",
124 "updated_at": "2026-10-15T10:01:00Z"
125 }
126 ]
127}
128```
129
130`statuses` is ordered by `context`. The list is empty and `state` is `null` when nothing has
131reported on the commit.