Table of Contents
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
Create tokens under Settings > API Tokens in the web UI, or via the API itself.
Token Scopes
| Scope | Access |
|---|---|
read |
Read repositories, issues, pull requests, org info |
write |
Create/update repositories, issues, pull requests, webhooks |
admin |
Delete repositories, administrative actions |
Some endpoints are public (no token required). Private repositories require at least read scope.
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: read
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: read
Returns all tokens for the authenticated user (token values are not included).
Create Token
POST /api/v1/user/tokens
Scope: read
Body:
{
"name": "CI Pipeline",
"scopes": ["read", "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: read
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: read (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: 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: 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": {
"node": "abc123...",
"short_node": "abc123",
"rev": 42,
"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. Triaging an issue moves it to open. Closing an issue moves it to closed. Reopening a closed issue returns it to open.
Note: Label management, issue templates, assignee management, and issue pinning are currently available through the web interface only. API endpoints for these features are planned for a future release.
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, or closed |
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",
"author": {
"id": 1,
"username": "chris",
"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" }
],
"created_at": "2025-01-20T14:30:00Z",
"updated_at": "2025-01-20T14:30:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 30,
"total": 5
}
}
The labels array is included when the issue has labels assigned. 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 all comments.
Response:
{
"data": {
"id": 1,
"number": 1,
"title": "Bug: something is broken",
"body": "Steps to reproduce...",
"state": "open",
"author": {
"id": 1,
"username": "chris",
"display_name": "Chris"
},
"labels": [
{ "id": 1, "name": "bug", "color": "#DC2626" }
],
"comments": [
{
"id": 1,
"body": "I can reproduce this on rev abc123.",
"author": {
"id": 2,
"username": "alice",
"display_name": "Alice"
},
"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.
Create Issue
POST /api/v1/repos/:org/:repo/issues
Scope: write
Body:
{
"title": "Bug: something is broken",
"body": "Steps to reproduce..."
}
New issues are created in the new state.
Update Issue
PATCH /api/v1/repos/:org/:repo/issues/:number
Scope: write (must be author or org owner)
Body (all fields optional):
{
"title": "Updated title",
"body": "Updated description",
"state": "closed"
}
Set state to "closed" to close the issue, or "open" to reopen a closed issue.
Comment on Issue
POST /api/v1/repos/:org/:repo/issues/:number/comments
Scope: write
Body:
{
"body": "Thanks for the report, this is fixed in rev abc123."
}
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: write (must be an org member)
Body:
{
"title": "Add new feature",
"body": "This PR adds...",
"source_bookmark": "my-feature",
"target_branch": "default"
}
The target_branch defaults to "default" if omitted.
Update Pull Request
PATCH /api/v1/repos/:org/:repo/pulls/:number
Scope: 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: write (must be an org member)
Merges the source bookmark into the target branch.
Comment on Pull Request
POST /api/v1/repos/:org/:repo/pulls/:number/comments
Scope: 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",
"event": "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"
}
]
}
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",
"event": "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"
}
]
}
Cancel Pipeline
POST /api/v1/repos/:org/:repo/pipelines/:number/cancel
Scope: 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: 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: write (must be an org owner)
Create Webhook
POST /api/v1/repos/:org/:repo/webhooks
Scope: write (must be an org owner)
Body:
{
"url": "https://example.com/webhook",
"secret": "my-secret",
"events": ["push", "issue", "pull_request"]
}
Event types: push, issue, pull_request
Update Webhook
PATCH /api/v1/repos/:org/:repo/webhooks/:webhook_id
Scope: write (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: write (must be an org owner)
List Webhook Deliveries
GET /api/v1/repos/:org/:repo/webhooks/:webhook_id/deliveries
Scope: write (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"
}
]
}
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: 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: write scope. All fields are optional — only provided fields are updated.
Delete Release
DELETE /api/v1/repos/:org/:repo/releases/:id
Requires: 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: 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: write scope