REST API Reference
The Isurus REST API is available at /api/v1/. All responses return JSON.
Authentication
Authenticate API requests using a Bearer token in the Authorization header:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-isurus-instance/api/v1/user
Tokens can be created at three levels:
- Personal tokens — Settings > Security > API Tokens — access all your repositories
- Org tokens — Org Settings > Integration > Tokens — access all repos in that org
- Repo tokens — Repo Settings > Integration > Tokens — access only that repo
Token Scopes
| Scope | Access |
|---|---|
repo:read |
Clone/read repository content |
repo:write |
Push to repositories, update settings |
issues:read |
Read issues and comments |
issues:write |
Create/update issues, add comments |
pulls:read |
Read pull requests |
pulls:write |
Create/update/merge pull requests |
releases:read |
Read releases and assets |
releases:write |
Create/update releases, upload assets |
ci:read |
View pipeline status and logs |
ci:write |
Trigger, cancel, and rerun pipelines |
admin |
Full access — settings, webhooks, secrets, delete repos |
Some endpoints are public (no token required). Private repositories require at least a :read scope.
Repo-scoped and org-scoped tokens are automatically restricted to their target — a repo token cannot access other repos, even if the user has access to them.
Response Format
Success
{
"data": { ... }
}
List with Pagination
{
"data": [ ... ],
"pagination": {
"page": 1,
"per_page": 30,
"total": 42
}
}
Error
{
"error": {
"code": "not_found",
"message": "Repository not found"
}
}
Pagination
List endpoints accept page and per_page query parameters:
| Parameter | Default | Max |
|---|---|---|
page |
1 | — |
per_page |
30 | 100 |
User
Get Current User
GET /api/v1/user
Scope: Any valid token
Returns the authenticated user's profile.
Response:
{
"data": {
"id": 1,
"username": "chris",
"email": "chris@example.com",
"display_name": "Chris",
"is_admin": true,
"avatar_url": "",
"created_at": "2025-01-15T10:00:00Z"
}
}
API Tokens
List Tokens
GET /api/v1/user/tokens
Scope: Any valid token
Returns all tokens for the authenticated user (token values are not included).
Create Token
POST /api/v1/user/tokens
Scope: Any valid token
Body:
{
"name": "CI Release Token",
"scopes": ["releases:write"],
"expires_at": "2026-12-31T23:59:59Z"
}
The expires_at field is optional (RFC 3339 format). Omit it for a non-expiring token.
Response: Returns the token object with a token field containing the plaintext value. This is the only time the token value is returned — store it securely.
Revoke Token
DELETE /api/v1/user/tokens/:id
Scope: Any valid token
Permanently deletes the token.
Organizations
Get Organization
GET /api/v1/orgs/:org
Auth: Optional (public endpoint)
Response:
{
"data": {
"id": 1,
"name": "leafscale",
"description": "Leafscale, LLC",
"created_at": "2025-01-15T10:00:00Z"
}
}
List Organization Repositories
GET /api/v1/orgs/:org/repos
Auth: Optional. Without auth, only public repos are returned. With auth, private repos visible to the user are included.
List Organization Members
GET /api/v1/orgs/:org/members
Scope: Any valid token (must be an org member)
Returns members with their roles.
Response:
{
"data": [
{
"user": { "id": 1, "username": "chris", ... },
"role": "owner"
}
]
}
Repositories
Get Repository
GET /api/v1/repos/:org/:repo
Auth: Optional (required for private repos)
Response:
{
"data": {
"id": 1,
"name": "my-project",
"org": "leafscale",
"description": "A cool project",
"is_private": false,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T14:30:00Z"
}
}
Create Repository
POST /api/v1/orgs/:org/repos
Scope: repo:write (must be an org member)
Body:
{
"name": "new-repo",
"description": "My new repository",
"is_private": false
}
Update Repository
PATCH /api/v1/repos/:org/:repo
Scope: repo:write (must be an org owner)
Body (all fields optional):
{
"description": "Updated description",
"is_private": true
}
Delete Repository
DELETE /api/v1/repos/:org/:repo
Scope: admin (must be an org owner)
Warning: This permanently deletes the repository and all its data.
Repository Contents
List Directory
GET /api/v1/repos/:org/:repo/tree/*path
Auth: Optional (required for private repos)
Query parameters:
| Parameter | Default | Description |
|---|---|---|
rev |
tip |
Revision (changeset hash, tag, bookmark, or tip) |
Response:
{
"data": [
{ "name": "src", "is_dir": true, "size": 0 },
{ "name": "README.md", "is_dir": false, "size": 1234 }
]
}
Get File Contents
GET /api/v1/repos/:org/:repo/blob/*path
Auth: Optional (required for private repos)
Query parameters:
| Parameter | Default | Description |
|---|---|---|
rev |
tip |
Revision |
Response:
{
"data": {
"path": "README.md",
"content": "# My Project\n...",
"size": 1234
}
}
Get Commit Log
GET /api/v1/repos/:org/:repo/log
Auth: Optional (required for private repos)
Query parameters:
| Parameter | Default | Description |
|---|---|---|
rev |
tip |
Starting revision |
page |
1 | Page number |
per_page |
30 | Results per page |
Get Changeset
GET /api/v1/repos/:org/:repo/changeset/:rev
Auth: Optional (required for private repos)
Returns changeset details including the diff.
Response:
{
"data": {
"id": "abc123...",
"short_id": "abc123",
"author": "Chris <chris@example.com>",
"date": "2025-01-20T14:30:00Z",
"message": "Full commit message",
"subject": "First line of commit",
"branch": "default",
"tags": ["v1.0"],
"diff": "diff -r ..."
}
}
Issues
Issues have three states: new, open, and closed. New issues start in the new state. Closing an issue moves it to closed. Reopening a closed issue returns it to open.
An issue in new automatically transitions to open when:
- The first comment is added, or
- An assignee is added (including assignees supplied on create)
Issue JSON always includes labels, assignees (empty arrays when none), and severity. Each label includes scope (admin, org, or repo). Assignees are org members represented as user objects.
Severity is a fixed enum: none, low, medium, high, or critical (default none). Invalid values → 400.
Note: Creating/editing/deleting label catalog entries, issue templates, and issue pinning remain web UI only. Listing available labels and applying/removing labels and assignees on issues is API-backed (this section).
List Issues
GET /api/v1/repos/:org/:repo/issues
Auth: Optional (required for private repos)
Query parameters:
| Parameter | Default | Description |
|---|---|---|
state |
open |
Filter by state: new, open, closed, active (new+open), or all (no state filter) |
severity |
— | Exact severity: none, low, medium, high, or critical (case-insensitive) |
label |
— | Label name (merged admin/org/repo catalog) |
assignee |
— | Assignee display name |
author |
— | Author display name |
q |
— | Title search (case-insensitive substring) |
sort |
newest |
newest, oldest, updated, or comments |
page |
1 | Page number |
per_page |
30 | Results per page |
Response:
{
"data": [
{
"id": 1,
"number": 1,
"title": "Bug: something is broken",
"body": "Steps to reproduce...",
"state": "new",
"severity": "none",
"author": {
"id": 1,
"email": "chris@example.com",
"display_name": "Chris",
"is_admin": true,
"avatar_url": "",
"created_at": "2025-01-15T10:00:00Z"
},
"labels": [
{ "id": 1, "name": "bug", "color": "#DC2626", "scope": "admin" }
],
"assignees": [],
"created_at": "2025-01-20T14:30:00Z",
"updated_at": "2025-01-20T14:30:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 30,
"total": 5
}
}
labels and assignees are always present ([] when empty). severity is always present. The author object is included when available.
Get Issue
GET /api/v1/repos/:org/:repo/issues/:number
Auth: Optional (required for private repos)
Returns the issue with labels, assignees, and severity, plus comments when any exist.
Response:
{
"data": {
"id": 1,
"number": 1,
"title": "Bug: something is broken",
"body": "Steps to reproduce...",
"state": "open",
"severity": "high",
"author": {
"id": 1,
"email": "chris@example.com",
"display_name": "Chris",
"is_admin": true,
"avatar_url": "",
"created_at": "2025-01-15T10:00:00Z"
},
"labels": [
{ "id": 1, "name": "bug", "color": "#DC2626", "scope": "admin" }
],
"assignees": [
{
"id": 2,
"email": "alice@example.com",
"display_name": "Alice",
"is_admin": false,
"avatar_url": "",
"created_at": "2025-01-10T08:00:00Z"
}
],
"comments": [
{
"id": 1,
"body": "I can reproduce this on rev abc123.",
"author": {
"id": 2,
"email": "alice@example.com",
"display_name": "Alice",
"is_admin": false,
"avatar_url": "",
"created_at": "2025-01-10T08:00:00Z"
},
"created_at": "2025-01-21T09:00:00Z"
}
],
"created_at": "2025-01-20T14:30:00Z",
"updated_at": "2025-01-21T09:00:00Z"
}
}
The comments array is only included when the issue has comments.
List Repository Labels
GET /api/v1/repos/:org/:repo/issues/labels
Auth: Optional (required for private repos)
Returns the merged label catalog available to the repository (admin + org + repo scopes).
Response:
{
"data": [
{
"id": 1,
"name": "bug",
"color": "#DC2626",
"scope": "admin",
"description": "Something is broken"
}
]
}
Create Issue
POST /api/v1/repos/:org/:repo/issues
Scope: issues:write
Body:
{
"title": "Bug: something is broken",
"body": "Steps to reproduce...",
"severity": "high",
"labels": ["bug", "needs-triage"],
"assignees": [
{ "user_id": 2 },
{ "display_name": "Alice" }
]
}
title is required. severity, labels, and assignees are optional. Omitted severity defaults to none. Labels are resolved by name in the merged catalog; assignees must be org members and may be identified by user_id, display_name, or both (they must refer to the same member). Unknown label/assignee → 404. Ambiguous display_name → 409. Duplicate labels or assignees in the request, assignee cap exceeded, or invalid severity → 400.
Create runs after this preflight validation so those bad values fail without creating an issue. Residual errors while applying already-validated metadata after insert are rare. When assignees are applied, a new issue is opened to open (same as assigning later).
Status: 201 Created with the enriched issue (including labels, assignees, and severity).
Without assignees, new issues are created in the new state.
Update Issue
PATCH /api/v1/repos/:org/:repo/issues/:number
Scope: issues:write
Body (all fields optional):
{
"title": "Updated title",
"body": "Updated description",
"state": "closed",
"severity": "low"
}
Title, body, and state may be changed only by the issue author or an org owner. Severity may be set by any authenticated caller with issues:write (same as applying labels or assignees). Set state to "closed" to close the issue, or "open" to reopen a closed issue. severity accepts none, low, medium, high, or critical (case-insensitive); invalid → 400.
Comment on Issue
POST /api/v1/repos/:org/:repo/issues/:number/comments
Scope: issues:write
Body:
{
"body": "Thanks for the report, this is fixed in rev abc123."
}
Status: 201 Created. The first comment on a new issue opens it (new → open).
Add Issue Label
POST /api/v1/repos/:org/:repo/issues/:number/labels
Scope: issues:write
Body:
{
"name": "bug"
}
Applies an existing catalog label by name. Unknown label → 404.
Status: 200 OK with the enriched issue.
Remove Issue Label
DELETE /api/v1/repos/:org/:repo/issues/:number/labels/:name
Scope: issues:write
:name is the label name (URL-encoded if it contains reserved characters). Unknown label → 404.
Status: 200 OK with the enriched issue.
Add Issue Assignee
POST /api/v1/repos/:org/:repo/issues/:number/assignees
Scope: issues:write
Body (provide user_id, display_name, or both):
{
"user_id": 2,
"display_name": "Alice"
}
Assignees must be organization members. Unknown member → 404. Ambiguous display_name → 409. Cap exceeded → 400.
Assigning to a new issue opens it (new → open).
Status: 200 OK with the enriched issue.
Remove Issue Assignee
DELETE /api/v1/repos/:org/:repo/issues/:number/assignees/:id
Scope: issues:write
:id is the assignee's user id.
Status: 200 OK with the enriched issue.
Pull Requests
List Pull Requests
GET /api/v1/repos/:org/:repo/pulls
Auth: Optional (required for private repos)
Query parameters:
| Parameter | Default | Description |
|---|---|---|
status |
open |
Filter: open, closed, or merged |
page |
1 | Page number |
per_page |
30 | Results per page |
Get Pull Request
GET /api/v1/repos/:org/:repo/pulls/:number
Auth: Optional (required for private repos)
Returns the PR with all comments.
Create Pull Request
POST /api/v1/repos/:org/:repo/pulls
Scope: pulls:write (must be an org member)
Body:
{
"title": "Add new feature",
"body": "This PR adds...",
"source_ref": "my-feature",
"target_ref": "@"
}
The API accepts the VCS-neutral names source_ref/target_ref; PR responses additionally include a ref_type field (bookmark for Mercurial, branch for Git/Fossil) indicating the kind of ref. ref_type is response-only and is not accepted in requests.
If target_ref is omitted, it defaults to the mainline @ bookmark for Mercurial repos, or the repository's default branch for Git/Fossil repos.
Update Pull Request
PATCH /api/v1/repos/:org/:repo/pulls/:number
Scope: pulls:write (must be author or org owner)
Body (all fields optional):
{
"title": "Updated title",
"body": "Updated description"
}
Merge Pull Request
POST /api/v1/repos/:org/:repo/pulls/:number/merge
Scope: pulls:write (must be an org member)
Merges the source bookmark into mainline (the @ bookmark). Fast-forwards when mainline hasn't diverged, otherwise creates a merge commit; @ advances to the result.
Comment on Pull Request
POST /api/v1/repos/:org/:repo/pulls/:number/comments
Scope: pulls:write
Body:
{
"body": "LGTM, ship it!"
}
CI/CD Pipelines
List Pipelines
GET /api/v1/repos/:org/:repo/pipelines
Auth: Optional (required for private repos)
Query parameters:
| Parameter | Default | Description |
|---|---|---|
status |
(all) | Filter by status: pending, running, success, failure, cancelled |
page |
1 | Page number |
per_page |
20 | Results per page (max 50) |
Response:
{
"total": 42,
"page": 1,
"per_page": 20,
"pipelines": [
{
"id": 1,
"number": 7,
"commit_node": "a1b2c3d4e5f6...",
"branch": "default",
"tag": "",
"event": "push",
"events": ["push"],
"status": "success",
"trigger_by": 1,
"trigger_user": { "id": 1, "username": "chris" },
"created_at": "2025-01-20T14:30:00Z",
"started_at": "2025-01-20T14:30:05Z",
"finished_at": "2025-01-20T14:32:10Z"
}
]
}
tag is the tag name for tag-triggered pipelines (empty string otherwise). events is the full list of events that triggered the pipeline (e.g. ["push", "pull_request"] for a dual-event pipeline) — event remains the single primary event. error_message (human-readable failure reason) is included only when set, e.g. on a failure or error status pipeline:
{
"error_message": "clone failed: repository not found"
}
Get Pipeline
GET /api/v1/repos/:org/:repo/pipelines/:number
Auth: Optional (required for private repos)
Returns the pipeline with all steps.
Response:
{
"id": 1,
"number": 7,
"commit_node": "a1b2c3d4e5f6...",
"branch": "default",
"tag": "",
"event": "push",
"events": ["push"],
"status": "success",
"trigger_by": 1,
"trigger_user": { "id": 1, "username": "chris" },
"created_at": "2025-01-20T14:30:00Z",
"started_at": "2025-01-20T14:30:05Z",
"finished_at": "2025-01-20T14:32:10Z",
"steps": [
{
"id": 1,
"number": 1,
"name": "test",
"image": "golang:1.26",
"status": "success",
"exit_code": 0,
"started_at": "2025-01-20T14:30:05Z",
"finished_at": "2025-01-20T14:32:10Z"
}
]
}
Get Pipeline Step Log
GET /api/v1/repos/:org/:repo/pipelines/:number/steps/:step/log
Scope: ci:read (authentication required; session users are treated as full access)
Returns the plain-text log for one step (Content-Type: text/plain; charset=utf-8).
:step may be the step number (as shown in the pipeline detail / steps[].number) or the step name (exact match).
Headers (response):
| Header | Meaning |
|---|---|
X-Isurus-Step-Name |
Step name |
X-Isurus-Step-Number |
Step number |
X-Isurus-Log-Truncated |
true if the body was capped (16 MiB max) |
Errors: 401 unauthenticated · 403 insufficient scope · 404 pipeline/step/log not found.
This is a one-shot snapshot (what isu ci logs uses). Live SSE streaming remains session-only on the web UI.
Cancel Pipeline
POST /api/v1/repos/:org/:repo/pipelines/:number/cancel
Scope: ci:write (must be an org member)
Cancels a running or pending pipeline and all its pending/running steps.
Re-run Pipeline
POST /api/v1/repos/:org/:repo/pipelines/:number/rerun
Scope: ci:write (must be an org member)
Creates a new pipeline from the same commit. Returns the new pipeline object.
Note: Pipeline deletion (single, bulk, and delete-all-failed) is available through the web interface only. API endpoints for pipeline deletion are planned for a future release.
CI Agent API
These endpoints are used by isurus-agent workers. They use agent token authentication (not user API tokens).
Agent Authentication
Agents authenticate with a Bearer token issued during agent registration:
curl -H "Authorization: Bearer AGENT_TOKEN" \
-X POST https://your-isurus-instance/api/v1/ci/poll
Poll for Job
POST /api/v1/ci/poll
Claims the next pending pipeline. Returns 204 No Content if no work is available.
Response (200):
{
"pipeline_id": 42,
"number": 7,
"commit_node": "a1b2c3d4e5f6...",
"branch": "default",
"event": "push",
"clone_url": "https://hg.example.com/leafscale/isurus",
"org": "leafscale",
"repo": "isurus",
"steps": [
{
"id": 1,
"number": 1,
"name": "test",
"image": "golang:1.26",
"commands": ["go test -v ./..."],
"environment": { "CGO_ENABLED": "0" },
"status": "pending"
}
],
"ci_env": {
"CI": "true",
"CI_PIPELINE_ID": "42",
"CI_COMMIT": "a1b2c3d4e5f6...",
"CI_BRANCH": "default",
"CI_REPO": "leafscale/isurus"
}
}
Heartbeat
POST /api/v1/ci/heartbeat
Updates the agent's last-seen timestamp. Called on each poll cycle.
Update Step Status
PUT /api/v1/ci/steps/:id/status
Body:
{
"status": "running",
"exit_code": 0,
"started_at": "2025-01-20T14:30:05Z",
"finished_at": "2025-01-20T14:32:10Z"
}
Status values: pending, running, success, failure, skipped, cancelled
Update Pipeline Status
PUT /api/v1/ci/pipelines/:id/status
Body:
{
"status": "success",
"finished_at": "2025-01-20T14:32:10Z"
}
Status values: pending, running, success, failure, cancelled, error
Append Step Log
POST /api/v1/ci/steps/:id/log
Appends raw log data to the step's log file. Body is raw text (not JSON). Maximum 1MB per request.
Webhooks
List Webhooks
GET /api/v1/repos/:org/:repo/webhooks
Scope: admin (must be an org owner)
Create Webhook
POST /api/v1/repos/:org/:repo/webhooks
Scope: admin (must be an org owner)
Body:
{
"url": "https://example.com/webhook",
"secret": "my-secret",
"events": ["push", "tag"]
}
Event types: push, tag, pull_request.opened, pull_request.closed, pull_request.merged, pull_request.commented, issue.opened, issue.closed, issue.reopened, issue.commented
tag is subscribed independently of push — a repository that only wants tag notifications does not need push checked, and vice versa. See Webhook Payloads below for what each event delivers.
Update Webhook
PATCH /api/v1/repos/:org/:repo/webhooks/:webhook_id
Scope: admin (must be an org owner)
Body (all fields optional):
{
"url": "https://example.com/new-endpoint",
"events": ["push"],
"is_active": false
}
Delete Webhook
DELETE /api/v1/repos/:org/:repo/webhooks/:webhook_id
Scope: admin (must be an org owner)
List Webhook Deliveries
GET /api/v1/repos/:org/:repo/webhooks/:webhook_id/deliveries
Scope: admin (must be an org owner)
Returns the 20 most recent deliveries.
Response:
{
"data": [
{
"id": 1,
"event_type": "push",
"response_code": 200,
"duration_ms": 150,
"success": true,
"delivered_at": "2025-01-20T14:30:00Z"
}
]
}
Webhook Payloads
Every delivery is an HTTP POST with Content-Type: application/json and a common envelope. The event-specific data is nested under payload.
Headers:
| Header | Description |
|---|---|
X-Isurus-Event |
The event type, e.g. push, tag, issue.opened |
X-Isurus-Delivery |
A UUID unique to this delivery attempt (retries of the same trigger share the delivery ID) |
X-Isurus-Signature-256 |
sha256=<hex hmac> — HMAC-SHA256 of the raw request body using the webhook's configured secret. Omitted if the webhook has no secret set. |
User-Agent |
Isurus-Webhooks/1.0 |
Envelope:
{
"event": "push",
"delivery_id": "b3f2a8e0-1e5b-4a3a-9c2e-2b7a6f0d9c1a",
"repository": {
"id": 42,
"name": "reef-lang",
"org": "leafscale",
"url": "https://example.com/leafscale/reef-lang"
},
"sender": {
"id": 7,
"display_name": "Chris Tusa"
},
"timestamp": "2026-07-07T14:03:11Z",
"payload": { }
}
Verify the signature by computing hmac_sha256(secret, raw_request_body) and comparing (constant-time) to the hex digest after sha256=.
Push payload
Fires on push. ref_type is "branch" for Git and Fossil pushes (ref is the branch name). For Mercurial, ref_type is "bookmark" only when a bookmark actually moved (ref is the bookmark name, e.g. @); an anonymous push to the default line reports ref "default" with ref_type "branch". commits holds up to 50 entries, newest-first; if the push introduced more than 50 commits, truncated is true and only the newest 50 are included. before/after are the repository tip before and after the push — for Mercurial these are changegroup-scoped tips, not a single ref's prior/new value. deleted is reserved and always false (ref deletion isn't fired yet).
{
"ref": "default",
"ref_type": "branch",
"default": true,
"before": "a1b2c3d4e5f60000000000000000000000000000",
"after": "f6e5d4c3b2a10000000000000000000000000000",
"created": false,
"deleted": false,
"commits": [
{
"id": "f6e5d4c3b2a10000000000000000000000000000",
"short_id": "f6e5d4c3b2a1",
"message": "Fix off-by-one in tag walk\n\nAlso adds a regression test.",
"subject": "Fix off-by-one in tag walk",
"author": {
"name": "Chris Tusa",
"email": "chris.tusa@leafscale.com",
"date": "2026-07-07T14:03:05Z"
},
"url": "https://example.com/leafscale/reef-lang/commit/f6e5d4c3b2a10000000000000000000000000000"
}
],
"head_commit": {
"id": "f6e5d4c3b2a10000000000000000000000000000",
"short_id": "f6e5d4c3b2a1",
"message": "Fix off-by-one in tag walk\n\nAlso adds a regression test.",
"subject": "Fix off-by-one in tag walk",
"author": {
"name": "Chris Tusa",
"email": "chris.tusa@leafscale.com",
"date": "2026-07-07T14:03:05Z"
},
"url": "https://example.com/leafscale/reef-lang/commit/f6e5d4c3b2a10000000000000000000000000000"
},
"total_commits": 1,
"truncated": false
}
| Field | Type | Description |
|---|---|---|
ref |
string | The branch/bookmark name that moved, or "default" for an anonymous Mercurial push |
ref_type |
string | "branch" (Git, Fossil, and anonymous Mercurial default-line pushes) or "bookmark" (a Mercurial bookmark moved) |
default |
bool | Whether ref is the repository's default/mainline line |
before |
string | Repo tip before the push (empty when the ref was newly created) |
after |
string | Repo tip after the push |
created |
bool | Whether this push created the ref |
deleted |
bool | Reserved; always false |
commits |
array | Up to 50 commits introduced by the push, newest-first |
head_commit |
object | null | The first (newest) entry of commits, or null if commits is empty |
total_commits |
int | Number of commits in the commits array (i.e. len(commits), capped at 50) |
truncated |
bool | true if the push introduced more than 50 commits |
Tag payload
Fires on tag — subscribable independently of push. A tag event is raised for a Git tag push, a Fossil tag, or a Mercurial commit that adds/modifies .hgtags (in Mercurial, applying a tag is itself a commit). commit is the tagged commit (same shape as a push commit) and is omitted if it could not be resolved.
{
"ref": "v1.4.0",
"ref_type": "tag",
"created": true,
"target": "f6e5d4c3b2a10000000000000000000000000000",
"commit": {
"id": "f6e5d4c3b2a10000000000000000000000000000",
"short_id": "f6e5d4c3b2a1",
"message": "Release 1.4.0",
"subject": "Release 1.4.0",
"author": {
"name": "Chris Tusa",
"email": "chris.tusa@leafscale.com",
"date": "2026-07-07T14:03:05Z"
},
"url": "https://example.com/leafscale/reef-lang/commit/f6e5d4c3b2a10000000000000000000000000000"
}
}
| Field | Type | Description |
|---|---|---|
ref |
string | Tag name |
ref_type |
string | Always "tag" |
created |
bool | Always true (tag deletion events are not yet fired) |
target |
string | Commit the tag points at |
commit |
object | Present when the target commit could be resolved; omitted otherwise |
Pull request and issue event payloads are unchanged from their existing shapes (see the Pull Requests and Issues docs).
Releases
List Releases
GET /api/v1/repos/:org/:repo/releases
Returns paginated releases. Draft releases are only visible to org members.
Query parameters: page, per_page
Get Release
GET /api/v1/repos/:org/:repo/releases/:id
Get Release by Tag
GET /api/v1/repos/:org/:repo/releases/tags/:tag
Create Release
POST /api/v1/repos/:org/:repo/releases
Requires: releases:write scope
{
"tag_name": "v1.0.0",
"name": "Version 1.0.0",
"body": "Release notes in markdown",
"draft": false,
"prerelease": false
}
Update Release
PATCH /api/v1/repos/:org/:repo/releases/:id
Requires: releases:write scope. All fields are optional — only provided fields are updated.
Delete Release
DELETE /api/v1/repos/:org/:repo/releases/:id
Requires: releases:write scope. Deletes the release and all its attachments.
List Release Assets
GET /api/v1/repos/:org/:repo/releases/:id/assets
Upload Release Asset
POST /api/v1/repos/:org/:repo/releases/:id/assets
Requires: releases:write scope. Send as multipart/form-data with field name attachment.
Delete Release Asset
DELETE /api/v1/repos/:org/:repo/releases/:id/assets/:asset_id
Requires: releases:write scope