New Upstream Release - golang-github-putdotio-go-putio

Ready changes

Summary

Merged new upstream version: 0.0~git20221111.2e102ad+ds (was: 0.0~git20190822.19b9c63).

Resulting package

Built on 2022-12-11T06:25 (took 3m21s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-putdotio-go-putio-dev

Lintian Result

Diff

diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml
new file mode 100644
index 0000000..0819247
--- /dev/null
+++ b/.github/workflows/go-test.yml
@@ -0,0 +1,21 @@
+name: Run go tests
+on:
+  push:
+  pull_request:
+
+jobs:
+  test:
+    name: Run tests
+    runs-on: ubuntu-latest
+    steps:
+      - name: Setup Go 1.13
+        uses: actions/setup-go@v1
+        with:
+          go-version: 1.13
+        id: go
+      - name: Checkout code
+        uses: actions/checkout@v1
+      - name: Get dependencies
+        run: go mod download
+      - name: Run tests
+        run: go test -p 1 -v -race ./...
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
new file mode 100644
index 0000000..033b84b
--- /dev/null
+++ b/.github/workflows/golangci-lint.yml
@@ -0,0 +1,15 @@
+name: Golang CI Linter
+on:
+  push:
+  pull_request:
+
+jobs:
+  golangci:
+    name: lint
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: golangci-lint
+        uses: golangci/golangci-lint-action@v2
+        with:
+          version: latest
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 1a0bbea..455ac23 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,6 @@
+arch:
+  - amd64
+  - ppc64le
 language: go
 go:
   - tip
diff --git a/README.md b/README.md
index 3afe2c1..964c772 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,20 @@
-# putio  [![Build Status](https://travis-ci.org/putdotio/go-putio.svg?branch=master)](https://travis-ci.org/putdotio/go-putio)
+[![Golang CI Linter](https://github.com/putdotio/go-putio/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/putdotio/go-putio/actions/workflows/golangci-lint.yml)
+[![Golang Tests](https://github.com/putdotio/go-putio/actions/workflows/go-test.yml/badge.svg)](https://github.com/putdotio/go-putio/actions/workflows/go-test.yml)
+
+
+# putio
 
 putio is a Go client library for accessing the [Put.io API v2](https://api.put.io/v2/docs).
 
 ## Documentation
 
-Available on [GoDoc](http://godoc.org/github.com/putdotio/go-putio/putio)
+Available on [GoDoc](http://godoc.org/github.com/putdotio/go-putio)
 
 ## Install
 
 ```sh
-go get -u github.com/putdotio/go-putio/putio"
+go get github.com/putdotio/go-putio@latest
+go get golang.org/x/oauth2@latest
 ```
 
 ## Usage
@@ -23,21 +28,21 @@ import (
         "context"
 
         "golang.org/x/oauth2"
-        "github.com/putdotio/go-putio/putio"
+        "github.com/putdotio/go-putio"
 )
 
 func main() {
     oauthToken := "<YOUR-TOKEN-HERE>"
     tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: oauthToken})
-    oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
+    oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
 
     client := putio.NewClient(oauthClient)
 
     const rootDir = 0
-    root, err := client.Files.Get(context.Background(), rootDir)
+    root, err := client.Files.Get(context.TODO(), rootDir)
     if err != nil {
         log.Fatal(err)
     }
-    fmt.Println(root.Filename)
+    fmt.Println(root.Name)
 }
 ```
diff --git a/putio/account.go b/account.go
similarity index 64%
rename from putio/account.go
rename to account.go
index cd39638..ea298ae 100644
--- a/putio/account.go
+++ b/account.go
@@ -1,6 +1,9 @@
 package putio
 
-import "context"
+import (
+	"context"
+	"net/http"
+)
 
 // AccountService is the service to gather information about user account.
 type AccountService struct {
@@ -9,15 +12,15 @@ type AccountService struct {
 
 // Info retrieves user account information.
 func (a *AccountService) Info(ctx context.Context) (AccountInfo, error) {
-	req, err := a.client.NewRequest(ctx, "GET", "/v2/account/info", nil)
+	req, err := a.client.NewRequest(ctx, http.MethodGet, "/v2/account/info", nil)
 	if err != nil {
-		return AccountInfo{}, nil
+		return AccountInfo{}, err
 	}
 
 	var r struct {
 		Info AccountInfo
 	}
-	_, err = a.client.Do(req, &r)
+	_, err = a.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return AccountInfo{}, err
 	}
@@ -26,14 +29,14 @@ func (a *AccountService) Info(ctx context.Context) (AccountInfo, error) {
 
 // Settings retrieves user preferences.
 func (a *AccountService) Settings(ctx context.Context) (Settings, error) {
-	req, err := a.client.NewRequest(ctx, "GET", "/v2/account/settings", nil)
+	req, err := a.client.NewRequest(ctx, http.MethodGet, "/v2/account/settings", nil)
 	if err != nil {
-		return Settings{}, nil
+		return Settings{}, err
 	}
 	var r struct {
 		Settings Settings
 	}
-	_, err = a.client.Do(req, &r)
+	_, err = a.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return Settings{}, err
 	}
diff --git a/putio/account_test.go b/account_test.go
similarity index 97%
rename from putio/account_test.go
rename to account_test.go
index 1a35de2..6253ef9 100644
--- a/putio/account_test.go
+++ b/account_test.go
@@ -58,7 +58,7 @@ func TestAccount_Info(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/account/info", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -106,7 +106,7 @@ func TestAccount_Settings(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/account/settings", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
diff --git a/putio/client.go b/client.go
similarity index 65%
rename from putio/client.go
rename to client.go
index ec0d9d7..919ff53 100644
--- a/putio/client.go
+++ b/client.go
@@ -8,6 +8,13 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"strings"
+	"time"
+)
+
+// Constants.
+const (
+	DefaultClientTimeout = 30 * time.Second
 )
 
 const (
@@ -15,6 +22,7 @@ const (
 	defaultMediaType = "application/json"
 	defaultBaseURL   = "https://api.put.io"
 	defaultUploadURL = "https://upload.put.io"
+	defaultTusURL    = "https://upload.put.io/files/"
 )
 
 // Client manages communication with Put.io v2 API.
@@ -25,15 +33,18 @@ type Client struct {
 	// Base URL for API requests
 	BaseURL *url.URL
 
-	// base url for upload requests
-	uploadURL *url.URL
-
 	// User agent for client
 	UserAgent string
 
+	// Override host header for API requests
+	Host string
+
 	// ExtraHeaders are passed to the API server on every request.
 	ExtraHeaders http.Header
 
+	// Timeout for HTTP requests. Zero means no timeout.
+	Timeout time.Duration
+
 	// Services used for communicating with the API
 	Account   *AccountService
 	Files     *FilesService
@@ -41,6 +52,8 @@ type Client struct {
 	Zips      *ZipsService
 	Friends   *FriendsService
 	Events    *EventsService
+	Config    *ConfigService
+	Upload    *UploadService
 }
 
 // NewClient returns a new Put.io API client, using the htttpClient, which must
@@ -52,13 +65,12 @@ func NewClient(httpClient *http.Client) *Client {
 	}
 
 	baseURL, _ := url.Parse(defaultBaseURL)
-	uploadURL, _ := url.Parse(defaultUploadURL)
 	c := &Client{
 		client:       httpClient,
 		BaseURL:      baseURL,
-		uploadURL:    uploadURL,
 		UserAgent:    defaultUserAgent,
 		ExtraHeaders: make(http.Header),
+		Timeout:      DefaultClientTimeout,
 	}
 
 	c.Account = &AccountService{client: c}
@@ -67,20 +79,26 @@ func NewClient(httpClient *http.Client) *Client {
 	c.Zips = &ZipsService{client: c}
 	c.Friends = &FriendsService{client: c}
 	c.Events = &EventsService{client: c}
+	c.Config = &ConfigService{client: c}
+	c.Upload = &UploadService{client: c}
 
 	return c
 }
 
+// ValidateToken validates user's OAuth Token.
 func (c *Client) ValidateToken(ctx context.Context) (userID *int64, err error) {
-	req, err := c.NewRequest(ctx, "GET", "/v2/oauth2/validate", nil)
+	req, err := c.NewRequest(ctx, http.MethodGet, "/v2/oauth2/validate", nil)
 	if err != nil {
 		return
 	}
 	var r struct {
 		UserID *int64 `json:"user_id"`
 	}
-	_, err = c.Do(req, &r)
-	return r.UserID, err
+	_, err = c.Do(req, &r) // nolint:bodyclose
+	if err != nil {
+		return nil, err
+	}
+	return r.UserID, nil
 }
 
 // NewRequest creates an API request. A relative URL can be provided via
@@ -88,26 +106,31 @@ func (c *Client) ValidateToken(ctx context.Context) (userID *int64, err error) {
 func (c *Client) NewRequest(ctx context.Context, method, relURL string, body io.Reader) (*http.Request, error) {
 	rel, err := url.Parse(relURL)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
+	// Workaround for upload endpoints. Upload server is different than API server.
 	var u *url.URL
-	// XXX: workaroud for upload endpoint. upload method has a different base url,
-	// so we've a special case for testing purposes.
-	if relURL == "/v2/files/upload" {
-		u = c.uploadURL.ResolveReference(rel)
-	} else {
+	switch {
+	case relURL == "$upload$":
+		u, _ = url.Parse(defaultUploadURL)
+	case relURL == "$upload-tus$":
+		u, _ = url.Parse(defaultTusURL)
+	case strings.HasPrefix(relURL, "http://") || strings.HasPrefix(relURL, "https://"):
+		u = rel
+	default:
 		u = c.BaseURL.ResolveReference(rel)
 	}
 
-	req, err := http.NewRequest(method, u.String(), body)
+	req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
-
-	req = req.WithContext(ctx)
 	req.Header.Set("Accept", defaultMediaType)
 	req.Header.Set("User-Agent", c.UserAgent)
+	if c.Host != "" {
+		req.Host = c.Host
+	}
 
 	// merge headers with extra headers
 	for header, values := range c.ExtraHeaders {
@@ -125,15 +148,21 @@ func (c *Client) NewRequest(ctx context.Context, method, relURL string, body io.
 // v is nil. If v is nil, response body is not closed and the body can be used
 // for streaming.
 func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
+	if c.Timeout > 0 {
+		ctx, cancel := context.WithTimeout(r.Context(), c.Timeout)
+		defer cancel()
+		r = r.WithContext(ctx)
+	}
+
 	resp, err := c.client.Do(r)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	err = checkResponse(resp)
 	if err != nil {
 		// close the body at all times if there is an http error
-		resp.Body.Close()
+		_ = resp.Body.Close()
 		return resp, err
 	}
 
@@ -142,11 +171,13 @@ func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
 	}
 
 	// close the body for all cases from here
-	defer resp.Body.Close()
+	defer func() {
+		_ = resp.Body.Close()
+	}()
 
 	err = json.NewDecoder(resp.Body).Decode(v)
 	if err != nil {
-		return resp, err
+		return resp, fmt.Errorf("%w", err)
 	}
 
 	return resp, nil
@@ -156,25 +187,22 @@ func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
 // status code is not in success range, it will try to return a structured
 // error.
 func checkResponse(r *http.Response) error {
-	status := r.StatusCode
-	switch {
-	case status >= 200 && status <= 399:
+	if r.StatusCode >= 200 && r.StatusCode <= 399 {
 		return nil
-	case status >= 400 && status <= 599:
-		// server returns json
-	default:
-		return fmt.Errorf("unexpected status code: %d", status)
 	}
-	errorResponse := &ErrorResponse{Response: r}
-	data, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		return fmt.Errorf("body read error: %s. status: %v. Details: %v:", err, status, string(data[:250]))
+
+	// server possibly returns json and more details
+	er := &ErrorResponse{Response: r}
+
+	er.Body, er.ParseError = ioutil.ReadAll(r.Body)
+	if er.ParseError != nil {
+		return er
 	}
-	if len(data) > 0 {
-		err = json.Unmarshal(data, errorResponse)
-		if err != nil {
-			return fmt.Errorf("json decode error: %s. status: %v. Details: %v:", err, status, string(data[:250]))
+	if r.Header.Get("content-type") == "application/json" {
+		er.ParseError = json.Unmarshal(er.Body, er)
+		if er.ParseError != nil {
+			return er
 		}
 	}
-	return errorResponse
+	return er
 }
diff --git a/putio/client_test.go b/client_test.go
similarity index 71%
rename from putio/client_test.go
rename to client_test.go
index e475f27..c187460 100644
--- a/putio/client_test.go
+++ b/client_test.go
@@ -21,7 +21,6 @@ func setup() {
 	client = NewClient(nil)
 	url, _ := url.Parse(server.URL)
 	client.BaseURL = url
-	client.uploadURL = url
 }
 
 func teardown() {
@@ -34,22 +33,22 @@ func testMethod(t *testing.T, r *http.Request, want string) {
 	}
 }
 
-func testHeader(t *testing.T, r *http.Request, key, value string) {
+func testHeader(t *testing.T, r *http.Request, key, value string) { // nolint
 	if r.Header.Get(key) != value {
 		t.Errorf("missing header. want: %q: %q", key, value)
 	}
 }
 
 func TestNewClient(t *testing.T) {
-	client := NewClient(nil)
-	if client.BaseURL.String() != defaultBaseURL {
-		t.Errorf("got: %v, want: %v", client.BaseURL.String(), defaultBaseURL)
+	cl := NewClient(nil)
+	if cl.BaseURL.String() != defaultBaseURL {
+		t.Errorf("got: %v, want: %v", cl.BaseURL.String(), defaultBaseURL)
 	}
 }
 
 func TestNewRequest_badURL(t *testing.T) {
-	client := NewClient(nil)
-	_, err := client.NewRequest(context.Background(), "GET", ":", nil)
+	cl := NewClient(nil)
+	_, err := cl.NewRequest(context.Background(), http.MethodGet, ":", nil)
 	if err == nil {
 		t.Errorf("bad URL accepted")
 	}
@@ -57,10 +56,10 @@ func TestNewRequest_badURL(t *testing.T) {
 
 func TestNewRequest_customUserAgent(t *testing.T) {
 	userAgent := "test"
-	client := NewClient(nil)
-	client.UserAgent = userAgent
+	cl := NewClient(nil)
+	cl.UserAgent = userAgent
 
-	req, _ := client.NewRequest(context.Background(), "GET", "/test", nil)
+	req, _ := cl.NewRequest(context.Background(), http.MethodGet, "/test", nil)
 	if got := req.Header.Get("User-Agent"); got != userAgent {
 		t.Errorf("got: %v, want: %v", got, userAgent)
 	}
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..7e23177
--- /dev/null
+++ b/config.go
@@ -0,0 +1,107 @@
+package putio
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+)
+
+// ConfigService represents configuration related operations.
+type ConfigService struct {
+	client *Client
+}
+
+// GetAll all fills config.
+func (f *ConfigService) GetAll(ctx context.Context, config interface{}) error {
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/config", nil)
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	var r struct {
+		Config json.RawMessage `json:"config"`
+	}
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	return json.Unmarshal(r.Config, &config) // nolint:wrapcheck
+}
+
+// Get fetches config item via given key.
+func (f *ConfigService) Get(ctx context.Context, key string, value interface{}) (found bool, err error) {
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/config/"+key, nil)
+	if err != nil {
+		return false, err
+	}
+	var r struct {
+		Value *json.RawMessage `json:"value"`
+	}
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
+	if err != nil {
+		return false, err
+	}
+	if r.Value == nil {
+		return false, nil
+	}
+	return true, json.Unmarshal(*r.Value, &value) // nolint:wrapcheck
+}
+
+// SetAll updates all config items.
+func (f *ConfigService) SetAll(ctx context.Context, config interface{}) error {
+	b, err := json.Marshal(config)
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	v := struct {
+		Config json.RawMessage `json:"config"`
+	}{
+		Config: b,
+	}
+	body, err := json.Marshal(v)
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	req, err := f.client.NewRequest(ctx, http.MethodPut, "/v2/config", bytes.NewReader(body))
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	req.Header.Set("content-type", "application/json")
+	_, err = f.client.Do(req, nil) // nolint:bodyclose
+	return fmt.Errorf("%w", err)
+}
+
+// Set updates given config key's value.
+func (f *ConfigService) Set(ctx context.Context, key string, value interface{}) error {
+	b, err := json.Marshal(value)
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	v := struct {
+		Value json.RawMessage `json:"value"`
+	}{
+		Value: b,
+	}
+	body, err := json.Marshal(v)
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	req, err := f.client.NewRequest(ctx, http.MethodPut, "/v2/config/"+key, bytes.NewReader(body))
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	req.Header.Set("content-type", "application/json")
+	_, err = f.client.Do(req, nil) // nolint:bodyclose
+	return fmt.Errorf("%w", err)
+}
+
+// Del destroys config item via given key.
+func (f *ConfigService) Del(ctx context.Context, key string) error {
+	req, err := f.client.NewRequest(ctx, http.MethodDelete, "/v2/config/"+key, nil)
+	if err != nil {
+		return fmt.Errorf("%w", err)
+	}
+	_, err = f.client.Do(req, nil) // nolint:bodyclose
+	return fmt.Errorf("%w", err)
+}
diff --git a/debian/changelog b/debian/changelog
index 77a1201..5e4ae4f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-putdotio-go-putio (0.0~git20221111.2e102ad+ds-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 11 Dec 2022 06:22:45 -0000
+
 golang-github-putdotio-go-putio (0.0~git20190822.19b9c63-3) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/putio/doc.go b/doc.go
similarity index 100%
rename from putio/doc.go
rename to doc.go
diff --git a/error.go b/error.go
new file mode 100644
index 0000000..7aa2ca1
--- /dev/null
+++ b/error.go
@@ -0,0 +1,59 @@
+package putio
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+)
+
+// Sentinel errors.
+var (
+	ErrEmptyFolderName          = errors.New("empty folder name")
+	ErrNoFileIDIsGiven          = errors.New("no file id is given")
+	ErrFilenameCanNotBeEmpty    = errors.New("filename cannot be empty")
+	ErrNewFilenameCanNotBeEmpty = errors.New("new filename cannot be empty")
+	ErrInvalidPageNumber        = errors.New("invalid page number")
+	ErrNoQueryGiven             = errors.New("no query given")
+	ErrEmptySubtitleKey         = errors.New("empty subtitle key is given")
+	ErrNegativeTimeValue        = errors.New("time cannot be negative")
+	ErrNoFileIsGiven            = errors.New("no files given")
+	ErrEmptyUserName            = errors.New("empty username")
+	ErrEmptyURL                 = errors.New("empty URL")
+	ErrUnexpected               = errors.New("unexpected error")
+)
+
+// ErrorResponse reports the error caused by an API request.
+type ErrorResponse struct {
+	// Original http.Response
+	Response *http.Response `json:"-"`
+
+	// Body read from Response
+	Body []byte `json:"-"`
+
+	// Error while parsing the response
+	ParseError error
+
+	// These fileds are parsed from response if JSON.
+	Message string `json:"error_message"`
+	Type    string `json:"error_type"`
+}
+
+// nolint:goerr113
+func (e *ErrorResponse) Error() string {
+	if e.ParseError != nil {
+		return fmt.Errorf(
+			"cannot parse response. code:%d error:%w body:%q",
+			e.Response.StatusCode,
+			e.ParseError,
+			string(e.Body[:250]),
+		).Error()
+	}
+	return fmt.Sprintf(
+		"putio error. code:%d type:%q message:%q request:%v %v",
+		e.Response.StatusCode,
+		e.Type,
+		e.Message,
+		e.Response.Request.Method,
+		e.Response.Request.URL,
+	)
+}
diff --git a/putio/events.go b/events.go
similarity index 65%
rename from putio/events.go
rename to events.go
index 0b0db2f..4d3753c 100644
--- a/putio/events.go
+++ b/events.go
@@ -1,17 +1,21 @@
 package putio
 
-import "context"
+import (
+	"context"
+	"net/http"
+)
 
 // EventsService is the service to gather information about user's events.
 type EventsService struct {
 	client *Client
 }
 
+// nolint:godox
 // FIXME: events list returns inconsistent data structures.
 
 // List gets list of dashboard events. It includes downloads and share events.
 func (e *EventsService) List(ctx context.Context) ([]Event, error) {
-	req, err := e.client.NewRequest(ctx, "GET", "/v2/events/list", nil)
+	req, err := e.client.NewRequest(ctx, http.MethodGet, "/v2/events/list", nil)
 	if err != nil {
 		return nil, err
 	}
@@ -19,23 +23,22 @@ func (e *EventsService) List(ctx context.Context) ([]Event, error) {
 	var r struct {
 		Events []Event
 	}
-	_, err = e.client.Do(req, &r)
+	_, err = e.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return nil, err
 	}
 	return r.Events, nil
-
 }
 
-// Delete Clears all all dashboard events.
+// Delete Clears all dashboard events.
 func (e *EventsService) Delete(ctx context.Context) error {
-	req, err := e.client.NewRequest(ctx, "POST", "/v2/events/delete", nil)
+	req, err := e.client.NewRequest(ctx, http.MethodPost, "/v2/events/delete", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = e.client.Do(req, &struct{}{})
+	_, err = e.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
diff --git a/putio/events_test.go b/events_test.go
similarity index 95%
rename from putio/events_test.go
rename to events_test.go
index b8f2397..a7fc079 100644
--- a/putio/events_test.go
+++ b/events_test.go
@@ -35,7 +35,7 @@ func TestEvents_List(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/events/list", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -58,7 +58,7 @@ func TestEvents_Delete(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/events/delete", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
diff --git a/putio/example_test.go b/example_test.go
similarity index 92%
rename from putio/example_test.go
rename to example_test.go
index bf4cf6c..bac3b63 100644
--- a/putio/example_test.go
+++ b/example_test.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"log"
 
-	"github.com/putdotio/go-putio/putio"
+	"github.com/putdotio/go-putio"
 	"golang.org/x/oauth2"
 )
 
diff --git a/putio/files.go b/files.go
similarity index 64%
rename from putio/files.go
rename to files.go
index 079ab49..596b5fb 100644
--- a/putio/files.go
+++ b/files.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"mime/multipart"
+	"net/http"
 	"net/url"
 	"strconv"
 	"strings"
@@ -20,7 +21,7 @@ type FilesService struct {
 
 // Get fetches file metadata for given file ID.
 func (f *FilesService) Get(ctx context.Context, id int64) (File, error) {
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id), nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id), nil)
 	if err != nil {
 		return File{}, err
 	}
@@ -28,7 +29,7 @@ func (f *FilesService) Get(ctx context.Context, id int64) (File, error) {
 	var r struct {
 		File File `json:"file"`
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return File{}, err
 	}
@@ -37,7 +38,7 @@ func (f *FilesService) Get(ctx context.Context, id int64) (File, error) {
 
 // List fetches children for given directory ID.
 func (f *FilesService) List(ctx context.Context, id int64) (children []File, parent File, err error) {
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/list?parent_id="+itoa(id)+"&per_page=1000", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/list?parent_id="+itoa(id)+"&per_page=1000", nil)
 	if err != nil {
 		return
 	}
@@ -46,7 +47,7 @@ func (f *FilesService) List(ctx context.Context, id int64) (children []File, par
 		Parent File   `json:"parent"`
 		Cursor string `json:"cursor"`
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return
 	}
@@ -54,14 +55,14 @@ func (f *FilesService) List(ctx context.Context, id int64) (children []File, par
 	parent = r.Parent
 	for r.Cursor != "" {
 		body := strings.NewReader(`{"cursor": "` + r.Cursor + `"}`)
-		req, err = f.client.NewRequest(ctx, "POST", "/v2/files/list/continue", body)
+		req, err = f.client.NewRequest(ctx, http.MethodPost, "/v2/files/list/continue", body)
 		if err != nil {
 			return
 		}
 		req.Header.Set("content-type", "application/json")
 		r.Files = nil
 		r.Cursor = ""
-		_, err = f.client.Do(req, &r)
+		_, err = f.client.Do(req, &r) // nolint:bodyclose
 		if err != nil {
 			return
 		}
@@ -77,7 +78,7 @@ func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (strin
 		notunnel = "notunnel=0"
 	}
 
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/url?"+notunnel, nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/url?"+notunnel, nil)
 	if err != nil {
 		return "", err
 	}
@@ -86,7 +87,7 @@ func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (strin
 		URL string `json:"url"`
 	}
 
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return "", err
 	}
@@ -97,14 +98,14 @@ func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (strin
 // CreateFolder creates a new folder under parent.
 func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int64) (File, error) {
 	if name == "" {
-		return File{}, fmt.Errorf("empty folder name")
+		return File{}, ErrEmptyFolderName
 	}
 
 	params := url.Values{}
 	params.Set("name", name)
 	params.Set("parent_id", itoa(parent))
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/create-folder", strings.NewReader(params.Encode()))
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/create-folder", strings.NewReader(params.Encode()))
 	if err != nil {
 		return File{}, err
 	}
@@ -113,7 +114,7 @@ func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int
 	var r struct {
 		File File `json:"file"`
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return File{}, err
 	}
@@ -124,10 +125,10 @@ func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int
 // Delete deletes given files.
 func (f *FilesService) Delete(ctx context.Context, files ...int64) error {
 	if len(files) == 0 {
-		return fmt.Errorf("no file id is given")
+		return ErrNoFileIDIsGiven
 	}
 
-	var ids []string
+	ids := []string{}
 	for _, id := range files {
 		ids = append(ids, itoa(id))
 	}
@@ -135,13 +136,13 @@ func (f *FilesService) Delete(ctx context.Context, files ...int64) error {
 	params := url.Values{}
 	params.Set("file_ids", strings.Join(ids, ","))
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/delete", strings.NewReader(params.Encode()))
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/delete", strings.NewReader(params.Encode()))
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -151,20 +152,20 @@ func (f *FilesService) Delete(ctx context.Context, files ...int64) error {
 // Rename change the name of the file to newname.
 func (f *FilesService) Rename(ctx context.Context, id int64, newname string) error {
 	if newname == "" {
-		return fmt.Errorf("new filename cannot be empty")
+		return ErrNewFilenameCanNotBeEmpty
 	}
 
 	params := url.Values{}
 	params.Set("file_id", itoa(id))
 	params.Set("name", newname)
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/rename", strings.NewReader(params.Encode()))
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/rename", strings.NewReader(params.Encode()))
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -175,10 +176,10 @@ func (f *FilesService) Rename(ctx context.Context, id int64, newname string) err
 // Move moves files to the given destination.
 func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) error {
 	if len(files) == 0 {
-		return fmt.Errorf("no files given")
+		return ErrNoFileIsGiven
 	}
 
-	var ids []string
+	ids := []string{}
 	for _, file := range files {
 		ids = append(ids, itoa(file))
 	}
@@ -187,13 +188,13 @@ func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) e
 	params.Set("file_ids", strings.Join(ids, ","))
 	params.Set("parent_id", itoa(parent))
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode()))
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/move", strings.NewReader(params.Encode()))
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -202,58 +203,58 @@ func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) e
 
 // Upload reads from given io.Reader and uploads the file contents to Put.io
 // servers under directory given by parent. If parent is negative, user's
-// prefered folder is used.
+// preferred folder is used.
 //
 // If the uploaded file is a torrent file, Put.io will interpret it as a
 // transfer and Transfer field will be present to represent the status of the
 // tranfer. Likewise, if the uploaded file is a regular file, Transfer field
 // would be nil and the uploaded file will be represented by the File field.
 //
-// This method reads the file contents into the memory, so it should be used for
-// <150MB files.
+// This method reads the file contents into the memory, so it should only be used for small files.
+// Use UploadService for larger files.
 func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string, parent int64) (Upload, error) {
 	if filename == "" {
-		return Upload{}, fmt.Errorf("filename cannot be empty")
+		return Upload{}, ErrFilenameCanNotBeEmpty
 	}
 
 	var buf bytes.Buffer
 	mw := multipart.NewWriter(&buf)
 
-	// negative parent means use user's prefered download folder.
+	// negative parent means use user's preferred download folder.
 	if parent >= 0 {
 		err := mw.WriteField("parent_id", itoa(parent))
 		if err != nil {
-			return Upload{}, err
+			return Upload{}, fmt.Errorf("%w", err)
 		}
 	}
 
 	formfile, err := mw.CreateFormFile("file", filename)
 	if err != nil {
-		return Upload{}, err
+		return Upload{}, fmt.Errorf("%w", err)
 	}
 
 	_, err = io.Copy(formfile, r)
 	if err != nil {
-		return Upload{}, err
+		return Upload{}, fmt.Errorf("%w", err)
 	}
 
 	err = mw.Close()
 	if err != nil {
-		return Upload{}, err
+		return Upload{}, fmt.Errorf("%w", err)
 	}
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/upload", &buf)
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "$upload$", &buf)
 	if err != nil {
-		return Upload{}, err
+		return Upload{}, fmt.Errorf("%w", err)
 	}
 	req.Header.Set("Content-Type", mw.FormDataContentType())
 
 	var response struct {
 		Upload
 	}
-	_, err = f.client.Do(req, &response)
+	_, err = f.client.Do(req, &response) // nolint:bodyclose
 	if err != nil {
-		return Upload{}, err
+		return Upload{}, fmt.Errorf("%w", err)
 	}
 	return response.Upload, nil
 }
@@ -263,21 +264,21 @@ func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string,
 // page is -1, all results are returned.
 func (f *FilesService) Search(ctx context.Context, query string, page int64) (Search, error) {
 	if page == 0 || page < -1 {
-		return Search{}, fmt.Errorf("invalid page number")
+		return Search{}, ErrInvalidPageNumber
 	}
 	if query == "" {
-		return Search{}, fmt.Errorf("no query given")
+		return Search{}, ErrNoQueryGiven
 	}
 
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/search/"+query+"/page/"+itoa(page), nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/search/"+query+"/page/"+itoa(page), nil)
 	if err != nil {
-		return Search{}, err
+		return Search{}, fmt.Errorf("%w", err)
 	}
 
 	var r Search
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
-		return Search{}, err
+		return Search{}, fmt.Errorf("%w", err)
 	}
 
 	return r, nil
@@ -285,17 +286,17 @@ func (f *FilesService) Search(ctx context.Context, query string, page int64) (Se
 
 // Shared returns list of shared files and share information.
 func (f *FilesService) shared(ctx context.Context) ([]share, error) {
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/shared", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/shared", nil)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	var r struct {
 		Shared []share
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	return r.Shared, nil
@@ -303,40 +304,41 @@ func (f *FilesService) shared(ctx context.Context) ([]share, error) {
 
 // SharedWith returns list of users the given file is shared with.
 func (f *FilesService) sharedWith(ctx context.Context, id int64) ([]share, error) {
+	// nolint:godox
 	// FIXME: shared-with returns different json structure than /shared/
 	// endpoint. so it's not an exported method until a common structure is
 	// decided
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/shared-with", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/shared-with", nil)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	var r struct {
 		Shared []share `json:"shared-with"`
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	return r.Shared, nil
 }
 
-// Subtitles lists available subtitles for the given file for user's prefered
+// Subtitles lists available subtitles for the given file for user's preferred
 // subtitle language.
 func (f *FilesService) Subtitles(ctx context.Context, id int64) ([]Subtitle, error) {
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/subtitles", nil)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	var r struct {
 		Subtitles []Subtitle
 		Default   string
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	return r.Subtitles, nil
@@ -348,18 +350,22 @@ func (f *FilesService) Subtitles(ctx context.Context, id int64) ([]Subtitle, err
 // - A subtitle file that has identical parent folder and name with the video.
 // - Subtitle file extracted from video if the format is MKV.
 // - First match from OpenSubtitles.org.
-func (f *FilesService) DownloadSubtitle(ctx context.Context, id int64, key string, format string) (io.ReadCloser, error) {
+func (f *FilesService) DownloadSubtitle(
+	ctx context.Context,
+	id int64,
+	key string,
+) (io.ReadCloser, error) {
 	if key == "" {
 		key = "default"
 	}
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles/"+key, nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/subtitles/"+key, nil)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
-	resp, err := f.client.Do(req, nil)
+	resp, err := f.client.Do(req, nil) // nolint:bodyclose
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	return resp.Body, nil
@@ -369,17 +375,22 @@ func (f *FilesService) DownloadSubtitle(ctx context.Context, id int64, key strin
 // subtitleKey to get available subtitles for user’s preferred languages.
 func (f *FilesService) HLSPlaylist(ctx context.Context, id int64, subtitleKey string) (io.ReadCloser, error) {
 	if subtitleKey == "" {
-		return nil, fmt.Errorf("empty subtitle key is given")
+		return nil, ErrEmptySubtitleKey
 	}
 
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/hls/media.m3u8?subtitle_key"+subtitleKey, nil)
+	req, err := f.client.NewRequest(
+		ctx,
+		http.MethodGet,
+		"/v2/files/"+itoa(id)+"/hls/media.m3u8?subtitle_key"+subtitleKey,
+		nil,
+	)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
-	resp, err := f.client.Do(req, nil)
+	resp, err := f.client.Do(req, nil) // nolint:bodyclose
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("%w", err)
 	}
 
 	return resp.Body, nil
@@ -388,21 +399,26 @@ func (f *FilesService) HLSPlaylist(ctx context.Context, id int64, subtitleKey st
 // SetVideoPosition sets default video position for a video file.
 func (f *FilesService) SetVideoPosition(ctx context.Context, id int64, t int) error {
 	if t < 0 {
-		return fmt.Errorf("time cannot be negative")
+		return ErrNegativeTimeValue
 	}
 
 	params := url.Values{}
 	params.Set("time", strconv.Itoa(t))
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/"+itoa(id)+"/start-from", strings.NewReader(params.Encode()))
+	req, err := f.client.NewRequest(
+		ctx,
+		http.MethodPost,
+		"/v2/files/"+itoa(id)+"/start-from",
+		strings.NewReader(params.Encode()),
+	)
 	if err != nil {
-		return err
+		return fmt.Errorf("%w", err)
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
-		return err
+		return fmt.Errorf("%w", err)
 	}
 
 	return nil
@@ -410,15 +426,15 @@ func (f *FilesService) SetVideoPosition(ctx context.Context, id int64, t int) er
 
 // DeleteVideoPosition deletes video position for a video file.
 func (f *FilesService) DeleteVideoPosition(ctx context.Context, id int64) error {
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/"+itoa(id)+"/start-from/delete", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/"+itoa(id)+"/start-from/delete", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
-		return err
+		return fmt.Errorf("%w", err)
 	}
 
 	return nil
diff --git a/putio/files_test.go b/files_test.go
similarity index 91%
rename from putio/files_test.go
rename to files_test.go
index 99d6262..4f3793e 100644
--- a/putio/files_test.go
+++ b/files_test.go
@@ -3,6 +3,7 @@ package putio
 import (
 	"bytes"
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -30,14 +31,15 @@ func TestFiles_Get(t *testing.T) {
 		"opensubtitles_hash": null,
 		"parent_id": 123,
 		"screenshot": null,
-		"size": 92
+		"size": 92,
+		"file_type": "TEXT"
 	},
     "status": "OK"
 }
 `
 
 	mux.HandleFunc("/v2/files/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 	mux.HandleFunc("/v2/files/2", http.NotFound)
@@ -47,6 +49,10 @@ func TestFiles_Get(t *testing.T) {
 		t.Error(err)
 	}
 
+	if file.FileType != FileTypeText {
+		t.Errorf("got: %v, want: %v", file.FileType, FileTypeText)
+	}
+
 	if file.Size != 92 {
 		t.Errorf("got: %v, want: 92", file.Size)
 	}
@@ -56,8 +62,10 @@ func TestFiles_Get(t *testing.T) {
 	if err == nil {
 		t.Fatal("must not return nil")
 	}
-	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
-		t.Errorf("error: %s, excepted: 404", err)
+
+	var myErr *ErrorResponse
+	if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+		t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
 	}
 }
 
@@ -81,7 +89,8 @@ func TestFiles_List(t *testing.T) {
 		"opensubtitles_hash": null,
 		"parent_id": 123,
 		"screenshot": null,
-		"size": 92
+		"size": 92,
+		"file_type": "TEXT"
 	},
 	{
 		"content_type": "video/x-matroska",
@@ -96,7 +105,8 @@ func TestFiles_List(t *testing.T) {
 		"opensubtitles_hash": "acc2785ffa573c69",
 		"parent_id": 123,
 		"screenshot": "https://put.io/screenshots/aF5rkZVtYV9pV1iWimSOZWJjWWFaXGZdaZBmY2OJY4uJlV5pj5FiXg%3D%3D.jpg",
-		"size": 1155197659
+		"size": 1155197659,
+		"file_type": "VIDEO"
 	}
 ],
 "parent": {
@@ -112,13 +122,14 @@ func TestFiles_List(t *testing.T) {
 	"opensubtitles_hash": null,
 	"parent_id": 0,
 	"screenshot": null,
-	"size": 1155197751
+	"size": 1155197751,
+	"file_type": "FOLDER"
 },
 "status": "OK"
 }
 `
 	mux.HandleFunc("/v2/files/list", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 
 		// not found handler
 		parentID := r.URL.Query().Get("parent_id")
@@ -147,8 +158,10 @@ func TestFiles_List(t *testing.T) {
 	if err == nil {
 		t.Fatal("must not return nil")
 	}
-	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
-		t.Errorf("error: %s, excepted: 404", err)
+
+	var myErr *ErrorResponse
+	if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+		t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
 	}
 }
 
@@ -182,7 +195,7 @@ func TestFiles_CreateFolder(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/files/create-folder", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, fixture)
 	})
@@ -208,7 +221,7 @@ func TestFiles_Delete(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/files/delete", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status": "OK"}`)
 	})
@@ -230,7 +243,7 @@ func TestFiles_Rename(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/files/rename", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
@@ -252,7 +265,7 @@ func TestFiles_Move(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/files/move", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
@@ -345,7 +358,7 @@ func TestFiles_Search(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/files/search/", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -380,7 +393,7 @@ func TestFiles_SetVideoPosition(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/files/1/start-from", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"statutus":"OK"}`)
 	})
@@ -402,7 +415,7 @@ func TestFiles_DeleteVideoPosition(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/files/1/start-from/delete", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"statutus":"OK"}`)
 	})
@@ -443,7 +456,7 @@ http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/2540_vod.m3u8
 http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/3340_vod.m3u8
 `
 	mux.HandleFunc("/v2/files/1/hls/media.m3u8", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		http.ServeContent(w, r, "media.m3u8", time.Now().UTC(), strings.NewReader(sampleHLS))
 	})
 
@@ -451,7 +464,9 @@ http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/3340_vod.m3u8
 	if err != nil {
 		t.Error(err)
 	}
-	defer body.Close()
+	defer func() {
+		_ = body.Close()
+	}()
 
 	var buf bytes.Buffer
 	_, err = io.Copy(&buf, body)
@@ -497,7 +512,7 @@ func TestFiles_Shared(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/files/shared", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -537,7 +552,7 @@ func TestFiles_SharedWith(t *testing.T) {
 `
 
 	mux.HandleFunc("/v2/files/1/shared-with", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -577,7 +592,7 @@ func TestFiles_Subtitles(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/files/1/subtitles", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -616,7 +631,7 @@ Let's go down.
 - Three out, eight left.
 `
 	mux.HandleFunc("/v2/files/1/subtitles/", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		// trim leading and trailing slashes and split the url path
 		f := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
 
@@ -639,11 +654,13 @@ Let's go down.
 	})
 
 	// valid file ID and valid key
-	rc, err := client.Files.DownloadSubtitle(context.Background(), 1, "key0", "")
+	rc, err := client.Files.DownloadSubtitle(context.Background(), 1, "key0")
 	if err != nil {
 		t.Error(err)
 	}
-	defer rc.Close()
+	defer func() {
+		_ = rc.Close()
+	}()
 
 	var buf bytes.Buffer
 	_, err = io.Copy(&buf, rc)
@@ -655,25 +672,31 @@ Let's go down.
 	}
 
 	// negative file ID
-	rc, err = client.Files.DownloadSubtitle(context.Background(), -1, "key0", "")
+	rc, err = client.Files.DownloadSubtitle(context.Background(), -1, "key0")
 	if err == nil {
-		defer rc.Close()
+		defer func() {
+			_ = rc.Close()
+		}()
 		t.Errorf("negative file ID accepted")
 	}
 
 	// invalid key
-	rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "key3", "")
+	rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "key3")
 	if err == nil {
-		defer rc.Close()
+		defer func() {
+			_ = rc.Close()
+		}()
 		t.Errorf("invalid key accepted")
 	}
 
 	// empty key
-	rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "", "")
+	rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "")
 	if err != nil {
 		t.Error(err)
 	}
-	defer rc.Close()
+	defer func() {
+		_ = rc.Close()
+	}()
 
 	buf.Reset()
 	_, err = io.Copy(&buf, rc)
diff --git a/putio/friends.go b/friends.go
similarity index 65%
rename from putio/friends.go
rename to friends.go
index cf29761..462e7df 100644
--- a/putio/friends.go
+++ b/friends.go
@@ -2,7 +2,7 @@ package putio
 
 import (
 	"context"
-	"fmt"
+	"net/http"
 )
 
 // FriendsService is the service to operate on user friends.
@@ -12,7 +12,7 @@ type FriendsService struct {
 
 // List lists users friends.
 func (f *FriendsService) List(ctx context.Context) ([]Friend, error) {
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/friends/list", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/friends/list", nil)
 	if err != nil {
 		return nil, err
 	}
@@ -21,7 +21,7 @@ func (f *FriendsService) List(ctx context.Context) ([]Friend, error) {
 		Friends []Friend
 		Total   int
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return nil, err
 	}
@@ -31,7 +31,7 @@ func (f *FriendsService) List(ctx context.Context) ([]Friend, error) {
 
 // WaitingRequests lists user's pending friend requests.
 func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error) {
-	req, err := f.client.NewRequest(ctx, "GET", "/v2/friends/waiting-requests", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/friends/waiting-requests", nil)
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +39,7 @@ func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error)
 	var r struct {
 		Friends []Friend
 	}
-	_, err = f.client.Do(req, &r)
+	_, err = f.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return nil, err
 	}
@@ -50,15 +50,15 @@ func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error)
 // Request sends a friend request to the given username.
 func (f *FriendsService) Request(ctx context.Context, username string) error {
 	if username == "" {
-		return fmt.Errorf("empty username")
+		return ErrEmptyUserName
 	}
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/request", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/request", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -69,16 +69,16 @@ func (f *FriendsService) Request(ctx context.Context, username string) error {
 // Approve approves a friend request from the given username.
 func (f *FriendsService) Approve(ctx context.Context, username string) error {
 	if username == "" {
-		return fmt.Errorf("empty username")
+		return ErrEmptyUserName
 	}
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/approve", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/approve", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -88,16 +88,16 @@ func (f *FriendsService) Approve(ctx context.Context, username string) error {
 // Deny denies a friend request from the given username.
 func (f *FriendsService) Deny(ctx context.Context, username string) error {
 	if username == "" {
-		return fmt.Errorf("empty username")
+		return ErrEmptyUserName
 	}
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/deny", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/deny", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -107,16 +107,16 @@ func (f *FriendsService) Deny(ctx context.Context, username string) error {
 // Unfriend removed friend from user's friend list.
 func (f *FriendsService) Unfriend(ctx context.Context, username string) error {
 	if username == "" {
-		return fmt.Errorf("empty username")
+		return ErrEmptyUserName
 	}
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/unfriend", nil)
+	req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/unfriend", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = f.client.Do(req, &struct{}{})
+	_, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
diff --git a/putio/friends_test.go b/friends_test.go
similarity index 94%
rename from putio/friends_test.go
rename to friends_test.go
index 4f63ec1..2d0bf36 100644
--- a/putio/friends_test.go
+++ b/friends_test.go
@@ -46,7 +46,7 @@ func TestFriends_List(t *testing.T) {
 `
 
 	mux.HandleFunc("/v2/friends/list", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -91,7 +91,7 @@ func TestFriends_WaitingRequests(t *testing.T) {
 `
 
 	mux.HandleFunc("/v2/friends/waiting-requests", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -118,7 +118,7 @@ func TestFriends_Request(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/friends/annie/request", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
@@ -140,7 +140,7 @@ func TestFriends_Approve(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/friends/bob/approve", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
@@ -162,7 +162,7 @@ func TestFriends_Deny(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/friends/andy/deny", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
@@ -184,7 +184,7 @@ func TestFriends_Unfriend(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/friends/lin/unfriend", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..ba49599
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/putdotio/go-putio
+
+go 1.13
+
+require golang.org/x/oauth2 v0.2.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..75f5822
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,47 @@
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
+golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/putio/error.go b/putio/error.go
deleted file mode 100644
index 6597278..0000000
--- a/putio/error.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package putio
-
-import (
-	"fmt"
-	"net/http"
-)
-
-// ErrorResponse reports the error caused by an API request.
-type ErrorResponse struct {
-	Response *http.Response `json:"-"`
-
-	Message string `json:"error_message"`
-	Type    string `json:"error_type"`
-}
-
-func (e *ErrorResponse) Error() string {
-	return fmt.Sprintf(
-		"Type: %v Message: %q. Original error: %v %v: %v",
-		e.Type,
-		e.Message,
-		e.Response.Request.Method,
-		e.Response.Request.URL,
-		e.Response.Status,
-	)
-}
diff --git a/putio/time.go b/time.go
similarity index 84%
rename from putio/time.go
rename to time.go
index 7684ce8..425b9b3 100644
--- a/putio/time.go
+++ b/time.go
@@ -1,6 +1,9 @@
 package putio
 
-import "time"
+import (
+	"fmt"
+	"time"
+)
 
 // Time is a wrapper around time.Time that can be unmarshalled from a JSON
 // string formatted as "2016-04-19T15:44:42". All methods of time.Time can be
@@ -17,7 +20,7 @@ func (t *Time) String() string {
 func (t *Time) UnmarshalJSON(data []byte) error {
 	// put.io API has inconsistent time layouts for different endpoints, such
 	// as /files and /events
-	var timeLayouts = []string{`"2006-01-02T15:04:05"`, `"2006-01-02 15:04:05"`}
+	timeLayouts := []string{`"2006-01-02T15:04:05"`, `"2006-01-02 15:04:05"`}
 
 	s := string(data)
 	var err error
@@ -29,5 +32,5 @@ func (t *Time) UnmarshalJSON(data []byte) error {
 			return nil
 		}
 	}
-	return err
+	return fmt.Errorf("%w", err)
 }
diff --git a/putio/transfers.go b/transfers.go
similarity index 74%
rename from putio/transfers.go
rename to transfers.go
index 627dff4..6726515 100644
--- a/putio/transfers.go
+++ b/transfers.go
@@ -2,7 +2,7 @@ package putio
 
 import (
 	"context"
-	"fmt"
+	"net/http"
 	"net/url"
 	"strings"
 )
@@ -16,7 +16,7 @@ type TransfersService struct {
 // List lists all active transfers. If a transfer is completed, it will not be
 // available in response.
 func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) {
-	req, err := t.client.NewRequest(ctx, "GET", "/v2/transfers/list", nil)
+	req, err := t.client.NewRequest(ctx, http.MethodGet, "/v2/transfers/list", nil)
 	if err != nil {
 		return nil, err
 	}
@@ -24,7 +24,7 @@ func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) {
 	var r struct {
 		Transfers []Transfer
 	}
-	_, err = t.client.Do(req, &r)
+	_, err = t.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return nil, err
 	}
@@ -38,7 +38,7 @@ func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) {
 // used to send a POST request after the transfer is finished downloading.
 func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64, callbackURL string) (Transfer, error) {
 	if urlStr == "" {
-		return Transfer{}, fmt.Errorf("empty URL")
+		return Transfer{}, ErrEmptyURL
 	}
 
 	params := url.Values{}
@@ -52,7 +52,7 @@ func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64,
 		params.Set("callback_url", callbackURL)
 	}
 
-	req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/add", strings.NewReader(params.Encode()))
+	req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/add", strings.NewReader(params.Encode()))
 	if err != nil {
 		return Transfer{}, err
 	}
@@ -61,7 +61,7 @@ func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64,
 	var r struct {
 		Transfer Transfer
 	}
-	_, err = t.client.Do(req, &r)
+	_, err = t.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return Transfer{}, err
 	}
@@ -71,7 +71,7 @@ func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64,
 
 // Get returns the given transfer's properties.
 func (t *TransfersService) Get(ctx context.Context, id int64) (Transfer, error) {
-	req, err := t.client.NewRequest(ctx, "GET", "/v2/transfers/"+itoa(id), nil)
+	req, err := t.client.NewRequest(ctx, http.MethodGet, "/v2/transfers/"+itoa(id), nil)
 	if err != nil {
 		return Transfer{}, err
 	}
@@ -79,7 +79,7 @@ func (t *TransfersService) Get(ctx context.Context, id int64) (Transfer, error)
 	var r struct {
 		Transfer Transfer
 	}
-	_, err = t.client.Do(req, &r)
+	_, err = t.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return Transfer{}, err
 	}
@@ -92,7 +92,7 @@ func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error
 	params := url.Values{}
 	params.Set("id", itoa(id))
 
-	req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/retry", strings.NewReader(params.Encode()))
+	req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/retry", strings.NewReader(params.Encode()))
 	if err != nil {
 		return Transfer{}, err
 	}
@@ -101,7 +101,7 @@ func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error
 	var r struct {
 		Transfer Transfer
 	}
-	_, err = t.client.Do(req, &r)
+	_, err = t.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return Transfer{}, err
 	}
@@ -112,10 +112,10 @@ func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error
 // Cancel deletes given transfers.
 func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error {
 	if len(ids) == 0 {
-		return fmt.Errorf("no id given")
+		return ErrNoFileIDIsGiven
 	}
 
-	var transfers []string
+	transfers := []string{}
 	for _, id := range ids {
 		transfers = append(transfers, itoa(id))
 	}
@@ -123,13 +123,13 @@ func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error {
 	params := url.Values{}
 	params.Set("transfer_ids", strings.Join(transfers, ","))
 
-	req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/cancel", strings.NewReader(params.Encode()))
+	req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/cancel", strings.NewReader(params.Encode()))
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = t.client.Do(req, &struct{}{})
+	_, err = t.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
@@ -139,13 +139,13 @@ func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error {
 
 // Clean removes completed transfers from the transfer list.
 func (t *TransfersService) Clean(ctx context.Context) error {
-	req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/clean", nil)
+	req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/clean", nil)
 	if err != nil {
 		return err
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
-	_, err = t.client.Do(req, &struct{}{})
+	_, err = t.client.Do(req, &struct{}{}) // nolint:bodyclose
 	if err != nil {
 		return err
 	}
diff --git a/putio/transfers_test.go b/transfers_test.go
similarity index 91%
rename from putio/transfers_test.go
rename to transfers_test.go
index 53985cc..e381e4d 100644
--- a/putio/transfers_test.go
+++ b/transfers_test.go
@@ -2,6 +2,7 @@ package putio
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -56,7 +57,7 @@ func TestTransfers_Get(t *testing.T) {
 	}
 	`
 	mux.HandleFunc("/v2/transfers/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -138,7 +139,7 @@ func TestTransfers_List(t *testing.T) {
 	`
 
 	mux.HandleFunc("/v2/transfers/list", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -204,7 +205,7 @@ func TestTransfers_Add(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/transfers/add", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 
 		// form values
@@ -217,7 +218,12 @@ func TestTransfers_Add(t *testing.T) {
 		fmt.Fprintln(w, fixture)
 	})
 
-	transfer, err := client.Transfers.Add(context.Background(), "http://releases.ubuntu.com/16.04/ubuntu-16.04-desktop-amd64.iso.torrent", 0, "")
+	transfer, err := client.Transfers.Add(
+		context.Background(),
+		"http://releases.ubuntu.com/16.04/ubuntu-16.04-desktop-amd64.iso.torrent",
+		0,
+		"",
+	)
 	if err != nil {
 		t.Error(err)
 	}
@@ -232,7 +238,7 @@ func TestTransfers_Add(t *testing.T) {
 		t.Errorf("empty URL accepted")
 	}
 
-	// negative parent folder means use the user's prefered download folder.
+	// negative parent folder means use the user's preferred download folder.
 	_, err = client.Transfers.Add(context.Background(), "filepath", -1, "")
 	if err != nil {
 		t.Error(err)
@@ -293,7 +299,7 @@ func TestTransfers_Retry(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/transfers/retry", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 
 		id := r.FormValue("id")
@@ -319,8 +325,9 @@ func TestTransfers_Retry(t *testing.T) {
 	if err == nil {
 		t.Fatal("must not return nil")
 	}
-	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
-		t.Errorf("error: %s, excepted: 404", err)
+	var myErr *ErrorResponse
+	if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+		t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
 	}
 }
 
@@ -338,7 +345,7 @@ func TestTransfers_Cancel(t *testing.T) {
 	}
 
 	mux.HandleFunc("/v2/transfers/cancel", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 
 		transferIdsStr := r.FormValue("transfer_ids")
@@ -376,8 +383,9 @@ func TestTransfers_Cancel(t *testing.T) {
 	if err == nil {
 		t.Fatal("must not return nil")
 	}
-	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
-		t.Errorf("error: %s, excepted: 404", err)
+	var myErr *ErrorResponse
+	if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+		t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
 	}
 }
 
@@ -386,7 +394,7 @@ func TestTransfers_Clean(t *testing.T) {
 	defer teardown()
 
 	mux.HandleFunc("/v2/transfers/clean", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, `{"status":"OK"}`)
 	})
diff --git a/putio/types.go b/types.go
similarity index 93%
rename from putio/types.go
rename to types.go
index 6a5ae84..7b2b76f 100644
--- a/putio/types.go
+++ b/types.go
@@ -2,6 +2,19 @@ package putio
 
 import "fmt"
 
+// File types.
+const (
+	FileTypeFolder  string = "FOLDER"
+	FileTypeFile    string = "FILE"
+	FileTypeAudio   string = "AUDIO"
+	FileTypeVideo   string = "VIDEO"
+	FileTypeImage   string = "IMAGE"
+	FileTypeArchive string = "ARCHIVE"
+	FileTypePDF     string = "PDF"
+	FileTypeText    string = "TEXT"
+	FileTypeSWF     string = "SWF"
+)
+
 // File represents a Put.io file.
 type File struct {
 	ID                int64  `json:"id"`
@@ -18,6 +31,7 @@ type File struct {
 	Icon              string `json:"icon"`
 	CRC32             string `json:"crc32"`
 	IsShared          bool   `json:"is_shared"`
+	FileType          string `json:"file_type"`
 }
 
 func (f *File) String() string {
@@ -50,6 +64,7 @@ type Transfer struct {
 	CreatedTorrent bool   `json:"created_torrent"`
 	ClientIP       string `json:"client_ip"`
 
+	// nolint:godox
 	// FIXME: API returns either string or float non-deterministically.
 	// CurrentRatio       float32 `json:"current_ratio"`
 
diff --git a/upload.go b/upload.go
new file mode 100644
index 0000000..bab9661
--- /dev/null
+++ b/upload.go
@@ -0,0 +1,190 @@
+package putio
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// UploadService uses TUS (resumable upload protocol) for sending files to put.io.
+type UploadService struct {
+	// Log is a user supplied function to collect log messages from upload methods.
+	Log func(message string)
+
+	client *Client
+}
+
+func (u *UploadService) log(message string) {
+	if u.Log != nil {
+		u.Log(message)
+	}
+}
+
+// CreateUpload is used for beginning new upload. Use returned location in SendFile function.
+func (u *UploadService) CreateUpload(
+	ctx context.Context,
+	filename string,
+	parentID, length int64,
+	overwrite bool,
+) (location string, err error) {
+	u.log(fmt.Sprintf("Creating upload %q at parent=%d", filename, parentID))
+	req, err := u.client.NewRequest(ctx, http.MethodPost, "$upload-tus$", nil)
+	if err != nil {
+		return
+	}
+	metadata := map[string]string{
+		"name":       filename,
+		"parent_id":  strconv.FormatInt(parentID, 10),
+		"no-torrent": "true",
+		"overwrite":  strconv.FormatBool(overwrite),
+	}
+	req.Header.Set("Content-Length", "0")
+	req.Header.Set("Upload-Length", strconv.FormatInt(length, 10))
+	req.Header.Set("Upload-Metadata", encodeMetadata(metadata))
+
+	resp, err := u.client.client.Do(req)
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusCreated {
+		err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+		return
+	}
+	location = resp.Header.Get("Location")
+	return
+}
+
+// SendFile sends the contents of the file to put.io.
+// In case of an transmission error, you can resume upload but you have to get the correct offset from server by
+// calling GetOffset and must seek to the new offset on io.Reader.
+func (u *UploadService) SendFile(
+	ctx context.Context,
+	r io.Reader,
+	location string,
+	offset int64,
+) (fileID int64, crc32 string, err error) {
+	u.log(fmt.Sprintf("Sending file %q offset=%d", location, offset))
+
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	// Stop upload if speed is too slow.
+	// Wrap reader so each read call resets the timer that cancels the request on certain duration.
+	if u.client.Timeout > 0 {
+		r = &timerResetReader{r: r, timer: time.AfterFunc(u.client.Timeout, cancel), timeout: u.client.Timeout}
+	}
+
+	req, err := u.client.NewRequest(ctx, http.MethodPatch, location, r)
+	if err != nil {
+		return
+	}
+
+	// putio.Client.NewRequests add another context for handling Client.Timeout. Replace it with original context.
+	// Request must not be cancelled on timeout because sending upload body takes a long time.
+	// We will be using timerResetReader for tracking uploaded bytes and doint cancellation there.
+	if u.client.Timeout > 0 {
+		req = req.WithContext(ctx)
+	}
+
+	req.Header.Set("content-type", "application/offset+octet-stream")
+	req.Header.Set("upload-offset", strconv.FormatInt(offset, 10))
+	resp, err := u.client.client.Do(req)
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusNoContent {
+		err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+		return
+	}
+	fileID, err = strconv.ParseInt(resp.Header.Get("putio-file-id"), 10, 64)
+	if err != nil {
+		err = fmt.Errorf("cannot parse putio-file-id header: %w", err)
+		return
+	}
+	crc32 = resp.Header.Get("putio-file-crc32")
+	return
+}
+
+// GetOffset returns the offset at the server.
+func (u *UploadService) GetOffset(ctx context.Context, location string) (n int64, err error) {
+	u.log(fmt.Sprintf("Getting upload offset %q", location))
+	req, err := u.client.NewRequest(ctx, http.MethodHead, location, nil)
+	if err != nil {
+		return
+	}
+
+	resp, err := u.client.client.Do(req)
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusOK {
+		err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+		return
+	}
+	n, err = strconv.ParseInt(resp.Header.Get("upload-offset"), 10, 64)
+	u.log(fmt.Sprintln("uploadJob offset:", n))
+	return n, fmt.Errorf("%w", err)
+}
+
+// TerminateUpload removes incomplete file from the server.
+func (u *UploadService) TerminateUpload(ctx context.Context, location string) (err error) {
+	u.log(fmt.Sprintf("Terminating upload %q", location))
+	req, err := u.client.NewRequest(ctx, http.MethodDelete, location, nil)
+	if err != nil {
+		return
+	}
+
+	resp, err := u.client.client.Do(req)
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusNoContent {
+		err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+		return
+	}
+	return nil
+}
+
+func encodeMetadata(metadata map[string]string) string {
+	encoded := make([]string, 0, len(metadata))
+	for k, v := range metadata {
+		encoded = append(encoded, fmt.Sprintf("%s %s", k, base64.StdEncoding.EncodeToString([]byte(v))))
+	}
+	return strings.Join(encoded, ",")
+}
+
+type timerResetReader struct {
+	r       io.Reader
+	timer   *time.Timer
+	timeout time.Duration
+}
+
+func (r *timerResetReader) Read(p []byte) (int, error) {
+	r.timer.Reset(r.timeout)
+	return r.r.Read(p) // nolint:wrapcheck
+}
diff --git a/putio/zips.go b/zips.go
similarity index 71%
rename from putio/zips.go
rename to zips.go
index 66e33cc..ee8d812 100644
--- a/putio/zips.go
+++ b/zips.go
@@ -2,7 +2,7 @@ package putio
 
 import (
 	"context"
-	"fmt"
+	"net/http"
 	"net/url"
 	"strings"
 )
@@ -14,24 +14,23 @@ type ZipsService struct {
 
 // Get gives detailed information about the given zip file id.
 func (z *ZipsService) Get(ctx context.Context, id int64) (Zip, error) {
-	req, err := z.client.NewRequest(ctx, "GET", "/v2/zips/"+itoa(id), nil)
+	req, err := z.client.NewRequest(ctx, http.MethodGet, "/v2/zips/"+itoa(id), nil)
 	if err != nil {
 		return Zip{}, err
 	}
 
 	var r Zip
-	_, err = z.client.Do(req, &r)
+	_, err = z.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return Zip{}, err
 	}
 
 	return r, nil
-
 }
 
 // List lists active zip files.
 func (z *ZipsService) List(ctx context.Context) ([]Zip, error) {
-	req, err := z.client.NewRequest(ctx, "GET", "/v2/zips/list", nil)
+	req, err := z.client.NewRequest(ctx, http.MethodGet, "/v2/zips/list", nil)
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +38,7 @@ func (z *ZipsService) List(ctx context.Context) ([]Zip, error) {
 	var r struct {
 		Zips []Zip
 	}
-	_, err = z.client.Do(req, &r)
+	_, err = z.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return nil, err
 	}
@@ -51,10 +50,10 @@ func (z *ZipsService) List(ctx context.Context) ([]Zip, error) {
 // a zip ID will be returned to keep track of zip process.
 func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, error) {
 	if len(fileIDs) == 0 {
-		return 0, fmt.Errorf("no file id given")
+		return 0, ErrNoFileIDIsGiven
 	}
 
-	var ids []string
+	ids := []string{}
 	for _, id := range fileIDs {
 		ids = append(ids, itoa(id))
 	}
@@ -62,7 +61,7 @@ func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, erro
 	params := url.Values{}
 	params.Set("file_ids", strings.Join(ids, ","))
 
-	req, err := z.client.NewRequest(ctx, "POST", "/v2/zips/create", strings.NewReader(params.Encode()))
+	req, err := z.client.NewRequest(ctx, http.MethodPost, "/v2/zips/create", strings.NewReader(params.Encode()))
 	if err != nil {
 		return 0, err
 	}
@@ -71,7 +70,7 @@ func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, erro
 	var r struct {
 		ID int64 `json:"zip_id"`
 	}
-	_, err = z.client.Do(req, &r)
+	_, err = z.client.Do(req, &r) // nolint:bodyclose
 	if err != nil {
 		return 0, err
 	}
diff --git a/putio/zips_test.go b/zips_test.go
similarity index 94%
rename from putio/zips_test.go
rename to zips_test.go
index f868d3a..0250923 100644
--- a/putio/zips_test.go
+++ b/zips_test.go
@@ -20,7 +20,7 @@ func TestZips_Get(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/zips/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -50,7 +50,7 @@ func TestZips_List(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/zips/list", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
+		testMethod(t, r, http.MethodGet)
 		fmt.Fprintln(w, fixture)
 	})
 
@@ -79,7 +79,7 @@ func TestZips_Create(t *testing.T) {
 }
 `
 	mux.HandleFunc("/v2/zips/create", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
+		testMethod(t, r, http.MethodPost)
 		testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
 		fmt.Fprintln(w, fixture)
 	})

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/account.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/account_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/client.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/client_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/config.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/doc.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/error.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/events.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/events_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/example_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/files.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/files_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/friends.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/friends_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/go.mod
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/go.sum
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/time.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/transfers.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/transfers_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/types.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/upload.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/zips.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/zips_test.go

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/account.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/account_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/client.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/client_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/doc.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/error.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/events.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/events_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/example_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/files.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/files_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/friends.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/friends_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/time.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/transfers.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/transfers_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/types.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/zips.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/putdotio/go-putio/putio/zips_test.go

No differences were encountered in the control files

More details

Full run details