diff --git a/.gitignore b/.gitignore
index 90d70c085..4cbc9626d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,7 @@
 *.so
 *.dylib
 vendor/
-__debug_bin
+__debug_bin*
 
 # Test binary, built with `go test -c`
 *.test
diff --git a/woodpecker-go/woodpecker/agent.go b/woodpecker-go/woodpecker/agent.go
new file mode 100644
index 000000000..81ff8883c
--- /dev/null
+++ b/woodpecker-go/woodpecker/agent.go
@@ -0,0 +1,50 @@
+package woodpecker
+
+import "fmt"
+
+const (
+	pathAgents     = "%s/api/agents"
+	pathAgent      = "%s/api/agents/%d"
+	pathAgentTasks = "%s/api/agents/%d/tasks"
+)
+
+// AgentCreate creates a new agent.
+func (c *client) AgentCreate(in *Agent) (*Agent, error) {
+	out := new(Agent)
+	uri := fmt.Sprintf(pathAgents, c.addr)
+	return out, c.post(uri, in, out)
+}
+
+// AgentList returns a list of all registered agents.
+func (c *client) AgentList() ([]*Agent, error) {
+	out := make([]*Agent, 0, 5)
+	uri := fmt.Sprintf(pathAgents, c.addr)
+	return out, c.get(uri, &out)
+}
+
+// Agent returns an agent by id.
+func (c *client) Agent(agentID int64) (*Agent, error) {
+	out := new(Agent)
+	uri := fmt.Sprintf(pathAgent, c.addr, agentID)
+	return out, c.get(uri, out)
+}
+
+// AgentUpdate updates the agent with the provided Agent struct.
+func (c *client) AgentUpdate(in *Agent) (*Agent, error) {
+	out := new(Agent)
+	uri := fmt.Sprintf(pathAgent, c.addr, in.ID)
+	return out, c.patch(uri, in, out)
+}
+
+// AgentDelete deletes the agent with the given id.
+func (c *client) AgentDelete(agentID int64) error {
+	uri := fmt.Sprintf(pathAgent, c.addr, agentID)
+	return c.delete(uri)
+}
+
+// AgentTasksList returns a list of all tasks for the agent with the given id.
+func (c *client) AgentTasksList(agentID int64) ([]*Task, error) {
+	out := make([]*Task, 0, 5)
+	uri := fmt.Sprintf(pathAgentTasks, c.addr, agentID)
+	return out, c.get(uri, &out)
+}
diff --git a/woodpecker-go/woodpecker/agent_test.go b/woodpecker-go/woodpecker/agent_test.go
new file mode 100644
index 000000000..d424336d8
--- /dev/null
+++ b/woodpecker-go/woodpecker/agent_test.go
@@ -0,0 +1,511 @@
+package woodpecker
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestClient_AgentCreate(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		input    *Agent
+		expected *Agent
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPost {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusCreated)
+				_, err := fmt.Fprint(w, `{"id":1,"name":"new_agent","backend":"local","capacity":2,"version":"1.0.0"}`)
+				assert.NoError(t, err)
+			},
+			input:    &Agent{Name: "new_agent", Backend: "local", Capacity: 2, Version: "1.0.0"},
+			expected: &Agent{ID: 1, Name: "new_agent", Backend: "local", Capacity: 2, Version: "1.0.0"},
+			wantErr:  false,
+		},
+		{
+			name: "invalid input",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPost {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusBadRequest)
+			},
+			input:    &Agent{},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPost {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			input:    &Agent{Name: "new_agent", Backend: "local", Capacity: 2, Version: "1.0.0"},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			agent, err := client.AgentCreate(tt.input)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, agent, tt.expected)
+		})
+	}
+}
+
+func TestClient_AgentList(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		expected []*Agent
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `[
+					{
+						"id": 1,
+						"name": "agent-1",
+						"backend": "local",
+						"capacity": 2,
+						"version": "1.0.0"
+					},
+					{
+						"id": 2,
+						"name": "agent-2",
+						"backend": "kubernetes",
+						"capacity": 4,
+						"version": "1.0.0"
+					}
+				]`)
+				assert.NoError(t, err)
+			},
+			expected: []*Agent{
+				{
+					ID:       1,
+					Name:     "agent-1",
+					Backend:  "local",
+					Capacity: 2,
+					Version:  "1.0.0",
+				},
+				{
+					ID:       2,
+					Name:     "agent-2",
+					Backend:  "kubernetes",
+					Capacity: 4,
+					Version:  "1.0.0",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "invalid response",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `invalid json`)
+				assert.NoError(t, err)
+			},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			agents, err := client.AgentList()
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, agents)
+		})
+	}
+}
+
+func TestClient_Agent(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		agentID  int64
+		expected *Agent
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `{"id":1,"name":"agent-1","backend":"local","capacity":2,"version":"1.0.0"}`)
+				assert.NoError(t, err)
+			},
+			agentID:  1,
+			expected: &Agent{ID: 1, Name: "agent-1", Backend: "local", Capacity: 2, Version: "1.0.0"},
+			wantErr:  false,
+		},
+		{
+			name: "not found",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusNotFound)
+			},
+			agentID:  999,
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			agentID:  1,
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "invalid response",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `invalid json`)
+				assert.NoError(t, err)
+			},
+			agentID:  1,
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			agent, err := client.Agent(tt.agentID)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, agent)
+		})
+	}
+}
+
+func TestClient_AgentUpdate(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		input    *Agent
+		expected *Agent
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `{"id":1,"name":"updated_agent"}`)
+				assert.NoError(t, err)
+			},
+			input:    &Agent{ID: 1, Name: "existing_agent"},
+			expected: &Agent{ID: 1, Name: "updated_agent"},
+			wantErr:  false,
+		},
+		{
+			name: "not found",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusNotFound)
+			},
+			input:    &Agent{ID: 999, Name: "nonexistent_agent"},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "invalid input",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusBadRequest)
+			},
+			input:    &Agent{},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			input:    &Agent{ID: 1, Name: "existing_agent"},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			agent, err := client.AgentUpdate(tt.input)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, agent, tt.expected)
+		})
+	}
+}
+
+func TestClient_AgentDelete(t *testing.T) {
+	tests := []struct {
+		name    string
+		handler http.HandlerFunc
+		agentID int64
+		wantErr bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodDelete {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+			},
+			agentID: 1,
+			wantErr: false,
+		},
+		{
+			name: "not found",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodDelete {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusNotFound)
+			},
+			agentID: 999,
+			wantErr: true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodDelete {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			agentID: 1,
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			err := client.AgentDelete(tt.agentID)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+		})
+	}
+}
+
+func TestClient_AgentTasksList(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		agentID  int64
+		expected []*Task
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `[
+					{
+						"id": "4696",
+						"data": "",
+						"labels": {
+							"platform": "linux/amd64",
+							"repo": "woodpecker-ci/woodpecker"
+						}
+					},
+					{
+						"id": "4697",
+						"data": "",
+						"labels": {
+							"platform": "linux/arm64",
+							"repo": "woodpecker-ci/woodpecker"
+						}
+					}
+				]`)
+				assert.NoError(t, err)
+			},
+			agentID: 1,
+			expected: []*Task{
+				{
+					ID:   "4696",
+					Data: []byte{},
+					Labels: map[string]string{
+						"platform": "linux/amd64",
+						"repo":     "woodpecker-ci/woodpecker",
+					},
+				},
+				{
+					ID:   "4697",
+					Data: []byte{},
+					Labels: map[string]string{
+						"platform": "linux/arm64",
+						"repo":     "woodpecker-ci/woodpecker",
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "not found",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusNotFound)
+			},
+			agentID:  999,
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			agentID:  1,
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "invalid response",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `invalid json`)
+				assert.NoError(t, err)
+			},
+			agentID:  1,
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			tasks, err := client.AgentTasksList(tt.agentID)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, tasks)
+		})
+	}
+}
diff --git a/woodpecker-go/woodpecker/client.go b/woodpecker-go/woodpecker/client.go
index d22622d9e..f2c385ebe 100644
--- a/woodpecker-go/woodpecker/client.go
+++ b/woodpecker-go/woodpecker/client.go
@@ -26,41 +26,8 @@ import (
 )
 
 const (
-	pathSelf           = "%s/api/user"
-	pathRepos          = "%s/api/user/repos"
-	pathRepoPost       = "%s/api/repos?forge_remote_id=%d"
-	pathRepo           = "%s/api/repos/%d"
-	pathRepoLookup     = "%s/api/repos/lookup/%s"
-	pathRepoMove       = "%s/api/repos/%d/move?to=%s"
-	pathChown          = "%s/api/repos/%d/chown"
-	pathRepair         = "%s/api/repos/%d/repair"
-	pathPipelines      = "%s/api/repos/%d/pipelines"
-	pathPipeline       = "%s/api/repos/%d/pipelines/%v"
-	pathPipelineLogs   = "%s/api/repos/%d/logs/%d"
-	pathStepLogs       = "%s/api/repos/%d/logs/%d/%d"
-	pathApprove        = "%s/api/repos/%d/pipelines/%d/approve"
-	pathDecline        = "%s/api/repos/%d/pipelines/%d/decline"
-	pathStop           = "%s/api/repos/%d/pipelines/%d/cancel"
-	pathRepoSecrets    = "%s/api/repos/%d/secrets"
-	pathRepoSecret     = "%s/api/repos/%d/secrets/%s"
-	pathRepoRegistries = "%s/api/repos/%d/registry"
-	pathRepoRegistry   = "%s/api/repos/%d/registry/%s"
-	pathRepoCrons      = "%s/api/repos/%d/cron"
-	pathRepoCron       = "%s/api/repos/%d/cron/%d"
-	pathOrg            = "%s/api/orgs/%d"
-	pathOrgLookup      = "%s/api/orgs/lookup/%s"
-	pathOrgSecrets     = "%s/api/orgs/%d/secrets"
-	pathOrgSecret      = "%s/api/orgs/%d/secrets/%s"
-	pathGlobalSecrets  = "%s/api/secrets"
-	pathGlobalSecret   = "%s/api/secrets/%s"
-	pathUsers          = "%s/api/users"
-	pathUser           = "%s/api/users/%s"
-	pathPipelineQueue  = "%s/api/pipelines"
-	pathQueue          = "%s/api/queue"
-	pathLogLevel       = "%s/api/log-level"
-	pathAgents         = "%s/api/agents"
-	pathAgent          = "%s/api/agents/%d"
-	pathAgentTasks     = "%s/api/agents/%d/tasks"
+	pathLogLevel = "%s/api/log-level"
+
 	// TODO: implement endpoints
 	// pathFeed           = "%s/api/user/feed"
 	// pathVersion        = "%s/version"
@@ -91,422 +58,6 @@ func (c *client) SetAddress(addr string) {
 	c.addr = addr
 }
 
-// Self returns the currently authenticated user.
-func (c *client) Self() (*User, error) {
-	out := new(User)
-	uri := fmt.Sprintf(pathSelf, c.addr)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// User returns a user by login.
-func (c *client) User(login string) (*User, error) {
-	out := new(User)
-	uri := fmt.Sprintf(pathUser, c.addr, login)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// UserList returns a list of all registered users.
-func (c *client) UserList() ([]*User, error) {
-	var out []*User
-	uri := fmt.Sprintf(pathUsers, c.addr)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// UserPost creates a new user account.
-func (c *client) UserPost(in *User) (*User, error) {
-	out := new(User)
-	uri := fmt.Sprintf(pathUsers, c.addr)
-	err := c.post(uri, in, out)
-	return out, err
-}
-
-// UserPatch updates a user account.
-func (c *client) UserPatch(in *User) (*User, error) {
-	out := new(User)
-	uri := fmt.Sprintf(pathUser, c.addr, in.Login)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-// UserDel deletes a user account.
-func (c *client) UserDel(login string) error {
-	uri := fmt.Sprintf(pathUser, c.addr, login)
-	err := c.delete(uri)
-	return err
-}
-
-// Repo returns a repository by id.
-func (c *client) Repo(repoID int64) (*Repo, error) {
-	out := new(Repo)
-	uri := fmt.Sprintf(pathRepo, c.addr, repoID)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// RepoLookup returns a repository by name.
-func (c *client) RepoLookup(fullName string) (*Repo, error) {
-	out := new(Repo)
-	uri := fmt.Sprintf(pathRepoLookup, c.addr, fullName)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// RepoList returns a list of all repositories to which
-// the user has explicit access in the host system.
-func (c *client) RepoList() ([]*Repo, error) {
-	var out []*Repo
-	uri := fmt.Sprintf(pathRepos, c.addr)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// RepoListOpts returns a list of all repositories to which
-// the user has explicit access in the host system.
-func (c *client) RepoListOpts(all bool) ([]*Repo, error) {
-	var out []*Repo
-	uri := fmt.Sprintf(pathRepos+"?all=%v", c.addr, all)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// RepoPost activates a repository.
-func (c *client) RepoPost(forgeRemoteID int64) (*Repo, error) {
-	out := new(Repo)
-	uri := fmt.Sprintf(pathRepoPost, c.addr, forgeRemoteID)
-	err := c.post(uri, nil, out)
-	return out, err
-}
-
-// RepoChown updates a repository owner.
-func (c *client) RepoChown(repoID int64) (*Repo, error) {
-	out := new(Repo)
-	uri := fmt.Sprintf(pathChown, c.addr, repoID)
-	err := c.post(uri, nil, out)
-	return out, err
-}
-
-// RepoRepair repairs the repository hooks.
-func (c *client) RepoRepair(repoID int64) error {
-	uri := fmt.Sprintf(pathRepair, c.addr, repoID)
-	return c.post(uri, nil, nil)
-}
-
-// RepoPatch updates a repository.
-func (c *client) RepoPatch(repoID int64, in *RepoPatch) (*Repo, error) {
-	out := new(Repo)
-	uri := fmt.Sprintf(pathRepo, c.addr, repoID)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-// RepoDel deletes a repository.
-func (c *client) RepoDel(repoID int64) error {
-	uri := fmt.Sprintf(pathRepo, c.addr, repoID)
-	err := c.delete(uri)
-	return err
-}
-
-// RepoMove moves a repository
-func (c *client) RepoMove(repoID int64, newFullName string) error {
-	uri := fmt.Sprintf(pathRepoMove, c.addr, repoID, newFullName)
-	return c.post(uri, nil, nil)
-}
-
-// Pipeline returns a repository pipeline by pipeline-id.
-func (c *client) Pipeline(repoID, pipeline int64) (*Pipeline, error) {
-	out := new(Pipeline)
-	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// Pipeline returns the latest repository pipeline by branch.
-func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) {
-	out := new(Pipeline)
-	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, "latest")
-	if len(branch) != 0 {
-		uri += "?branch=" + branch
-	}
-	err := c.get(uri, out)
-	return out, err
-}
-
-// PipelineList returns a list of recent pipelines for the
-// the specified repository.
-func (c *client) PipelineList(repoID int64) ([]*Pipeline, error) {
-	var out []*Pipeline
-	uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeline, error) {
-	var out *Pipeline
-	uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
-	err := c.post(uri, options, &out)
-	return out, err
-}
-
-// PipelineQueue returns a list of enqueued pipelines.
-func (c *client) PipelineQueue() ([]*Feed, error) {
-	var out []*Feed
-	uri := fmt.Sprintf(pathPipelineQueue, c.addr)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// PipelineStart re-starts a stopped pipeline.
-func (c *client) PipelineStart(repoID, pipeline int64, params map[string]string) (*Pipeline, error) {
-	out := new(Pipeline)
-	val := mapValues(params)
-	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
-	err := c.post(uri+"?"+val.Encode(), nil, out)
-	return out, err
-}
-
-// PipelineStop cancels the running step.
-func (c *client) PipelineStop(repoID, pipeline int64) error {
-	uri := fmt.Sprintf(pathStop, c.addr, repoID, pipeline)
-	err := c.post(uri, nil, nil)
-	return err
-}
-
-// PipelineApprove approves a blocked pipeline.
-func (c *client) PipelineApprove(repoID, pipeline int64) (*Pipeline, error) {
-	out := new(Pipeline)
-	uri := fmt.Sprintf(pathApprove, c.addr, repoID, pipeline)
-	err := c.post(uri, nil, out)
-	return out, err
-}
-
-// PipelineDecline declines a blocked pipeline.
-func (c *client) PipelineDecline(repoID, pipeline int64) (*Pipeline, error) {
-	out := new(Pipeline)
-	uri := fmt.Sprintf(pathDecline, c.addr, repoID, pipeline)
-	err := c.post(uri, nil, out)
-	return out, err
-}
-
-// PipelineKill force kills the running pipeline.
-func (c *client) PipelineKill(repoID, pipeline int64) error {
-	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
-	err := c.delete(uri)
-	return err
-}
-
-// LogsPurge purges the pipeline all steps logs for the specified pipeline.
-func (c *client) LogsPurge(repoID, pipeline int64) error {
-	uri := fmt.Sprintf(pathPipelineLogs, c.addr, repoID, pipeline)
-	err := c.delete(uri)
-	return err
-}
-
-// StepLogEntries returns the pipeline logs for the specified step.
-func (c *client) StepLogEntries(repoID, num, step int64) ([]*LogEntry, error) {
-	uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, num, step)
-	var out []*LogEntry
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// StepLogsPurge purges the pipeline logs for the specified step.
-func (c *client) StepLogsPurge(repoID, pipelineNumber, stepID int64) error {
-	uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, pipelineNumber, stepID)
-	err := c.delete(uri)
-	return err
-}
-
-// Deploy triggers a deployment for an existing pipeline using the
-// specified target environment.
-func (c *client) Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) {
-	out := new(Pipeline)
-	val := mapValues(params)
-	val.Set("event", EventDeploy)
-	val.Set("deploy_to", env)
-	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
-	err := c.post(uri+"?"+val.Encode(), nil, out)
-	return out, err
-}
-
-// Registry returns a registry by hostname.
-func (c *client) Registry(repoID int64, hostname string) (*Registry, error) {
-	out := new(Registry)
-	uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// RegistryList returns a list of all repository registries.
-func (c *client) RegistryList(repoID int64) ([]*Registry, error) {
-	var out []*Registry
-	uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// RegistryCreate creates a registry.
-func (c *client) RegistryCreate(repoID int64, in *Registry) (*Registry, error) {
-	out := new(Registry)
-	uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
-	err := c.post(uri, in, out)
-	return out, err
-}
-
-// RegistryUpdate updates a registry.
-func (c *client) RegistryUpdate(repoID int64, in *Registry) (*Registry, error) {
-	out := new(Registry)
-	uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, in.Address)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-// RegistryDelete deletes a registry.
-func (c *client) RegistryDelete(repoID int64, hostname string) error {
-	uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
-	return c.delete(uri)
-}
-
-// Secret returns a secret by name.
-func (c *client) Secret(repoID int64, secret string) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// SecretList returns a list of all repository secrets.
-func (c *client) SecretList(repoID int64) ([]*Secret, error) {
-	var out []*Secret
-	uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// SecretCreate creates a secret.
-func (c *client) SecretCreate(repoID int64, in *Secret) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
-	err := c.post(uri, in, out)
-	return out, err
-}
-
-// SecretUpdate updates a secret.
-func (c *client) SecretUpdate(repoID int64, in *Secret) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, in.Name)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-// SecretDelete deletes a secret.
-func (c *client) SecretDelete(repoID int64, secret string) error {
-	uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
-	return c.delete(uri)
-}
-
-// Org returns an organization by id.
-func (c *client) Org(orgID int64) (*Org, error) {
-	out := new(Org)
-	uri := fmt.Sprintf(pathOrg, c.addr, orgID)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// OrgLookup returns a organization by its name.
-func (c *client) OrgLookup(name string) (*Org, error) {
-	out := new(Org)
-	uri := fmt.Sprintf(pathOrgLookup, c.addr, name)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// OrgSecret returns an organization secret by name.
-func (c *client) OrgSecret(orgID int64, secret string) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// OrgSecretList returns a list of all organization secrets.
-func (c *client) OrgSecretList(orgID int64) ([]*Secret, error) {
-	var out []*Secret
-	uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// OrgSecretCreate creates an organization secret.
-func (c *client) OrgSecretCreate(orgID int64, in *Secret) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
-	err := c.post(uri, in, out)
-	return out, err
-}
-
-// OrgSecretUpdate updates an organization secret.
-func (c *client) OrgSecretUpdate(orgID int64, in *Secret) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, in.Name)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-// OrgSecretDelete deletes an organization secret.
-func (c *client) OrgSecretDelete(orgID int64, secret string) error {
-	uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
-	return c.delete(uri)
-}
-
-// GlobalOrgSecret returns an global secret by name.
-func (c *client) GlobalSecret(secret string) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
-	err := c.get(uri, out)
-	return out, err
-}
-
-// GlobalSecretList returns a list of all global secrets.
-func (c *client) GlobalSecretList() ([]*Secret, error) {
-	var out []*Secret
-	uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
-	err := c.get(uri, &out)
-	return out, err
-}
-
-// GlobalSecretCreate creates a global secret.
-func (c *client) GlobalSecretCreate(in *Secret) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
-	err := c.post(uri, in, out)
-	return out, err
-}
-
-// GlobalSecretUpdate updates a global secret.
-func (c *client) GlobalSecretUpdate(in *Secret) (*Secret, error) {
-	out := new(Secret)
-	uri := fmt.Sprintf(pathGlobalSecret, c.addr, in.Name)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-// GlobalSecretDelete deletes a global secret.
-func (c *client) GlobalSecretDelete(secret string) error {
-	uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
-	return c.delete(uri)
-}
-
-// QueueInfo returns queue info
-func (c *client) QueueInfo() (*Info, error) {
-	out := new(Info)
-	uri := fmt.Sprintf(pathQueue+"/info", c.addr)
-	err := c.get(uri, out)
-	return out, err
-}
-
 // LogLevel returns the current logging level
 func (c *client) LogLevel() (*LogLevel, error) {
 	out := new(LogLevel)
@@ -523,96 +74,31 @@ func (c *client) SetLogLevel(in *LogLevel) (*LogLevel, error) {
 	return out, err
 }
 
-func (c *client) CronList(repoID int64) ([]*Cron, error) {
-	out := make([]*Cron, 0, 5)
-	uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
-	return out, c.get(uri, &out)
-}
-
-func (c *client) CronCreate(repoID int64, in *Cron) (*Cron, error) {
-	out := new(Cron)
-	uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
-	return out, c.post(uri, in, out)
-}
-
-func (c *client) CronUpdate(repoID int64, in *Cron) (*Cron, error) {
-	out := new(Cron)
-	uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, in.ID)
-	err := c.patch(uri, in, out)
-	return out, err
-}
-
-func (c *client) CronDelete(repoID, cronID int64) error {
-	uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
-	return c.delete(uri)
-}
-
-func (c *client) CronGet(repoID, cronID int64) (*Cron, error) {
-	out := new(Cron)
-	uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
-	return out, c.get(uri, out)
-}
-
-func (c *client) AgentList() ([]*Agent, error) {
-	out := make([]*Agent, 0, 5)
-	uri := fmt.Sprintf(pathAgents, c.addr)
-	return out, c.get(uri, &out)
-}
-
-func (c *client) Agent(agentID int64) (*Agent, error) {
-	out := new(Agent)
-	uri := fmt.Sprintf(pathAgent, c.addr, agentID)
-	return out, c.get(uri, out)
-}
-
-func (c *client) AgentCreate(in *Agent) (*Agent, error) {
-	out := new(Agent)
-	uri := fmt.Sprintf(pathAgents, c.addr)
-	return out, c.post(uri, in, out)
-}
-
-func (c *client) AgentUpdate(in *Agent) (*Agent, error) {
-	out := new(Agent)
-	uri := fmt.Sprintf(pathAgent, c.addr, in.ID)
-	return out, c.patch(uri, in, out)
-}
-
-func (c *client) AgentDelete(agentID int64) error {
-	uri := fmt.Sprintf(pathAgent, c.addr, agentID)
-	return c.delete(uri)
-}
-
-func (c *client) AgentTasksList(agentID int64) ([]*Task, error) {
-	out := make([]*Task, 0, 5)
-	uri := fmt.Sprintf(pathAgentTasks, c.addr, agentID)
-	return out, c.get(uri, &out)
-}
-
 //
 // http request helper functions
 //
 
-// helper function for making an http GET request.
+// Helper function for making an http GET request.
 func (c *client) get(rawurl string, out any) error {
 	return c.do(rawurl, http.MethodGet, nil, out)
 }
 
-// helper function for making an http POST request.
+// Helper function for making an http POST request.
 func (c *client) post(rawurl string, in, out any) error {
 	return c.do(rawurl, http.MethodPost, in, out)
 }
 
-// helper function for making an http PATCH request.
+// Helper function for making an http PATCH request.
 func (c *client) patch(rawurl string, in, out any) error {
 	return c.do(rawurl, http.MethodPatch, in, out)
 }
 
-// helper function for making an http DELETE request.
+// Helper function for making an http DELETE request.
 func (c *client) delete(rawurl string) error {
 	return c.do(rawurl, http.MethodDelete, nil, nil)
 }
 
-// helper function to make an http request
+// Helper function to make an http request.
 func (c *client) do(rawurl, method string, in, out any) error {
 	body, err := c.open(rawurl, method, in)
 	if err != nil {
@@ -625,7 +111,7 @@ func (c *client) do(rawurl, method string, in, out any) error {
 	return nil
 }
 
-// helper function to open an http request
+// Helper function to open an http request.
 func (c *client) open(rawurl, method string, in any) (io.ReadCloser, error) {
 	uri, err := url.Parse(rawurl)
 	if err != nil {
diff --git a/woodpecker-go/woodpecker/client_test.go b/woodpecker-go/woodpecker/client_test.go
index a5ad5a2b7..4bdad1dc4 100644
--- a/woodpecker-go/woodpecker/client_test.go
+++ b/woodpecker-go/woodpecker/client_test.go
@@ -25,44 +25,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func Test_QueueInfo(t *testing.T) {
-	fixtureHandler := func(w http.ResponseWriter, _ *http.Request) {
-		fmt.Fprint(w, `{
-			"pending": null,
-			"running": [
-					{
-							"id": "4696",
-							"data": "",
-							"labels": {
-									"platform": "linux/amd64",
-									"repo": "woodpecker-ci/woodpecker"
-							},
-							"Dependencies": [],
-							"DepStatus": {},
-							"RunOn": null
-					}
-			],
-			"stats": {
-					"worker_count": 3,
-					"pending_count": 0,
-					"waiting_on_deps_count": 0,
-					"running_count": 1,
-					"completed_count": 0
-			},
-			"Paused": false
-	}`)
-	}
-
-	ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
-	defer ts.Close()
-
-	client := NewClient(ts.URL, http.DefaultClient)
-
-	info, err := client.QueueInfo()
-	assert.NoError(t, err)
-	assert.Equal(t, 3, info.Stats.Workers)
-}
-
 func Test_LogLevel(t *testing.T) {
 	logLevel := "warn"
 	fixtureHandler := func(w http.ResponseWriter, r *http.Request) {
diff --git a/woodpecker-go/woodpecker/const.go b/woodpecker-go/woodpecker/const.go
index cf0792646..06dfe324a 100644
--- a/woodpecker-go/woodpecker/const.go
+++ b/woodpecker-go/woodpecker/const.go
@@ -49,7 +49,7 @@ const (
 	LogEntryProgress
 )
 
-// StepType identifies the type of step
+// StepType identifies the type of step.
 type StepType string
 
 const (
diff --git a/woodpecker-go/woodpecker/global_secret.go b/woodpecker-go/woodpecker/global_secret.go
new file mode 100644
index 000000000..7befc97e5
--- /dev/null
+++ b/woodpecker-go/woodpecker/global_secret.go
@@ -0,0 +1,46 @@
+package woodpecker
+
+import "fmt"
+
+const (
+	pathGlobalSecrets = "%s/api/secrets"
+	pathGlobalSecret  = "%s/api/secrets/%s"
+)
+
+// GlobalOrgSecret returns an global secret by name.
+func (c *client) GlobalSecret(secret string) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// GlobalSecretList returns a list of all global secrets.
+func (c *client) GlobalSecretList() ([]*Secret, error) {
+	var out []*Secret
+	uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// GlobalSecretCreate creates a global secret.
+func (c *client) GlobalSecretCreate(in *Secret) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
+	err := c.post(uri, in, out)
+	return out, err
+}
+
+// GlobalSecretUpdate updates a global secret.
+func (c *client) GlobalSecretUpdate(in *Secret) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathGlobalSecret, c.addr, in.Name)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// GlobalSecretDelete deletes a global secret.
+func (c *client) GlobalSecretDelete(secret string) error {
+	uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
+	return c.delete(uri)
+}
diff --git a/woodpecker-go/woodpecker/interface.go b/woodpecker-go/woodpecker/interface.go
index 56edc8f6f..9ad2ee33e 100644
--- a/woodpecker-go/woodpecker/interface.go
+++ b/woodpecker-go/woodpecker/interface.go
@@ -190,42 +190,42 @@ type Client interface {
 	// QueueInfo returns the queue state.
 	QueueInfo() (*Info, error)
 
-	// LogLevel returns the current logging level
+	// LogLevel returns the current logging level.
 	LogLevel() (*LogLevel, error)
 
-	// SetLogLevel sets the server's logging level
+	// SetLogLevel sets the server's logging level.
 	SetLogLevel(logLevel *LogLevel) (*LogLevel, error)
 
-	// CronList list all cron jobs of a repo
+	// CronList list all cron jobs of a repo.
 	CronList(repoID int64) ([]*Cron, error)
 
-	// CronGet get a specific cron job of a repo by id
+	// CronGet get a specific cron job of a repo by id.
 	CronGet(repoID, cronID int64) (*Cron, error)
 
-	// CronDelete delete a specific cron job of a repo by id
+	// CronDelete delete a specific cron job of a repo by id.
 	CronDelete(repoID, cronID int64) error
 
-	// CronCreate create a new cron job in a repo
+	// CronCreate create a new cron job in a repo.
 	CronCreate(repoID int64, cron *Cron) (*Cron, error)
 
-	// CronUpdate update an existing cron job of a repo
+	// CronUpdate update an existing cron job of a repo.
 	CronUpdate(repoID int64, cron *Cron) (*Cron, error)
 
-	// AgentList returns a list of all registered agents
+	// AgentList returns a list of all registered agents.
 	AgentList() ([]*Agent, error)
 
-	// Agent returns an agent by id
+	// Agent returns an agent by id.
 	Agent(int64) (*Agent, error)
 
-	// AgentCreate creates a new agent
+	// AgentCreate creates a new agent.
 	AgentCreate(*Agent) (*Agent, error)
 
-	// AgentUpdate updates an existing agent
+	// AgentUpdate updates an existing agent.
 	AgentUpdate(*Agent) (*Agent, error)
 
-	// AgentDelete deletes an agent
+	// AgentDelete deletes an agent.
 	AgentDelete(int64) error
 
-	// AgentTasksList returns a list of all tasks executed by an agent
+	// AgentTasksList returns a list of all tasks executed by an agent.
 	AgentTasksList(int64) ([]*Task, error)
 }
diff --git a/woodpecker-go/woodpecker/org.go b/woodpecker-go/woodpecker/org.go
new file mode 100644
index 000000000..885232c05
--- /dev/null
+++ b/woodpecker-go/woodpecker/org.go
@@ -0,0 +1,64 @@
+package woodpecker
+
+import "fmt"
+
+const (
+	pathOrg        = "%s/api/orgs/%d"
+	pathOrgLookup  = "%s/api/orgs/lookup/%s"
+	pathOrgSecrets = "%s/api/orgs/%d/secrets"
+	pathOrgSecret  = "%s/api/orgs/%d/secrets/%s"
+)
+
+// Org returns an organization by id.
+func (c *client) Org(orgID int64) (*Org, error) {
+	out := new(Org)
+	uri := fmt.Sprintf(pathOrg, c.addr, orgID)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// OrgLookup returns a organization by its name.
+func (c *client) OrgLookup(name string) (*Org, error) {
+	out := new(Org)
+	uri := fmt.Sprintf(pathOrgLookup, c.addr, name)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// OrgSecret returns an organization secret by name.
+func (c *client) OrgSecret(orgID int64, secret string) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// OrgSecretList returns a list of all organization secrets.
+func (c *client) OrgSecretList(orgID int64) ([]*Secret, error) {
+	var out []*Secret
+	uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// OrgSecretCreate creates an organization secret.
+func (c *client) OrgSecretCreate(orgID int64, in *Secret) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
+	err := c.post(uri, in, out)
+	return out, err
+}
+
+// OrgSecretUpdate updates an organization secret.
+func (c *client) OrgSecretUpdate(orgID int64, in *Secret) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, in.Name)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// OrgSecretDelete deletes an organization secret.
+func (c *client) OrgSecretDelete(orgID int64, secret string) error {
+	uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
+	return c.delete(uri)
+}
diff --git a/woodpecker-go/woodpecker/pipeline.go b/woodpecker-go/woodpecker/pipeline.go
new file mode 100644
index 000000000..8ed4d6d20
--- /dev/null
+++ b/woodpecker-go/woodpecker/pipeline.go
@@ -0,0 +1,13 @@
+package woodpecker
+
+import "fmt"
+
+const pathPipelineQueue = "%s/api/pipelines"
+
+// PipelineQueue returns a list of enqueued pipelines.
+func (c *client) PipelineQueue() ([]*Feed, error) {
+	var out []*Feed
+	uri := fmt.Sprintf(pathPipelineQueue, c.addr)
+	err := c.get(uri, &out)
+	return out, err
+}
diff --git a/woodpecker-go/woodpecker/queue.go b/woodpecker-go/woodpecker/queue.go
new file mode 100644
index 000000000..1e0f27e15
--- /dev/null
+++ b/woodpecker-go/woodpecker/queue.go
@@ -0,0 +1,13 @@
+package woodpecker
+
+import "fmt"
+
+const pathQueue = "%s/api/queue"
+
+// QueueInfo returns queue info.
+func (c *client) QueueInfo() (*Info, error) {
+	out := new(Info)
+	uri := fmt.Sprintf(pathQueue+"/info", c.addr)
+	err := c.get(uri, out)
+	return out, err
+}
diff --git a/woodpecker-go/woodpecker/queue_test.go b/woodpecker-go/woodpecker/queue_test.go
new file mode 100644
index 000000000..099169d3a
--- /dev/null
+++ b/woodpecker-go/woodpecker/queue_test.go
@@ -0,0 +1,116 @@
+package woodpecker
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestClient_QueueInfo(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		expected *Info
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `{
+					"pending": null,
+					"running": [
+							{
+									"id": "4696",
+									"data": "",
+									"labels": {
+											"platform": "linux/amd64",
+											"repo": "woodpecker-ci/woodpecker"
+									},
+									"Dependencies": [],
+									"DepStatus": {},
+									"RunOn": null
+							}
+					],
+					"stats": {
+						"worker_count": 2,
+						"pending_count": 0,
+						"waiting_on_deps_count": 0,
+						"running_count": 0,
+						"completed_count": 0
+					},
+					"Paused": false
+				}`)
+				assert.NoError(t, err)
+			},
+			expected: &Info{
+				Running: []Task{
+					{
+						ID:   "4696",
+						Data: []byte{},
+						Labels: map[string]string{
+							"platform": "linux/amd64",
+							"repo":     "woodpecker-ci/woodpecker",
+						},
+						Dependencies: []string{},
+						DepStatus:    nil,
+						RunOn:        nil,
+					},
+				},
+				Stats: struct {
+					Workers       int `json:"worker_count"`
+					Pending       int `json:"pending_count"`
+					WaitingOnDeps int `json:"waiting_on_deps_count"`
+					Running       int `json:"running_count"`
+					Complete      int `json:"completed_count"`
+				}{
+					Workers:       2,
+					Pending:       0,
+					WaitingOnDeps: 0,
+					Running:       0,
+					Complete:      0,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "invalid response",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `invalid json`)
+				assert.NoError(t, err)
+			},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			info, err := client.QueueInfo()
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, info)
+		})
+	}
+}
diff --git a/woodpecker-go/woodpecker/repo.go b/woodpecker-go/woodpecker/repo.go
new file mode 100644
index 000000000..91f488f9e
--- /dev/null
+++ b/woodpecker-go/woodpecker/repo.go
@@ -0,0 +1,304 @@
+package woodpecker
+
+import "fmt"
+
+const (
+	pathRepoPost       = "%s/api/repos?forge_remote_id=%d"
+	pathRepo           = "%s/api/repos/%d"
+	pathRepoLookup     = "%s/api/repos/lookup/%s"
+	pathRepoMove       = "%s/api/repos/%d/move?to=%s"
+	pathChown          = "%s/api/repos/%d/chown"
+	pathRepair         = "%s/api/repos/%d/repair"
+	pathPipelines      = "%s/api/repos/%d/pipelines"
+	pathPipeline       = "%s/api/repos/%d/pipelines/%v"
+	pathPipelineLogs   = "%s/api/repos/%d/logs/%d"
+	pathStepLogs       = "%s/api/repos/%d/logs/%d/%d"
+	pathApprove        = "%s/api/repos/%d/pipelines/%d/approve"
+	pathDecline        = "%s/api/repos/%d/pipelines/%d/decline"
+	pathStop           = "%s/api/repos/%d/pipelines/%d/cancel"
+	pathRepoSecrets    = "%s/api/repos/%d/secrets"
+	pathRepoSecret     = "%s/api/repos/%d/secrets/%s"
+	pathRepoRegistries = "%s/api/repos/%d/registry"
+	pathRepoRegistry   = "%s/api/repos/%d/registry/%s"
+	pathRepoCrons      = "%s/api/repos/%d/cron"
+	pathRepoCron       = "%s/api/repos/%d/cron/%d"
+)
+
+// Repo returns a repository by id.
+func (c *client) Repo(repoID int64) (*Repo, error) {
+	out := new(Repo)
+	uri := fmt.Sprintf(pathRepo, c.addr, repoID)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// RepoLookup returns a repository by name.
+func (c *client) RepoLookup(fullName string) (*Repo, error) {
+	out := new(Repo)
+	uri := fmt.Sprintf(pathRepoLookup, c.addr, fullName)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// RepoPost activates a repository.
+func (c *client) RepoPost(forgeRemoteID int64) (*Repo, error) {
+	out := new(Repo)
+	uri := fmt.Sprintf(pathRepoPost, c.addr, forgeRemoteID)
+	err := c.post(uri, nil, out)
+	return out, err
+}
+
+// RepoChown updates a repository owner.
+func (c *client) RepoChown(repoID int64) (*Repo, error) {
+	out := new(Repo)
+	uri := fmt.Sprintf(pathChown, c.addr, repoID)
+	err := c.post(uri, nil, out)
+	return out, err
+}
+
+// RepoRepair repairs the repository hooks.
+func (c *client) RepoRepair(repoID int64) error {
+	uri := fmt.Sprintf(pathRepair, c.addr, repoID)
+	return c.post(uri, nil, nil)
+}
+
+// RepoPatch updates a repository.
+func (c *client) RepoPatch(repoID int64, in *RepoPatch) (*Repo, error) {
+	out := new(Repo)
+	uri := fmt.Sprintf(pathRepo, c.addr, repoID)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// RepoDel deletes a repository.
+func (c *client) RepoDel(repoID int64) error {
+	uri := fmt.Sprintf(pathRepo, c.addr, repoID)
+	err := c.delete(uri)
+	return err
+}
+
+// RepoMove moves a repository.
+func (c *client) RepoMove(repoID int64, newFullName string) error {
+	uri := fmt.Sprintf(pathRepoMove, c.addr, repoID, newFullName)
+	return c.post(uri, nil, nil)
+}
+
+// Registry returns a registry by hostname.
+func (c *client) Registry(repoID int64, hostname string) (*Registry, error) {
+	out := new(Registry)
+	uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// RegistryList returns a list of all repository registries.
+func (c *client) RegistryList(repoID int64) ([]*Registry, error) {
+	var out []*Registry
+	uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// RegistryCreate creates a registry.
+func (c *client) RegistryCreate(repoID int64, in *Registry) (*Registry, error) {
+	out := new(Registry)
+	uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
+	err := c.post(uri, in, out)
+	return out, err
+}
+
+// RegistryUpdate updates a registry.
+func (c *client) RegistryUpdate(repoID int64, in *Registry) (*Registry, error) {
+	out := new(Registry)
+	uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, in.Address)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// RegistryDelete deletes a registry.
+func (c *client) RegistryDelete(repoID int64, hostname string) error {
+	uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
+	return c.delete(uri)
+}
+
+// Secret returns a secret by name.
+func (c *client) Secret(repoID int64, secret string) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// SecretList returns a list of all repository secrets.
+func (c *client) SecretList(repoID int64) ([]*Secret, error) {
+	var out []*Secret
+	uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// SecretCreate creates a secret.
+func (c *client) SecretCreate(repoID int64, in *Secret) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
+	err := c.post(uri, in, out)
+	return out, err
+}
+
+// SecretUpdate updates a secret.
+func (c *client) SecretUpdate(repoID int64, in *Secret) (*Secret, error) {
+	out := new(Secret)
+	uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, in.Name)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// SecretDelete deletes a secret.
+func (c *client) SecretDelete(repoID int64, secret string) error {
+	uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
+	return c.delete(uri)
+}
+
+// CronList returns a list of cronjobs for the specified repository.
+func (c *client) CronList(repoID int64) ([]*Cron, error) {
+	out := make([]*Cron, 0, 5)
+	uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
+	return out, c.get(uri, &out)
+}
+
+// CronCreate creates a new cron job for the specified repository.
+func (c *client) CronCreate(repoID int64, in *Cron) (*Cron, error) {
+	out := new(Cron)
+	uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
+	return out, c.post(uri, in, out)
+}
+
+// CronUpdate updates an existing cron job for the specified repository.
+func (c *client) CronUpdate(repoID int64, in *Cron) (*Cron, error) {
+	out := new(Cron)
+	uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, in.ID)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// CronDelete deletes a cron job by cron-id for the specified repository.
+func (c *client) CronDelete(repoID, cronID int64) error {
+	uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
+	return c.delete(uri)
+}
+
+// CronGet returns a cron job by cron-id for the specified repository.
+func (c *client) CronGet(repoID, cronID int64) (*Cron, error) {
+	out := new(Cron)
+	uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
+	return out, c.get(uri, out)
+}
+
+// Pipeline returns a repository pipeline by pipeline-id.
+func (c *client) Pipeline(repoID, pipeline int64) (*Pipeline, error) {
+	out := new(Pipeline)
+	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// Pipeline returns the latest repository pipeline by branch.
+func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) {
+	out := new(Pipeline)
+	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, "latest")
+	if len(branch) != 0 {
+		uri += "?branch=" + branch
+	}
+	err := c.get(uri, out)
+	return out, err
+}
+
+// PipelineList returns a list of recent pipelines for the
+// the specified repository.
+func (c *client) PipelineList(repoID int64) ([]*Pipeline, error) {
+	var out []*Pipeline
+	uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// PipelineCreate creates a new pipeline for the specified repository.
+func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeline, error) {
+	var out *Pipeline
+	uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
+	err := c.post(uri, options, &out)
+	return out, err
+}
+
+// PipelineStart re-starts a stopped pipeline.
+func (c *client) PipelineStart(repoID, pipeline int64, params map[string]string) (*Pipeline, error) {
+	out := new(Pipeline)
+	val := mapValues(params)
+	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
+	err := c.post(uri+"?"+val.Encode(), nil, out)
+	return out, err
+}
+
+// PipelineStop cancels the running step.
+func (c *client) PipelineStop(repoID, pipeline int64) error {
+	uri := fmt.Sprintf(pathStop, c.addr, repoID, pipeline)
+	err := c.post(uri, nil, nil)
+	return err
+}
+
+// PipelineApprove approves a blocked pipeline.
+func (c *client) PipelineApprove(repoID, pipeline int64) (*Pipeline, error) {
+	out := new(Pipeline)
+	uri := fmt.Sprintf(pathApprove, c.addr, repoID, pipeline)
+	err := c.post(uri, nil, out)
+	return out, err
+}
+
+// PipelineDecline declines a blocked pipeline.
+func (c *client) PipelineDecline(repoID, pipeline int64) (*Pipeline, error) {
+	out := new(Pipeline)
+	uri := fmt.Sprintf(pathDecline, c.addr, repoID, pipeline)
+	err := c.post(uri, nil, out)
+	return out, err
+}
+
+// PipelineKill force kills the running pipeline.
+func (c *client) PipelineKill(repoID, pipeline int64) error {
+	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
+	err := c.delete(uri)
+	return err
+}
+
+// LogsPurge purges the pipeline all steps logs for the specified pipeline.
+func (c *client) LogsPurge(repoID, pipeline int64) error {
+	uri := fmt.Sprintf(pathPipelineLogs, c.addr, repoID, pipeline)
+	err := c.delete(uri)
+	return err
+}
+
+// Deploy triggers a deployment for an existing pipeline using the
+// specified target environment.
+func (c *client) Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) {
+	out := new(Pipeline)
+	val := mapValues(params)
+	val.Set("event", EventDeploy)
+	val.Set("deploy_to", env)
+	uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
+	err := c.post(uri+"?"+val.Encode(), nil, out)
+	return out, err
+}
+
+// StepLogEntries returns the pipeline logs for the specified step.
+func (c *client) StepLogEntries(repoID, num, step int64) ([]*LogEntry, error) {
+	uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, num, step)
+	var out []*LogEntry
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// StepLogsPurge purges the pipeline logs for the specified step.
+func (c *client) StepLogsPurge(repoID, pipelineNumber, stepID int64) error {
+	uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, pipelineNumber, stepID)
+	err := c.delete(uri)
+	return err
+}
diff --git a/woodpecker-go/woodpecker/types.go b/woodpecker-go/woodpecker/types.go
index cf84e01b9..f8054e66d 100644
--- a/woodpecker-go/woodpecker/types.go
+++ b/woodpecker-go/woodpecker/types.go
@@ -181,12 +181,22 @@ type (
 		Commit  string `json:"commit,omitempty"`
 	}
 
+	// QueueStats struct {
+	// 	Workers       int `json:"worker_count"`
+	// 	Pending       int `json:"pending_count"`
+	// 	WaitingOnDeps int `json:"waiting_on_deps_count"`
+	// 	Running       int `json:"running_count"`
+	// 	Complete      int `json:"completed_count"`
+	// }
+
 	// Info provides queue stats.
 	Info struct {
 		Pending       []Task `json:"pending"`
 		WaitingOnDeps []Task `json:"waiting_on_deps"`
 		Running       []Task `json:"running"`
-		Stats         struct {
+		// TODO use dedicated struct in 3.x
+		// Stats         QueueStats `json:"stats"`
+		Stats struct {
 			Workers       int `json:"worker_count"`
 			Pending       int `json:"pending_count"`
 			WaitingOnDeps int `json:"waiting_on_deps_count"`
@@ -196,7 +206,7 @@ type (
 		Paused bool `json:"paused,omitempty"`
 	}
 
-	// LogLevel is for checking/setting logging level
+	// LogLevel is for checking/setting logging level.
 	LogLevel struct {
 		Level string `json:"log-level"`
 	}
@@ -211,7 +221,7 @@ type (
 		Type   LogEntryType `json:"type"`
 	}
 
-	// Cron is the JSON data of a cron job
+	// Cron is the JSON data of a cron job.
 	Cron struct {
 		ID        int64  `json:"id"`
 		Name      string `json:"name"`
@@ -223,13 +233,13 @@ type (
 		Branch    string `json:"branch"`
 	}
 
-	// PipelineOptions is the JSON data for creating a new pipeline
+	// PipelineOptions is the JSON data for creating a new pipeline.
 	PipelineOptions struct {
 		Branch    string            `json:"branch"`
 		Variables map[string]string `json:"variables"`
 	}
 
-	// Agent is the JSON data for an agent
+	// Agent is the JSON data for an agent.
 	Agent struct {
 		ID          int64  `json:"id"`
 		Created     int64  `json:"created"`
@@ -245,7 +255,7 @@ type (
 		NoSchedule  bool   `json:"no_schedule"`
 	}
 
-	// Task is the JSON data for a task
+	// Task is the JSON data for a task.
 	Task struct {
 		ID           string            `json:"id"`
 		Data         []byte            `json:"data"`
@@ -256,7 +266,7 @@ type (
 		AgentID      int64             `json:"agent_id"`
 	}
 
-	// Org is the JSON data for an organization
+	// Org is the JSON data for an organization.
 	Org struct {
 		ID     int64  `json:"id"`
 		Name   string `json:"name"`
diff --git a/woodpecker-go/woodpecker/user.go b/woodpecker-go/woodpecker/user.go
new file mode 100644
index 000000000..6cba11e53
--- /dev/null
+++ b/woodpecker-go/woodpecker/user.go
@@ -0,0 +1,75 @@
+package woodpecker
+
+import "fmt"
+
+const (
+	pathSelf  = "%s/api/user"
+	pathRepos = "%s/api/user/repos"
+	pathUsers = "%s/api/users"
+	pathUser  = "%s/api/users/%s"
+)
+
+// Self returns the currently authenticated user.
+func (c *client) Self() (*User, error) {
+	out := new(User)
+	uri := fmt.Sprintf(pathSelf, c.addr)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// User returns a user by login.
+func (c *client) User(login string) (*User, error) {
+	out := new(User)
+	uri := fmt.Sprintf(pathUser, c.addr, login)
+	err := c.get(uri, out)
+	return out, err
+}
+
+// UserList returns a list of all registered users.
+func (c *client) UserList() ([]*User, error) {
+	var out []*User
+	uri := fmt.Sprintf(pathUsers, c.addr)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// UserPost creates a new user account.
+func (c *client) UserPost(in *User) (*User, error) {
+	out := new(User)
+	uri := fmt.Sprintf(pathUsers, c.addr)
+	err := c.post(uri, in, out)
+	return out, err
+}
+
+// UserPatch updates a user account.
+func (c *client) UserPatch(in *User) (*User, error) {
+	out := new(User)
+	uri := fmt.Sprintf(pathUser, c.addr, in.Login)
+	err := c.patch(uri, in, out)
+	return out, err
+}
+
+// UserDel deletes a user account.
+func (c *client) UserDel(login string) error {
+	uri := fmt.Sprintf(pathUser, c.addr, login)
+	err := c.delete(uri)
+	return err
+}
+
+// RepoList returns a list of all repositories to which
+// the user has explicit access in the host system.
+func (c *client) RepoList() ([]*Repo, error) {
+	var out []*Repo
+	uri := fmt.Sprintf(pathRepos, c.addr)
+	err := c.get(uri, &out)
+	return out, err
+}
+
+// RepoListOpts returns a list of all repositories to which
+// the user has explicit access in the host system.
+func (c *client) RepoListOpts(all bool) ([]*Repo, error) {
+	var out []*Repo
+	uri := fmt.Sprintf(pathRepos+"?all=%v", c.addr, all)
+	err := c.get(uri, &out)
+	return out, err
+}
diff --git a/woodpecker-go/woodpecker/user_test.go b/woodpecker-go/woodpecker/user_test.go
new file mode 100644
index 000000000..4d99226c1
--- /dev/null
+++ b/woodpecker-go/woodpecker/user_test.go
@@ -0,0 +1,267 @@
+package woodpecker
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestClient_UserList(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		expected []*User
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `[{"id":1,"login":"user1"},{"id":2,"login":"user2"}]`)
+				assert.NoError(t, err)
+			},
+			expected: []*User{{ID: 1, Login: "user1"}, {ID: 2, Login: "user2"}},
+			wantErr:  false,
+		},
+		{
+			name: "empty response",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `[]`)
+				assert.NoError(t, err)
+			},
+			expected: []*User{},
+			wantErr:  false,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			users, err := client.UserList()
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, users, tt.expected)
+		})
+	}
+}
+
+func TestClient_UserPost(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		input    *User
+		expected *User
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusCreated)
+				_, err := fmt.Fprint(w, `{"id":1,"login":"new_user"}`)
+				assert.NoError(t, err)
+			},
+			input:    &User{Login: "new_user"},
+			expected: &User{ID: 1, Login: "new_user"},
+			wantErr:  false,
+		},
+		{
+			name: "invalid input",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusBadRequest)
+			},
+			input:    &User{},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, _ *http.Request) {
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			input:    &User{Login: "new_user"},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			user, err := client.UserPost(tt.input)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, user, tt.expected)
+		})
+	}
+}
+
+func TestClient_UserPatch(t *testing.T) {
+	tests := []struct {
+		name     string
+		handler  http.HandlerFunc
+		input    *User
+		expected *User
+		wantErr  bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+				_, err := fmt.Fprint(w, `{"id":1,"login":"updated_user"}`)
+				assert.NoError(t, err)
+			},
+			input:    &User{ID: 1, Login: "existing_user"},
+			expected: &User{ID: 1, Login: "updated_user"},
+			wantErr:  false,
+		},
+		{
+			name: "not found",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusNotFound)
+			},
+			input:    &User{ID: 999, Login: "nonexistent_user"},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "invalid input",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusBadRequest)
+			},
+			input:    &User{},
+			expected: nil,
+			wantErr:  true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPatch {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			input:    &User{ID: 1, Login: "existing_user"},
+			expected: nil,
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			user, err := client.UserPatch(tt.input)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.Equal(t, user, tt.expected)
+		})
+	}
+}
+
+func TestClient_UserDel(t *testing.T) {
+	tests := []struct {
+		name    string
+		handler http.HandlerFunc
+		login   string
+		wantErr bool
+	}{
+		{
+			name: "success",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodDelete {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusOK)
+			},
+			login:   "existing_user",
+			wantErr: false,
+		},
+		{
+			name: "not found",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodDelete {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusNotFound)
+			},
+			login:   "nonexistent_user",
+			wantErr: true,
+		},
+		{
+			name: "server error",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodDelete {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+			},
+			login:   "existing_user",
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ts := httptest.NewServer(tt.handler)
+			defer ts.Close()
+
+			client := NewClient(ts.URL, http.DefaultClient)
+			err := client.UserDel(tt.login)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+		})
+	}
+}