Tahinli/forge
publicforge JSON API
A small JSON API under /api/v1. It is forge's own; it does not copy another forge's API.
The examples use the base URL from config/forge.toml, http://127.0.0.1:7660.
Authentication
Requests authenticate with a personal access token. Create one at Settings → Tokens
(/settings/tokens). Send it either way:
Authorization: Bearer forge_...
Authorization: token forge_...
- A token needs the
reposcope. It is the only scope the token form offers. A token without it gets403. - Session cookies are never read by this API. A browser that is logged in cannot call these
routes by itself, so CSRF does not apply. Without a token, a write gets
401. - Anonymous reads of public repos need no token. Anonymous calls that cannot read a repo get
401, as git does, so a private repo looks the same as a missing one to an anonymous caller. A signed-in user with no access to a repo gets404. - Failed authentication counts against the client IP. It shares the budget git authentication
uses, so after too many failures the API answers
429withRetry-After.
Errors are JSON: {"error": "message"}.
| Status | Meaning |
|---|---|
| 400 | The body is not valid JSON. |
| 401 | No token, an unknown or expired token, or an anonymous caller who cannot use the repo. |
| 403 | The token lacks the repo scope, or the user has read-only access and tried to write. |
| 404 | The repo or the commit does not exist, or the signed-in user cannot see the repo. |
| 413 | The request body is larger than 16 KiB. |
| 422 | A field has an invalid value, or sha is not a full commit id. |
| 429 | Too many failed authentication attempts from this IP. |
Commit statuses
A status reports the result of one check (for example a CI job) on one commit. Each commit can have one status per context. Reporting again for the same context replaces the earlier report, so the latest report wins.
A status has these fields:
| Field | Notes |
|---|---|
state |
pending, success, failure or error. |
context |
Name of the check, 1 to 255 characters. Defaults to default. |
description |
Optional, at most 1024 characters. |
target_url |
Optional, http or https only, at most 2048 bytes. Shown as a link. |
The combined state of a commit is worked out from every context:
- any
failureorerrorgivesfailure; - otherwise any
pendinggivespending; - otherwise
success; - a commit with no statuses has
state: null.
Report a status
POST /api/v1/repos/{owner}/{repo}/statuses/{sha} needs a token with write access to the repo.
sha is the full 40-character (or 64-character, SHA-256) commit id. Upper-case hex is accepted.
curl -X POST http://127.0.0.1:7660/api/v1/repos/alice/demo/statuses/$SHA \
-H "Authorization: Bearer $FORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"state": "success", "context": "ci/build", "description": "Passed in 42s",
"target_url": "https://ci.example.com/runs/7"}'
Response, 201 Created:
{
"id": "01J...",
"sha": "0123...",
"context": "ci/build",
"state": "success",
"description": "Passed in 42s",
"target_url": "https://ci.example.com/runs/7",
"created_at": "2026-10-15T10:00:00Z",
"updated_at": "2026-10-15T10:00:00Z"
}
Only state is required. A commit that is not in the repo gets 404.
Read the statuses of a commit
GET /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses needs no token for a public repo. A
token is needed for a private one.
curl http://127.0.0.1:7660/api/v1/repos/alice/demo/commits/$SHA/statuses \
-H "Authorization: Bearer $FORGE_TOKEN"
Response, 200 OK:
{
"state": "failure",
"statuses": [
{
"id": "01J...",
"sha": "0123...",
"context": "ci/build",
"state": "failure",
"description": "2 tests failed",
"target_url": "https://ci.example.com/runs/8",
"created_at": "2026-10-15T10:05:00Z",
"updated_at": "2026-10-15T10:05:00Z"
},
{
"id": "01J...",
"sha": "0123...",
"context": "lint",
"state": "success",
"description": null,
"target_url": null,
"created_at": "2026-10-15T10:01:00Z",
"updated_at": "2026-10-15T10:01:00Z"
}
]
}
statuses is ordered by context. The list is empty and state is null when nothing has
reported on the commit.