2
0
Files
logikonline 8806fcecba
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m19s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m39s
Build and Release / Lint (push) Successful in 4m46s
Build and Release / Build Binary (linux/arm64) (push) Has been cancelled
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been cancelled
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been cancelled
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been cancelled
docs(detached-note): add comprehensive API reference and user guide
Add API.md (3200+ lines) with complete REST API documentation covering authentication, repository management, issues, pull requests, organizations, package registry, Actions, and Vault APIs. Includes code examples and error handling.

Add GUIDE.md with user-focused documentation for getting started, repository operations, collaboration workflows, and CI/CD setup.

Implement documentation tab in repository view with automatic detection and rendering of API.md and GUIDE.md files alongside README. Add locale strings and template for doc tab navigation.
2026-01-27 22:43:56 -05:00

64 KiB

GitCaddy API Reference

Complete technical reference for developers integrating with or using GitCaddy, a self-hosted Git repository management platform.

Table of Contents

Introduction

GitCaddy provides comprehensive REST APIs for repository management, collaboration tools, CI/CD workflows, package registry, and enterprise features. All API endpoints are accessible at:

https://your-gitcaddy-instance.com/api/v1/

Base URL Format:

https://{instance}/api/v1/{endpoint}

Content Type: All requests and responses use application/json unless otherwise specified.

API Versioning: Current stable version: v1

Authentication

GitCaddy supports multiple authentication methods for API access.

Personal Access Tokens

Personal access tokens (PATs) provide programmatic access to the GitCaddy API.

Creating a Token:

  1. Navigate to User Settings → Applications → Manage Access Tokens
  2. Click "Generate New Token"
  3. Set token name and scopes
  4. Copy the generated token (shown only once)

Using Tokens:

Include the token in the Authorization header:

Authorization: token YOUR_ACCESS_TOKEN

Example Request:

curl -H "Authorization: token abc123def456" \
  https://gitcaddy.example.com/api/v1/user

Token Scopes:

Scope Description
repo Full access to repositories
repo:status Read-only access to repository status
public_repo Access to public repositories only
admin:org Full organization administration
write:org Write access to organizations
read:org Read-only organization access
admin:public_key Manage public SSH keys
admin:repo_hook Manage repository webhooks
admin:org_hook Manage organization webhooks
notification Access notifications
user Full user profile access
read:user Read-only user profile access
user:email Access user email addresses
delete_repo Delete repositories
package Access package registry
admin:gpg_key Manage GPG keys
admin:application Manage OAuth applications

OAuth2 Authentication

GitCaddy supports OAuth2 for third-party application integration.

OAuth2 Flow:

  1. Register Application:

    • User Settings → Applications → OAuth2 Applications
    • Create new OAuth2 application
    • Note client_id and client_secret
  2. Authorization Request:

GET /login/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}
  1. Token Exchange:
POST /login/oauth/access_token
Content-Type: application/json

{
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "code": "authorization_code",
  "grant_type": "authorization_code",
  "redirect_uri": "https://your-app.com/callback"
}

Response:

{
  "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
  "token_type": "bearer",
  "scope": "repo,user"
}
  1. Using Access Token:
Authorization: Bearer gho_16C7e42F292c6912E7710c838347Ae178B4a

Supported OAuth2 Providers:

GitCaddy can authenticate users via external OAuth2 providers:

  • GitHub
  • GitLab
  • Bitbucket
  • Google
  • Microsoft Azure AD
  • Custom OpenID Connect providers

WebAuthn (Passkeys)

GitCaddy supports WebAuthn for passwordless authentication and two-factor authentication.

Registration API:

// Client-side registration
const response = await fetch('/user/settings/security/webauthn/register', {
  method: 'GET',
  credentials: 'include'
});

const options = await response.json();

// Create credential
const credential = await navigator.credentials.create({
  publicKey: options
});

// Send credential to server
await fetch('/user/settings/security/webauthn/register', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'My Security Key',
    credential: credential
  })
});

Authentication API:

// Get authentication challenge
const response = await fetch('/user/webauthn/assertion', {
  method: 'GET'
});

const options = await response.json();

// Get credential
const assertion = await navigator.credentials.get({
  publicKey: options
});

// Verify assertion
await fetch('/user/webauthn/assertion', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(assertion)
});

REST API Endpoints

Repository APIs

List User Repositories

GET /api/v1/user/repos

Parameters:

Parameter Type Required Description
page integer No Page number (default: 1)
limit integer No Page size (default: 10, max: 50)
type string No Filter by type: owner, collaborator, member

Response:

[
  {
    "id": 1,
    "owner": {
      "id": 1,
      "login": "username",
      "full_name": "User Name",
      "avatar_url": "https://gitcaddy.example.com/avatars/1"
    },
    "name": "my-repo",
    "full_name": "username/my-repo",
    "description": "Repository description",
    "private": false,
    "fork": false,
    "parent": null,
    "empty": false,
    "mirror": false,
    "size": 1024,
    "html_url": "https://gitcaddy.example.com/username/my-repo",
    "ssh_url": "git@gitcaddy.example.com:username/my-repo.git",
    "clone_url": "https://gitcaddy.example.com/username/my-repo.git",
    "website": "",
    "stars_count": 5,
    "forks_count": 2,
    "watchers_count": 3,
    "open_issues_count": 1,
    "default_branch": "main",
    "archived": false,
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-15T12:30:00Z",
    "permissions": {
      "admin": true,
      "push": true,
      "pull": true
    }
  }
]

Create Repository

POST /api/v1/user/repos

Request Body:

{
  "name": "new-repo",
  "description": "My new repository",
  "private": false,
  "auto_init": true,
  "gitignores": "Go",
  "license": "MIT",
  "readme": "Default",
  "default_branch": "main"
}

Parameters:

Parameter Type Required Description
name string Yes Repository name
description string No Repository description
private boolean No Create as private (default: false)
auto_init boolean No Initialize with README (default: false)
gitignores string No .gitignore template name
license string No License template (MIT, Apache-2.0, GPL-3.0, etc.)
readme string No README template
default_branch string No Default branch name (default: main)
trust_model string No Trust model: default, collaborator, committer, collaboratorcommitter

Response: 201 Created

{
  "id": 2,
  "name": "new-repo",
  "full_name": "username/new-repo",
  "private": false,
  "html_url": "https://gitcaddy.example.com/username/new-repo",
  "clone_url": "https://gitcaddy.example.com/username/new-repo.git",
  "created_at": "2024-01-20T10:00:00Z"
}

Get Repository

GET /api/v1/repos/{owner}/{repo}

Response:

{
  "id": 1,
  "owner": {
    "id": 1,
    "login": "username",
    "full_name": "User Name"
  },
  "name": "my-repo",
  "full_name": "username/my-repo",
  "description": "Repository description",
  "private": false,
  "default_branch": "main",
  "permissions": {
    "admin": true,
    "push": true,
    "pull": true
  }
}

Delete Repository

DELETE /api/v1/repos/{owner}/{repo}

Response: 204 No Content

List Repository Branches

GET /api/v1/repos/{owner}/{repo}/branches

Response:

[
  {
    "name": "main",
    "commit": {
      "id": "abc123def456",
      "message": "Initial commit",
      "url": "https://gitcaddy.example.com/username/my-repo/commit/abc123def456"
    },
    "protected": true,
    "user_can_push": true,
    "user_can_merge": true
  }
]

Create Branch

POST /api/v1/repos/{owner}/{repo}/branches

Request Body:

{
  "new_branch_name": "feature-branch",
  "old_branch_name": "main"
}

Get Branch Protection

GET /api/v1/repos/{owner}/{repo}/branch_protections/{branch}

Response:

{
  "branch_name": "main",
  "enable_push": false,
  "enable_push_whitelist": true,
  "push_whitelist_usernames": ["admin"],
  "push_whitelist_teams": ["core-team"],
  "push_whitelist_deploy_keys": false,
  "enable_merge_whitelist": true,
  "merge_whitelist_usernames": ["maintainer"],
  "merge_whitelist_teams": ["reviewers"],
  "enable_status_check": true,
  "status_check_contexts": ["ci/tests", "ci/lint"],
  "required_approvals": 2,
  "enable_approvals_whitelist": false,
  "block_on_rejected_reviews": true,
  "dismiss_stale_approvals": true,
  "require_signed_commits": true,
  "protected_file_patterns": "*.lock",
  "unprotected_file_patterns": "docs/*"
}

List Repository Tags

GET /api/v1/repos/{owner}/{repo}/tags

Response:

[
  {
    "name": "v1.0.0",
    "commit": {
      "sha": "abc123def456",
      "url": "https://gitcaddy.example.com/username/my-repo/commit/abc123def456"
    },
    "zipball_url": "https://gitcaddy.example.com/username/my-repo/archive/v1.0.0.zip",
    "tarball_url": "https://gitcaddy.example.com/username/my-repo/archive/v1.0.0.tar.gz"
  }
]

Get File Contents

GET /api/v1/repos/{owner}/{repo}/contents/{filepath}?ref={branch}

Parameters:

Parameter Type Required Description
ref string No Branch, tag, or commit SHA (default: default branch)

Response:

{
  "name": "README.md",
  "path": "README.md",
  "sha": "abc123",
  "type": "file",
  "size": 1024,
  "encoding": "base64",
  "content": "IyBNeVJlcG8KClRoaXMgaXMgYSBSRUFETUUgZmlsZS4=",
  "target": null,
  "url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/contents/README.md",
  "html_url": "https://gitcaddy.example.com/username/my-repo/src/branch/main/README.md",
  "git_url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/git/blobs/abc123",
  "download_url": "https://gitcaddy.example.com/username/my-repo/raw/branch/main/README.md"
}

Create/Update File

POST /api/v1/repos/{owner}/{repo}/contents/{filepath}

Request Body:

{
  "content": "base64_encoded_content",
  "message": "Create README.md",
  "branch": "main",
  "sha": "abc123def456",
  "author": {
    "name": "Author Name",
    "email": "author@example.com"
  },
  "committer": {
    "name": "Committer Name",
    "email": "committer@example.com"
  },
  "dates": {
    "author": "2024-01-20T10:00:00Z",
    "committer": "2024-01-20T10:00:00Z"
  },
  "signoff": false,
  "new_branch": "feature-branch"
}

Parameters:

Parameter Type Required Description
content string Yes Base64-encoded file content
message string Yes Commit message
branch string No Branch name (default: default branch)
sha string No Blob SHA for update (required when updating)
new_branch string No Create new branch for commit

Delete File

DELETE /api/v1/repos/{owner}/{repo}/contents/{filepath}

Request Body:

{
  "message": "Delete file",
  "sha": "abc123def456",
  "branch": "main"
}

Fork Repository

POST /api/v1/repos/{owner}/{repo}/forks

Request Body:

{
  "organization": "my-org",
  "name": "forked-repo"
}

List Forks

GET /api/v1/repos/{owner}/{repo}/forks

Mirror Repository

POST /api/v1/repos/migrate

Request Body:

{
  "clone_addr": "https://github.com/user/repo.git",
  "auth_username": "username",
  "auth_password": "password_or_token",
  "uid": 1,
  "repo_name": "mirrored-repo",
  "mirror": true,
  "private": false,
  "description": "Mirrored repository"
}

Issue APIs

List Repository Issues

GET /api/v1/repos/{owner}/{repo}/issues

Parameters:

Parameter Type Required Description
state string No Filter by state: open, closed, all (default: open)
labels string No Comma-separated label IDs or names
q string No Search query
type string No Filter by type: issues, pulls
milestones string No Comma-separated milestone names
since string No Only issues updated after this time (ISO 8601)
before string No Only issues updated before this time (ISO 8601)
created_by string No Filter by creator username
assigned_by string No Filter by assignee username
mentioned_by string No Filter by mentioned username
page integer No Page number
limit integer No Page size

Response:

[
  {
    "id": 1,
    "url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/issues/1",
    "html_url": "https://gitcaddy.example.com/username/my-repo/issues/1",
    "number": 1,
    "user": {
      "id": 1,
      "login": "username",
      "full_name": "User Name"
    },
    "title": "Bug: Application crashes on startup",
    "body": "Detailed description of the issue...",
    "labels": [
      {
        "id": 1,
        "name": "bug",
        "color": "#ee0701",
        "description": "Something isn't working"
      }
    ],
    "milestone": {
      "id": 1,
      "title": "v1.0",
      "description": "First release",
      "state": "open",
      "due_on": "2024-12-31T23:59:59Z"
    },
    "assignees": [
      {
        "id": 2,
        "login": "developer"
      }
    ],
    "state": "open",
    "is_locked": false,
    "comments": 3,
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-16T14:30:00Z",
    "closed_at": null,
    "due_date": null,
    "pull_request": null
  }
]

Create Issue

POST /api/v1/repos/{owner}/{repo}/issues

Request Body:

{
  "title": "New issue title",
  "body": "Issue description with **markdown** support",
  "assignees": ["developer1", "developer2"],
  "labels": [1, 2],
  "milestone": 1,
  "closed": false,
  "due_date": "2024-12-31T23:59:59Z",
  "ref": "main"
}

Response: 201 Created

Get Issue

GET /api/v1/repos/{owner}/{repo}/issues/{index}

Update Issue

PATCH /api/v1/repos/{owner}/{repo}/issues/{index}

Request Body:

{
  "title": "Updated title",
  "body": "Updated description",
  "state": "closed",
  "assignees": ["user1"],
  "labels": [1, 3],
  "milestone": 2,
  "due_date": "2024-12-31T23:59:59Z",
  "unset_due_date": false
}

List Issue Comments

GET /api/v1/repos/{owner}/{repo}/issues/{index}/comments

Response:

[
  {
    "id": 1,
    "html_url": "https://gitcaddy.example.com/username/my-repo/issues/1#issuecomment-1",
    "pull_request_url": "",
    "issue_url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/issues/1",
    "user": {
      "id": 1,
      "login": "username"
    },
    "original_author": "",
    "original_author_id": 0,
    "body": "This is a comment on the issue",
    "created_at": "2024-01-15T11:00:00Z",
    "updated_at": "2024-01-15T11:00:00Z"
  }
]

Create Issue Comment

POST /api/v1/repos/{owner}/{repo}/issues/{index}/comments

Request Body:

{
  "body": "Comment text with **markdown** support"
}

Edit Issue Comment

PATCH /api/v1/repos/{owner}/{repo}/issues/comments/{id}

Delete Issue Comment

DELETE /api/v1/repos/{owner}/{repo}/issues/comments/{id}

List Issue Labels

GET /api/v1/repos/{owner}/{repo}/labels

Response:

[
  {
    "id": 1,
    "name": "bug",
    "color": "#ee0701",
    "description": "Something isn't working",
    "url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/labels/1"
  }
]

Create Label

POST /api/v1/repos/{owner}/{repo}/labels

Request Body:

{
  "name": "enhancement",
  "color": "#a2eeef",
  "description": "New feature or request"
}

Track Time on Issue

POST /api/v1/repos/{owner}/{repo}/issues/{index}/times

Request Body:

{
  "time": 3600,
  "created": "2024-01-15T10:00:00Z",
  "user_name": "username"
}

Parameters:

Parameter Type Required Description
time integer Yes Time in seconds
created string No Timestamp (ISO 8601)
user_name string No Username (admin only)

List Tracked Times

GET /api/v1/repos/{owner}/{repo}/issues/{index}/times

Pull Request APIs

List Pull Requests

GET /api/v1/repos/{owner}/{repo}/pulls

Parameters:

Parameter Type Required Description
state string No Filter by state: open, closed, all
sort string No Sort by: oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority
milestone integer No Filter by milestone ID
labels string No Comma-separated label IDs
page integer No Page number
limit integer No Page size

Response:

[
  {
    "id": 1,
    "url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/pulls/1",
    "number": 1,
    "user": {
      "id": 1,
      "login": "contributor"
    },
    "title": "Add new feature",
    "body": "This PR adds a new feature...",
    "labels": [],
    "milestone": null,
    "assignees": [],
    "state": "open",
    "is_locked": false,
    "comments": 2,
    "html_url": "https://gitcaddy.example.com/username/my-repo/pulls/1",
    "diff_url": "https://gitcaddy.example.com/username/my-repo/pulls/1.diff",
    "patch_url": "https://gitcaddy.example.com/username/my-repo/pulls/1.patch",
    "mergeable": true,
    "merged": false,
    "merged_at": null,
    "merge_commit_sha": null,
    "merged_by": null,
    "base": {
      "label": "main",
      "ref": "main",
      "sha": "abc123",
      "repo_id": 1,
      "repo": {
        "id": 1,
        "name": "my-repo",
        "full_name": "username/my-repo"
      }
    },
    "head": {
      "label": "contributor:feature-branch",
      "ref": "feature-branch",
      "sha": "def456",
      "repo_id": 2,
      "repo": {
        "id": 2,
        "name": "my-repo",
        "full_name": "contributor/my-repo"
      }
    },
    "merge_base": "abc123",
    "due_date": null,
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-16T14:30:00Z",
    "closed_at": null
  }
]

Create Pull Request

POST /api/v1/repos/{owner}/{repo}/pulls

Request Body:

{
  "title": "Add new feature",
  "body": "Detailed description of changes",
  "head": "feature-branch",
  "base": "main",
  "assignees": ["reviewer1"],
  "labels": [1],
  "milestone": 1,
  "due_date": "2024-12-31T23:59:59Z"
}

Parameters:

Parameter Type Required Description
title string Yes PR title
body string No PR description
head string Yes Branch containing changes
base string Yes Branch to merge into
assignees array No Array of usernames
labels array No Array of label IDs
milestone integer No Milestone ID

Get Pull Request

GET /api/v1/repos/{owner}/{repo}/pulls/{index}

Update Pull Request

PATCH /api/v1/repos/{owner}/{repo}/pulls/{index}

Request Body:

{
  "title": "Updated title",
  "body": "Updated description",
  "base": "main",
  "assignees": ["reviewer1", "reviewer2"],
  "labels": [1, 2],
  "milestone": 1,
  "state": "closed",
  "due_date": "2024-12-31T23:59:59Z"
}

Merge Pull Request

POST /api/v1/repos/{owner}/{repo}/pulls/{index}/merge

Request Body:

{
  "Do": "merge",
  "MergeMessageField": "Merge pull request #1",
  "MergeTitleField": "Add new feature",
  "delete_branch_after_merge": true,
  "force_merge": false,
  "head_commit_id": "def456",
  "merge_when_checks_succeed": false
}

Parameters:

Parameter Type Required Description
Do string Yes Merge method: merge, rebase, rebase-merge, squash, manually-merged
MergeMessageField string No Merge commit message
MergeTitleField string No Merge commit title
delete_branch_after_merge boolean No Delete head branch after merge
force_merge boolean No Force merge even if checks fail
head_commit_id string No Expected head commit SHA

List PR Reviews

GET /api/v1/repos/{owner}/{repo}/pulls/{index}/reviews

Response:

[
  {
    "id": 1,
    "user": {
      "id": 2,
      "login": "reviewer"
    },
    "body": "Looks good to me!",
    "commit_id": "def456",
    "state": "APPROVED",
    "html_url": "https://gitcaddy.example.com/username/my-repo/pulls/1#pullrequestreview-1",
    "submitted_at": "2024-01-16T10:00:00Z"
  }
]

Create PR Review

POST /api/v1/repos/{owner}/{repo}/pulls/{index}/reviews

Request Body:

{
  "body": "Review comment",
  "event": "APPROVED",
  "comments": [
    {
      "path": "src/main.go",
      "body": "Consider refactoring this function",
      "old_position": 0,
      "new_position": 42
    }
  ]
}

Parameters:

Parameter Type Required Description
body string No Overall review comment
event string Yes Review state: APPROVED, REQUEST_CHANGES, COMMENT
comments array No Array of line comments

Submit PR Review

POST /api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}

Dismiss PR Review

POST /api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals

Request Body:

{
  "message": "Outdated review"
}

Request AI Code Review

POST /api/v1/repos/{owner}/{repo}/pulls/{index}/ai-review

Request Body:

{
  "provider": "openai",
  "model": "gpt-4"
}

Response:

{
  "review_id": 42,
  "status": "pending",
  "message": "AI review requested. Results will be posted as comments."
}

Organization APIs

List Organizations

GET /api/v1/orgs

Parameters:

Parameter Type Required Description
page integer No Page number
limit integer No Page size

Get Organization

GET /api/v1/orgs/{org}

Response:

{
  "id": 1,
  "username": "my-org",
  "full_name": "My Organization",
  "avatar_url": "https://gitcaddy.example.com/avatars/1",
  "description": "Organization description",
  "website": "https://example.com",
  "location": "San Francisco, CA",
  "visibility": "public",
  "repo_admin_change_team_access": false,
  "username": "my-org"
}

Create Organization

POST /api/v1/orgs

Request Body:

{
  "username": "new-org",
  "full_name": "New Organization",
  "description": "Organization description",
  "website": "https://example.com",
  "location": "San Francisco, CA",
  "visibility": "public",
  "repo_admin_change_team_access": false
}

Parameters:

Parameter Type Required Description
username string Yes Organization username
full_name string No Display name
description string No Organization description
website string No Website URL
location string No Location
visibility string No Visibility: public, limited, private

Edit Organization

PATCH /api/v1/orgs/{org}

Delete Organization

DELETE /api/v1/orgs/{org}

List Organization Teams

GET /api/v1/orgs/{org}/teams

Response:

[
  {
    "id": 1,
    "name": "core-team",
    "description": "Core development team",
    "organization": {
      "id": 1,
      "username": "my-org",
      "full_name": "My Organization"
    },
    "permission": "admin",
    "can_create_org_repo": true,
    "includes_all_repositories": false,
    "units": [
      "repo.code",
      "repo.issues",
      "repo.pulls",
      "repo.releases",
      "repo.wiki"
    ]
  }
]

Create Team

POST /api/v1/orgs/{org}/teams

Request Body:

{
  "name": "new-team",
  "description": "Team description",
  "permission": "write",
  "can_create_org_repo": false,
  "includes_all_repositories": false,
  "units": [
    "repo.code",
    "repo.issues",
    "repo.pulls"
  ]
}

Parameters:

Parameter Type Required Description
name string Yes Team name
description string No Team description
permission string No Permission level: read, write, admin
units array No Repository unit permissions

List Team Members

GET /api/v1/teams/{id}/members

Add Team Member

PUT /api/v1/teams/{id}/members/{username}

Remove Team Member

DELETE /api/v1/teams/{id}/members/{username}

List Team Repositories

GET /api/v1/teams/{id}/repos

Add Team Repository

PUT /api/v1/teams/{id}/repos/{org}/{repo}

User APIs

Get Authenticated User

GET /api/v1/user

Response:

{
  "id": 1,
  "login": "username",
  "full_name": "User Name",
  "email": "user@example.com",
  "avatar_url": "https://gitcaddy.example.com/avatars/1",
  "language": "en-US",
  "is_admin": false,
  "last_login": "2024-01-20T10:00:00Z",
  "created": "2023-01-01T00:00:00Z",
  "restricted": false,
  "active": true,
  "prohibit_login": false,
  "location": "San Francisco",
  "website": "https://example.com",
  "description": "User bio",
  "visibility": "public",
  "followers_count": 10,
  "following_count": 5,
  "starred_repos_count": 20
}

Get User

GET /api/v1/users/{username}

List User Emails

GET /api/v1/user/emails

Response:

[
  {
    "email": "user@example.com",
    "verified": true,
    "primary": true
  },
  {
    "email": "alternate@example.com",
    "verified": false,
    "primary": false
  }
]

Add Email

POST /api/v1/user/emails

Request Body:

{
  "emails": ["new@example.com"]
}

Delete Email

DELETE /api/v1/user/emails

Request Body:

{
  "emails": ["old@example.com"]
}

List SSH Keys

GET /api/v1/user/keys

Response:

[
  {
    "id": 1,
    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...",
    "url": "https://gitcaddy.example.com/api/v1/user/keys/1",
    "title": "Work Laptop",
    "fingerprint": "SHA256:abc123def456",
    "created_at": "2024-01-01T00:00:00Z",
    "read_only": false
  }
]

Add SSH Key

POST /api/v1/user/keys

Request Body:

{
  "title": "My Key",
  "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...",
  "read_only": false
}

Delete SSH Key

DELETE /api/v1/user/keys/{id}

List GPG Keys

GET /api/v1/user/gpg_keys

Response:

[
  {
    "id": 1,
    "primary_key_id": "",
    "key_id": "ABC123DEF456",
    "public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
    "emails": [
      {
        "email": "user@example.com",
        "verified": true
      }
    ],
    "subkeys": [],
    "can_sign": true,
    "can_encrypt_comms": true,
    "can_encrypt_storage": true,
    "can_certify": true,
    "created_at": "2024-01-01T00:00:00Z",
    "expires_at": "2025-01-01T00:00:00Z"
  }
]

Add GPG Key

POST /api/v1/user/gpg_keys

Request Body:

{
  "armored_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
}

List Starred Repositories

GET /api/v1/user/starred

Star Repository

PUT /api/v1/user/starred/{owner}/{repo}

Unstar Repository

DELETE /api/v1/user/starred/{owner}/{repo}

List Watched Repositories

GET /api/v1/user/subscriptions

Watch Repository

PUT /api/v1/repos/{owner}/{repo}/subscription

Unwatch Repository

DELETE /api/v1/repos/{owner}/{repo}/subscription

Package Registry APIs

GitCaddy supports multiple package formats with dedicated API endpoints.

List Packages

GET /api/v1/packages/{owner}

Parameters:

Parameter Type Required Description
type string No Package type: npm, maven, docker, pypi, alpine, etc.
q string No Search query
page integer No Page number
limit integer No Page size

Response:

[
  {
    "id": 1,
    "name": "my-package",
    "version": "1.0.0",
    "type": "npm",
    "owner": {
      "id": 1,
      "login": "username"
    },
    "repository": {
      "id": 1,
      "name": "my-repo",
      "full_name": "username/my-repo"
    },
    "creator": {
      "id": 1,
      "login": "username"
    },
    "html_url": "https://gitcaddy.example.com/username/-/packages/npm/my-package/1.0.0",
    "created_at": "2024-01-15T10:00:00Z"
  }
]

Get Package

GET /api/v1/packages/{owner}/{type}/{name}/{version}

Delete Package

DELETE /api/v1/packages/{owner}/{type}/{name}/{version}

NPM Package API

Configure npm registry:

npm config set registry https://gitcaddy.example.com/api/packages/{owner}/npm/
npm config set //gitcaddy.example.com/api/packages/{owner}/npm/:_authToken {token}

Publish package:

npm publish

Install package:

npm install @{owner}/{package}

Docker Registry API

Login:

docker login gitcaddy.example.com

Tag and push:

docker tag myimage gitcaddy.example.com/{owner}/{image}:{tag}
docker push gitcaddy.example.com/{owner}/{image}:{tag}

Pull:

docker pull gitcaddy.example.com/{owner}/{image}:{tag}

Maven Package API

Configure in pom.xml:

<repositories>
  <repository>
    <id>gitcaddy</id>
    <url>https://gitcaddy.example.com/api/packages/{owner}/maven</url>
  </repository>
</repositories>

<distributionManagement>
  <repository>
    <id>gitcaddy</id>
    <url>https://gitcaddy.example.com/api/packages/{owner}/maven</url>
  </repository>
</distributionManagement>

Configure authentication in ~/.m2/settings.xml:

<servers>
  <server>
    <id>gitcaddy</id>
    <username>{username}</username>
    <password>{token}</password>
  </server>
</servers>

PyPI Package API

Configure pip:

pip config set global.index-url https://gitcaddy.example.com/api/packages/{owner}/pypi/simple

Upload with twine:

twine upload --repository-url https://gitcaddy.example.com/api/packages/{owner}/pypi dist/*

Actions APIs

List Workflow Runs

GET /api/v1/repos/{owner}/{repo}/actions/runs

Parameters:

Parameter Type Required Description
status string No Filter by status: success, failure, waiting, running
event string No Filter by event: push, pull_request, schedule
actor string No Filter by actor username
branch string No Filter by branch
page integer No Page number
limit integer No Page size

Response:

{
  "total_count": 10,
  "workflow_runs": [
    {
      "id": 1,
      "name": "CI",
      "head_branch": "main",
      "head_sha": "abc123def456",
      "run_number": 42,
      "event": "push",
      "status": "completed",
      "conclusion": "success",
      "workflow_id": 1,
      "url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/actions/runs/1",
      "html_url": "https://gitcaddy.example.com/username/my-repo/actions/runs/1",
      "created_at": "2024-01-20T10:00:00Z",
      "updated_at": "2024-01-20T10:05:00Z",
      "run_started_at": "2024-01-20T10:00:30Z"
    }
  ]
}

Get Workflow Run

GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}

Cancel Workflow Run

POST /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/cancel

Rerun Workflow

POST /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/rerun

List Workflow Jobs

GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/jobs

Response:

{
  "total_count": 3,
  "jobs": [
    {
      "id": 1,
      "run_id": 1,
      "name": "build",
      "status": "completed",
      "conclusion": "success",
      "started_at": "2024-01-20T10:00:30Z",
      "completed_at": "2024-01-20T10:03:00Z",
      "url": "https://gitcaddy.example.com/api/v1/repos/username/my-repo/actions/jobs/1",
      "html_url": "https://gitcaddy.example.com/username/my-repo/actions/runs/1/jobs/1"
    }
  ]
}

Get Job Logs

GET /api/v1/repos/{owner}/{repo}/actions/jobs/{job_id}/logs

Response: Plain text log output

List Repository Secrets

GET /api/v1/repos/{owner}/{repo}/actions/secrets

Response:

{
  "total_count": 2,
  "secrets": [
    {
      "name": "API_KEY",
      "created_at": "2024-01-15T10:00:00Z",
      "updated_at": "2024-01-15T10:00:00Z"
    }
  ]
}

Create/Update Secret

PUT /api/v1/repos/{owner}/{repo}/actions/secrets/{secret_name}

Request Body:

{
  "value": "secret_value"
}

Delete Secret

DELETE /api/v1/repos/{owner}/{repo}/actions/secrets/{secret_name}

List Runners

GET /api/v1/repos/{owner}/{repo}/actions/runners

Response:

{
  "total_count": 1,
  "runners": [
    {
      "id": 1,
      "name": "runner-1",
      "os": "linux",
      "status": "online",
      "busy": false,
      "labels": [
        {
          "id": 1,
          "name": "ubuntu-latest",
          "type": "read-only"
        }
      ]
    }
  ]
}

Register Runner

POST /api/v1/repos/{owner}/{repo}/actions/runners/registration-token

Response:

{
  "token": "RUNNER_REGISTRATION_TOKEN",
  "expires_at": "2024-01-20T11:00:00Z"
}

Vault APIs

Enterprise-grade secrets management with encryption and audit logging.

List Vault Secrets

GET /api/v1/repos/{owner}/{repo}/vault/secrets

Response:

{
  "total_count": 5,
  "secrets": [
    {
      "id": "secret-id-123",
      "name": "database_password",
      "description": "Production database password",
      "version": 3,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-15T10:00:00Z",
      "created_by": {
        "id": 1,
        "login": "admin"
      },
      "last_accessed_at": "2024-01-20T09:30:00Z",
      "access_count": 42
    }
  ]
}

Create Vault Secret

POST /api/v1/repos/{owner}/{repo}/vault/secrets

Request Body:

{
  "name": "api_key",
  "description": "External API key",
  "value": "secret_value_encrypted",
  "metadata": {
    "environment": "production",
    "service": "payment-gateway"
  }
}

Response: 201 Created

{
  "id": "secret-id-456",
  "name": "api_key",
  "version": 1,
  "created_at": "2024-01-20T10:00:00Z"
}

Get Vault Secret

GET /api/v1/repos/{owner}/{repo}/vault/secrets/{secret_id}

Parameters:

Parameter Type Required Description
version integer No Specific version (default: latest)

Response:

{
  "id": "secret-id-456",
  "name": "api_key",
  "description": "External API key",
  "value": "decrypted_secret_value",
  "version": 1,
  "created_at": "2024-01-20T10:00:00Z",
  "metadata": {
    "environment": "production",
    "service": "payment-gateway"
  }
}

Update Vault Secret

PUT /api/v1/repos/{owner}/{repo}/vault/secrets/{secret_id}

Request Body:

{
  "value": "new_secret_value",
  "description": "Updated description"
}

Response:

{
  "id": "secret-id-456",
  "version": 2,
  "updated_at": "2024-01-20T11:00:00Z"
}

Delete Vault Secret

DELETE /api/v1/repos/{owner}/{repo}/vault/secrets/{secret_id}

Response: 204 No Content

List Secret Versions

GET /api/v1/repos/{owner}/{repo}/vault/secrets/{secret_id}/versions

Response:

{
  "total_count": 3,
  "versions": [
    {
      "version": 3,
      "created_at": "2024-01-20T10:00:00Z",
      "created_by": {
        "id": 1,
        "login": "admin"
      }
    },
    {
      "version": 2,
      "created_at": "2024-01-15T10:00:00Z",
      "created_by": {
        "id": 1,
        "login": "admin"
      }
    }
  ]
}

Get Vault Audit Log

GET /api/v1/repos/{owner}/{repo}/vault/audit

Parameters:

Parameter Type Required Description
secret_id string No Filter by secret ID
action string No Filter by action: create, read, update, delete
user string No Filter by username
since string No Start date (ISO 8601)
before string No End date (ISO 8601)
page integer No Page number
limit integer No Page size

Response:

{
  "total_count": 100,
  "entries": [
    {
      "id": "audit-log-123",
      "secret_id": "secret-id-456",
      "secret_name": "api_key",
      "action": "read",
      "user": {
        "id": 2,
        "login": "developer"
      },
      "ip_address": "192.168.1.100",
      "user_agent": "GitCaddy-Actions/1.0",
      "timestamp": "2024-01-20T10:30:00Z",
      "success": true
    }
  ]
}

Generate CI/CD Token

POST /api/v1/repos/{owner}/{repo}/vault/tokens

Request Body:

{
  "name": "ci-token",
  "secret_ids": ["secret-id-456", "secret-id-789"],
  "expires_at": "2024-12-31T23:59:59Z",
  "read_only": true
}

Response:

{
  "token": "vault_token_abc123def456",
  "expires_at": "2024-12-31T23:59:59Z"
}

Git Protocol

GitCaddy supports standard Git protocols for repository operations.

HTTP(S) Protocol

Clone:

git clone https://gitcaddy.example.com/username/repo.git

Authentication:

Use personal access token as password:

git clone https://username:token@gitcaddy.example.com/username/repo.git

Or configure credential helper:

git config --global credential.helper store
git clone https://gitcaddy.example.com/username/repo.git
# Enter username and token when prompted

SSH Protocol

Clone:

git clone git@gitcaddy.example.com:username/repo.git

Setup:

  1. Generate SSH key:
ssh-keygen -t ed25519 -C "your_email@example.com"
  1. Add public key to GitCaddy:

    • User Settings → SSH/GPG Keys → Add Key
    • Paste contents of ~/.ssh/id_ed25519.pub
  2. Configure SSH:

# ~/.ssh/config
Host gitcaddy.example.com
  User git
  IdentityFile ~/.ssh/id_ed25519

Git LFS

Enable LFS for repository:

git lfs install
git lfs track "*.psd"
git add .gitattributes
git commit -m "Enable LFS for PSD files"

Configuration:

GitCaddy automatically handles LFS objects when pushing/pulling.

LFS API Endpoints:

POST /api/v1/repos/{owner}/{repo}/lfs/objects/batch

Request Body:

{
  "operation": "upload",
  "transfers": ["basic"],
  "objects": [
    {
      "oid": "abc123def456...",
      "size": 1048576
    }
  ]
}

WebSocket APIs

GitCaddy uses WebSocket connections for real-time features.

EventSource (Server-Sent Events)

Time Tracking Updates:

const eventSource = new EventSource('/user/events');

eventSource.addEventListener('timetrack', (event) => {
  const data = JSON.parse(event.data);
  console.log('Time tracking update:', data);
  // Update stopwatch UI
});

eventSource.addEventListener('error', (error) => {
  console.error('EventSource error:', error);
});

Notification Updates:

const notificationSource = new EventSource('/api/v1/notifications/events');

notificationSource.addEventListener('notification', (event) => {
  const notification = JSON.parse(event.data);
  console.log('New notification:', notification);
  // Display notification toast
});

SharedWorker

Shared stopwatch across tabs:

// Initialize shared worker for time tracking
const worker = new SharedWorker('/assets/js/stopwatch-worker.js');

worker.port.start();

worker.port.postMessage({
  action: 'start',
  issueId: 123
});

worker.port.onmessage = (event) => {
  const { action, elapsed } = event.data;
  if (action === 'tick') {
    updateStopwatchDisplay(elapsed);
  }
};

Frontend JavaScript APIs

GitCaddy provides modular JavaScript APIs for frontend functionality.

Core Utilities

DOM Manipulation

import {$, $$} from './modules/utils/dom.js';

// Select single element
const button = $('#submit-button');

// Select multiple elements
const listItems = $$('.list-item');

// Event delegation
$(document).on('click', '.dynamic-button', (event) => {
  console.log('Button clicked:', event.target);
});

HTML Escaping

import {htmlEscape} from './modules/utils/html.js';

const userInput = '<script>alert("xss")</script>';
const safe = htmlEscape(userInput);
// Result: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;

Date/Time Utilities

import {parseISODateTime, formatDatetime} from './modules/utils/time.js';

const date = parseISODateTime('2024-01-20T10:00:00Z');
const formatted = formatDatetime(date, 'short'); // "Jan 20, 2024"

Color Utilities

import {useLightTextOnBackground} from './modules/utils/color.js';

const bgColor = '#ff0000';
const useLight = useLightTextOnBackground(bgColor);
// Returns true if light text should be used on this background

Glob Pattern Matching

import {matchGlobPattern} from './modules/utils/glob.js';

const pattern = '*.js';
const filename = 'script.js';
const matches = matchGlobPattern(pattern, filename); // true

DOM Manipulation

Creating Elements

import {createElementFromHTML} from './modules/utils/dom.js';

const html = '<div class="alert">Message</div>';
const element = createElementFromHTML(html);
document.body.appendChild(element);

Toggle Classes

import {toggleClass} from './modules/utils/dom.js';

const element = $('#my-element');
toggleClass(element, 'active', true); // Add class
toggleClass(element, 'hidden', false); // Remove class

Web Components

Absolute Date Component

<absolute-date date="2024-01-20T10:00:00Z" format="short"></absolute-date>

JavaScript API:

const dateElement = document.querySelector('absolute-date');
dateElement.setAttribute('date', '2024-01-21T12:00:00Z');

Origin URL Component

<origin-url data-url="https://example.com/page"></origin-url>

Displays sanitized origin URL with proper formatting.

Overflow Menu Component

<overflow-menu>
  <button class="item">Action 1</button>
  <button class="item">Action 2</button>
  <button class="item">Action 3</button>
</overflow-menu>

Automatically creates overflow menu for items that don't fit.

Vue Components

File Tree Component

import {createApp} from 'vue';
import FileTreeView from './modules/features/repo-file-tree.js';

const app = createApp({
  components: {
    FileTreeView
  },
  data() {
    return {
      files: [
        {name: 'src', type: 'dir', children: [...]},
        {name: 'README.md', type: 'file'}
      ]
    };
  }
});

app.mount('#file-tree');

Template:

<file-tree-view :files="files" @file-click="handleFileClick"></file-tree-view>

Context Popup Component

import {createContextPopup} from './modules/features/context-popup.js';

const popup = createContextPopup({
  items: [
    {label: 'Edit', action: () => console.log('Edit')},
    {label: 'Delete', action: () => console.log('Delete')}
  ],
  position: {x: event.clientX, y: event.clientY}
});

popup.show();

Tooltip System

import {createTippy} from './modules/features/tippy.js';

// Simple tooltip
createTippy('#my-element', {
  content: 'Tooltip text'
});

// HTML content
createTippy('.help-icon', {
  content: '<strong>Help:</strong> This is help text',
  allowHTML: true
});

// Interactive tooltip
createTippy('.interactive', {
  content: document.querySelector('#tooltip-content'),
  interactive: true,
  trigger: 'click'
});

Toast Notifications

import {showToast} from './modules/features/toast.js';

// Success toast
showToast('Operation completed successfully', 'success');

// Error toast
showToast('An error occurred', 'error');

// Warning toast
showToast('Please review your changes', 'warning');

// Info toast
showToast('New version available', 'info');

// Custom duration
showToast('Auto-closing in 5 seconds', 'info', 5000);

Markdown Rendering

import {renderMarkdown} from './modules/features/markup.js';

const markdown = '# Heading\n\nParagraph with **bold** text.';
const html = await renderMarkdown(markdown);
document.querySelector('#content').innerHTML = html;

Features:

  • GitHub-flavored markdown
  • Syntax highlighting
  • Math rendering (KaTeX)
  • Mermaid diagrams
  • Task lists
  • Emoji support

Mermaid Diagrams

import {renderMermaid} from './modules/features/mermaid.js';

const diagramCode = `
graph TD
  A[Start] --> B[Process]
  B --> C[End]
`;

await renderMermaid('#diagram-container', diagramCode);

SortableJS Integration

import Sortable from 'sortablejs';

const projectBoard = document.querySelector('#project-board');

Sortable.create(projectBoard, {
  animation: 150,
  handle: '.card-handle',
  onEnd: (event) => {
    const cardId = event.item.dataset.cardId;
    const newPosition = event.newIndex;
    
    // Update card position via API
    fetch(`/api/v1/projects/cards/${cardId}/move`, {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({position: newPosition})
    });
  }
});

Error Codes

GitCaddy uses standard HTTP status codes with detailed error responses.

HTTP Status Codes

Code Status Description
200 OK Request succeeded
201 Created Resource created successfully
204 No Content Request succeeded with no response body
400 Bad Request Invalid request parameters
401 Unauthorized Authentication required
403 Forbidden Insufficient permissions
404 Not Found Resource not found
409 Conflict Resource conflict (e.g., duplicate name)
422 Unprocessable Entity Validation error
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Server error
503 Service Unavailable Service temporarily unavailable

Error Response Format

{
  "message": "Human-readable error message",
  "errors": [
    {
      "field": "email",
      "message": "Email is required"
    }
  ],
  "url": "https://docs.gitcaddy.example.com/api/errors/validation"
}

Common Error Codes

Authentication Errors:

{
  "message": "Unauthorized: Invalid or expired token",
  "code": "INVALID_TOKEN"
}

Validation Errors:

{
  "message": "Validation failed",
  "errors": [
    {
      "field": "name",
      "message": "Repository name already exists"
    },
    {
      "field": "description",
      "message": "Description must be less than 500 characters"
    }
  ]
}

Permission Errors:

{
  "message": "Forbidden: You do not have permission to perform this action",
  "required_permission": "admin"
}

Rate Limit Errors:

{
  "message": "API rate limit exceeded",
  "limit": 5000,
  "remaining": 0,
  "reset": "2024-01-20T11:00:00Z"
}

Resource Not Found:

{
  "message": "Repository not found",
  "resource": "repository",
  "id": "username/nonexistent-repo"
}

Rate Limiting

GitCaddy implements rate limiting to prevent abuse.

Rate Limit Headers

All API responses include rate limit information:

X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
X-RateLimit-Reset: 1705750800
Header Description
X-RateLimit-Limit Maximum requests per hour
X-RateLimit-Remaining Remaining requests in current window
X-RateLimit-Reset Unix timestamp when limit resets

Rate Limits by Authentication

Authentication Requests/Hour
Unauthenticated 60
Authenticated (PAT) 5,000
OAuth Application 5,000
Enterprise License 15,000

Handling Rate Limits

async function makeAPIRequest(url) {
  const response = await fetch(url, {
    headers: {
      'Authorization': 'token YOUR_TOKEN'
    }
  });
  
  const remaining = response.headers.get('X-RateLimit-Remaining');
  const reset = response.headers.get('X-RateLimit-Reset');
  
  if (response.status === 429) {
    const resetDate = new Date(reset * 1000);
    const waitTime = resetDate - new Date();
    
    console.log(`Rate limited. Retry after ${waitTime}ms`);
    await new Promise(resolve => setTimeout(resolve, waitTime));
    return makeAPIRequest(url); // Retry
  }
  
  if (remaining < 100) {
    console.warn(`Low rate limit: ${remaining} requests remaining`);
  }
  
  return response.json();
}

Webhooks

GitCaddy can send HTTP POST requests to external URLs when events occur.

Creating Webhooks

Via API:

POST /api/v1/repos/{owner}/{repo}/hooks

Request Body:

{
  "type": "gitea",
  "config": {
    "url": "https://example.com/webhook",
    "content_type": "json",
    "secret": "webhook_secret"
  },
  "events": [
    "push",
    "pull_request",
    "issues",
    "issue_comment",
    "release"
  ],
  "active": true
}

Parameters:

Parameter Type Required Description
type string Yes Webhook type: gitea, gogs, slack, discord
config.url string Yes Webhook URL
config.content_type string No Content type: json, form
config.secret string No Secret for signature verification
events array Yes Events to trigger webhook
active boolean No Enable webhook (default: true)

Webhook Events

Event Description
push Git push to repository
create Branch or tag created
delete Branch or tag deleted
fork Repository forked
issues Issue opened, closed, edited
issue_comment Comment on issue
pull_request PR opened, closed, merged, edited
pull_request_review PR review submitted
pull_request_review_comment Comment on PR review
release Release published
repository Repository created, deleted
wiki Wiki page created, edited

Webhook Payload

Push Event:

{
  "ref": "refs/heads/main",
  "before": "abc123def456",
  "after": "def456ghi789",
  "compare_url": "https://gitcaddy.example.com/username/repo/compare/abc123...def456",
  "commits": [
    {
      "id": "def456ghi789",
      "message": "Fix bug in authentication",
      "url": "https://gitcaddy.example.com/username/repo/commit/def456ghi789",
      "author": {
        "name": "Developer",
        "email": "dev@example.com",
        "username": "developer"
      },
      "committer": {
        "name": "Developer",
        "email": "dev@example.com",
        "username": "developer"
      },
      "timestamp": "2024-01-20T10:00:00Z",
      "added": ["new-file.js"],
      "removed": [],
      "modified": ["existing-file.js"]
    }
  ],
  "repository": {
    "id": 1,
    "name": "repo",
    "full_name": "username/repo",
    "html_url": "https://gitcaddy.example.com/username/repo",
    "private": false,
    "owner": {
      "id": 1,
      "login": "username",
      "full_name": "User Name"
    }
  },
  "pusher": {
    "id": 1,
    "login": "username",
    "full_name": "User Name"
  },
  "sender": {
    "id": 1,
    "login": "username",
    "full_name": "User Name"
  }
}

Pull Request Event:

{
  "action": "opened",
  "number": 1,
  "pull_request": {
    "id": 1,
    "number": 1,
    "user": {
      "id": 2,
      "login": "contributor"
    },
    "title": "Add new feature",
    "body": "Description of changes",
    "state": "open",
    "html_url": "https://gitcaddy.example.com/username/repo/pulls/1",
    "diff_url": "https://gitcaddy.example.com/username/repo/pulls/1.diff",
    "patch_url": "https://gitcaddy.example.com/username/repo/pulls/1.patch",
    "merged": false,
    "mergeable": true,
    "base": {
      "label": "main",
      "ref": "main",
      "sha": "abc123"
    },
    "head": {
      "label": "contributor:feature",
      "ref": "feature",
      "sha": "def456"
    },
    "created_at": "2024-01-20T10:00:00Z",
    "updated_at": "2024-01-20T10:00:00Z"
  },
  "repository": {
    "id": 1,
    "name": "repo",
    "full_name": "username/repo"
  },
  "sender": {
    "id": 2,
    "login": "contributor"
  }
}

Webhook Security

Signature Verification:

GitCaddy signs webhook payloads with HMAC-SHA256 using the configured secret.

X-Gitea-Signature: sha256=abc123def456...

Verification Example (Node.js):

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  const calculatedSignature = 'sha256=' + hmac.digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(calculatedSignature)
  );
}

// Express middleware
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-gitea-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  res.status(200).send('OK');
});

Testing Webhooks

Manual Test:

curl -X POST https://gitcaddy.example.com/api/v1/repos/username/repo/hooks/1/test \
  -H "Authorization: token YOUR_TOKEN"

View Webhook Deliveries:

GET /api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries

Response:

[
  {
    "id": 1,
    "uuid": "abc-123-def-456",
    "url": "https://example.com/webhook",
    "request": {
      "headers": {
        "Content-Type": "application/json",
        "X-Gitea-Event": "push",
        "X-Gitea-Signature": "sha256=..."
      },
      "body": "{...}"
    },
    "response": {
      "status": 200,
      "headers": {},
      "body": "OK"
    },
    "delivered_at": "2024-01-20T10:00:00Z",
    "duration": 0.123
  }
]

Code Examples

Complete Repository Workflow

// Create repository
async function createRepository(name, description) {
  const response = await fetch('https://gitcaddy.example.com/api/v1/user/repos', {
    method: 'POST',
    headers: {
      'Authorization': 'token YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: name,
      description: description,
      private: false,
      auto_init: true,
      license: 'MIT'
    })
  });
  
  return response.json();
}

// Add file to repository
async function addFile(owner, repo, path, content, message) {
  const encodedContent = btoa(content);
  
  const response = await fetch(
    `https://gitcaddy.example.com/api/v1/repos/${owner}/${repo}/contents/${path}`,
    {
      method: 'POST',
      headers: {
        'Authorization': 'token YOUR_TOKEN',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        content: encodedContent,
        message: message,
        branch: 'main'
      })
    }
  );
  
  return response.json();
}

// Create pull request
async function createPullRequest(owner, repo, title, head, base) {
  const response = await fetch(
    `https://gitcaddy.example.com/api/v1/repos/${owner}/${repo}/pulls`,
    {
      method: 'POST',
      headers: {
        'Authorization': 'token YOUR_TOKEN',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        title: title,
        head: head,
        base: base,
        body: 'Please review these changes'
      })
    }
  );
  
  return response.json();
}

// Usage
(async () => {
  const repo = await createRepository('my-project', 'My awesome project');
  console.log('Repository created:', repo.html_url);
  
  const file = await addFile(
    'username',
    'my-project',
    'src/index.js',
    'console.log("Hello World");',
    'Add index.js'
  );
  console.log('File added:', file.commit.sha);
  
  const pr = await createPullRequest(
    'username',
    'my-project',
    'Add feature',
    'feature-branch',
    'main'
  );
  console.log('Pull request created:', pr.html_url);
})();

Issue Management

import requests

class GitCaddyClient:
    def __init__(self, base_url, token):
        self.base_url = base_url
        self.headers = {
            'Authorization': f'token {token}',
            'Content-Type': 'application/json'
        }
    
    def create_issue(self, owner, repo, title, body, labels=None):
        url = f'{self.base_url}/api/v1/repos/{owner}/{repo}/issues'
        data = {
            'title': title,
            'body': body
        }
        if labels:
            data['labels'] = labels
        
        response = requests.post(url, json=data, headers=self.headers)
        return response.json()
    
    def add_comment(self, owner, repo, issue_number, comment):
        url = f'{self.base_url}/api/v1/repos/{owner}/{repo}/issues/{issue_number}/comments'
        data = {'body': comment}
        
        response = requests.post(url, json=data, headers=self.headers)
        return response.json()
    
    def close_issue(self, owner, repo, issue_number):
        url = f'{self.base_url}/api/v1/repos/{owner}/{repo}/issues/{issue_number}'
        data = {'state': 'closed'}
        
        response = requests.patch(url, json=data, headers=self.headers)
        return response.json()
    
    def track_time(self, owner, repo, issue_number, seconds):
        url = f'{self.base_url}/api/v1/repos/{owner}/{repo}/issues/{issue_number}/times'
        data = {'time': seconds}
        
        response = requests.post(url, json=data, headers=self.headers)
        return response.json()

# Usage
client = GitCaddyClient('https://gitcaddy.example.com', 'YOUR_TOKEN')

# Create issue
issue = client.create_issue(
    'username',
    'my-repo',
    'Bug: Login not working',
    'Users cannot log in with correct credentials',
    labels=[1, 2]  # bug, high-priority
)
print(f'Issue created: #{issue["number"]}')

# Add comment
comment = client.add_comment(
    'username',
    'my-repo',
    issue['number'],
    'Investigating the issue...'
)

# Track time (2 hours)
time_entry = client.track_time('username', 'my-repo', issue['number'], 7200)
print(f'Tracked {time_entry["time"]}s')

# Close issue
closed = client.close_issue('username', 'my-repo', issue['number'])
print(f'Issue closed: {closed["state"]}')

CI/CD Integration

# .gitcaddy/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test
      
      - name: Get secrets from Vault
        id: vault
        uses: actions/vault-secrets@v1
        with:
          secrets: |
            database_password
            api_key
      
      - name: Deploy
        if: github.ref == 'refs/heads/main'
        run: |
          echo "Deploying with API key: ${{ steps.vault.outputs.api_key }}"
          npm run deploy
        env:
          DATABASE_PASSWORD: ${{ steps.vault.outputs.database_password }}
          API_KEY: ${{ steps.vault.outputs.api_key }}

Webhook Handler

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

type PushEvent struct {
    Ref        string `json:"ref"`
    Before     string `json:"before"`
    After      string `json:"after"`
    Repository struct {
        Name     string `json:"name"`
        FullName string `json:"full_name"`
    } `json:"repository"`
    Pusher struct {
        Login string `json:"login"`
    } `json:"pusher"`
    Commits []struct {
        ID      string `json:"id"`
        Message string `json:"message"`
        Author  struct {
            Name  string `json:"name"`
            Email string `json:"email"`
        } `json:"author"`
    } `json:"commits"`
}

func verifySignature(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expectedMAC := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expectedMAC))
}

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    payload, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Error reading request body", http.StatusBadRequest)
        return
    }
    defer r.Body.Close()
    
    signature := r.Header.Get("X-Gitea-Signature")
    if !verifySignature(payload, signature, "webhook_secret") {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }
    
    event := r.Header.Get("X-Gitea-Event")
    
    switch event {
    case "push":
        var pushEvent PushEvent
        if err := json.Unmarshal(payload, &pushEvent); err != nil {
            http.Error(w, "Error parsing JSON", http.StatusBadRequest)
            return
        }
        
        log.Printf("Push to %s by %s", pushEvent.Repository.FullName, pushEvent.Pusher.Login)
        log.Printf("Commits: %d", len(pushEvent.Commits))
        
        for _, commit := range pushEvent.Commits {
            log.Printf("  - %s: %s", commit.ID[:7], commit.Message)
        }
        
    case "pull_request":
        log.Printf("Pull request event received")
        
    default:
        log.Printf("Unknown event: %s", event)
    }
    
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Webhook processed")
}

func main() {
    http.HandleFunc("/webhook", handleWebhook)
    log.Println("Webhook server listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Package Publishing

#!/bin/bash
# publish-npm.sh - Publish npm package to GitCaddy registry

GITCADDY_URL="https://gitcaddy.example.com"
OWNER="username"
TOKEN="YOUR_TOKEN"

# Configure npm
npm config set registry "${GITCADDY_URL}/api/packages/${OWNER}/npm/"
npm config set "//${GITCADDY_URL#https://}/api/packages/${OWNER}/npm/:_authToken" "${TOKEN}"

# Update version
npm version patch

# Publish
npm publish

echo "Package published to GitCaddy registry"
# Dockerfile - Build and push Docker image to GitCaddy registry
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

# Build and push:
# docker build -t gitcaddy.example.com/username/myapp:latest .
# docker login gitcaddy.example.com
# docker push gitcaddy.example.com/username/myapp:latest

For additional support and documentation, visit the GitCaddy Documentation or join the community forum.