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..6653ce1 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,12 @@ putio is a Go client library for accessing the [Put.io API v2](https://api.put.i
 
 ## 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 -u github.com/putdotio/go-putio
 ```
 
 ## Usage
@@ -23,21 +23,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 100%
rename from putio/account.go
rename to account.go
diff --git a/putio/account_test.go b/account_test.go
similarity index 100%
rename from putio/account_test.go
rename to account_test.go
diff --git a/putio/client.go b/client.go
similarity index 72%
rename from putio/client.go
rename to client.go
index ec0d9d7..c80df12 100644
--- a/putio/client.go
+++ b/client.go
@@ -3,11 +3,16 @@ package putio
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"strings"
+	"time"
+)
+
+const (
+	DefaultClientTimeout = 30 * time.Second
 )
 
 const (
@@ -15,6 +20,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 +31,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 +50,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 +63,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,6 +77,8 @@ 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
 }
@@ -80,7 +92,10 @@ func (c *Client) ValidateToken(ctx context.Context) (userID *int64, err error) {
 		UserID *int64 `json:"user_id"`
 	}
 	_, err = c.Do(req, &r)
-	return r.UserID, err
+	if err != nil {
+		return nil, err
+	}
+	return r.UserID, nil
 }
 
 // NewRequest creates an API request. A relative URL can be provided via
@@ -91,23 +106,28 @@ func (c *Client) NewRequest(ctx context.Context, method, relURL string, body io.
 		return nil, 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
 	}
-
-	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,6 +145,12 @@ 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
@@ -156,25 +182,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 98%
rename from putio/client_test.go
rename to client_test.go
index e475f27..a447b4b 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() {
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..4ac0056
--- /dev/null
+++ b/config.go
@@ -0,0 +1,100 @@
+package putio
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"net/http"
+)
+
+type ConfigService struct {
+	client *Client
+}
+
+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 err
+	}
+	var r struct {
+		Config json.RawMessage `json:"config"`
+	}
+	_, err = f.client.Do(req, &r)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(r.Config, &config)
+}
+
+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)
+	if err != nil {
+		return false, err
+	}
+	if r.Value == nil {
+		return false, nil
+	}
+	return true, json.Unmarshal(*r.Value, &value)
+}
+
+func (f *ConfigService) SetAll(ctx context.Context, config interface{}) error {
+	b, err := json.Marshal(config)
+	if err != nil {
+		return err
+	}
+	v := struct {
+		Config json.RawMessage `json:"config"`
+	}{
+		Config: b,
+	}
+	body, err := json.Marshal(v)
+	if err != nil {
+		return err
+	}
+	req, err := f.client.NewRequest(ctx, http.MethodPut, "/v2/config", bytes.NewReader(body))
+	if err != nil {
+		return err
+	}
+	req.Header.Set("content-type", "application/json")
+	_, err = f.client.Do(req, nil)
+	return err
+}
+
+func (f *ConfigService) Set(ctx context.Context, key string, value interface{}) error {
+	b, err := json.Marshal(value)
+	if err != nil {
+		return err
+	}
+	v := struct {
+		Value json.RawMessage `json:"value"`
+	}{
+		Value: b,
+	}
+	body, err := json.Marshal(v)
+	if err != nil {
+		return err
+	}
+	req, err := f.client.NewRequest(ctx, http.MethodPut, "/v2/config/"+key, bytes.NewReader(body))
+	if err != nil {
+		return err
+	}
+	req.Header.Set("content-type", "application/json")
+	_, err = f.client.Do(req, nil)
+	return err
+}
+
+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 err
+	}
+	_, err = f.client.Do(req, nil)
+	return err
+}
diff --git a/debian/changelog b/debian/changelog
index 2f144cf..10f04d0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-putdotio-go-putio (0.0~git20210607.4bb34fd-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 10 Mar 2022 16:12:35 -0000
+
 golang-github-putdotio-go-putio (0.0~git20190822.19b9c63-2) unstable; urgency=medium
 
   * Source-only upload.
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..5fbcd60
--- /dev/null
+++ b/error.go
@@ -0,0 +1,37 @@
+package putio
+
+import (
+	"fmt"
+	"net/http"
+)
+
+// 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"`
+}
+
+func (e *ErrorResponse) Error() string {
+	if e.ParseError != nil {
+		return fmt.Errorf("cannot parse response. code:%d error:%q body:%q",
+			e.Response.StatusCode, e.ParseError.Error(), 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 99%
rename from putio/events.go
rename to events.go
index 0b0db2f..b7605a4 100644
--- a/putio/events.go
+++ b/events.go
@@ -24,7 +24,6 @@ func (e *EventsService) List(ctx context.Context) ([]Event, error) {
 		return nil, err
 	}
 	return r.Events, nil
-
 }
 
 // Delete Clears all all dashboard events.
diff --git a/putio/events_test.go b/events_test.go
similarity index 100%
rename from putio/events_test.go
rename to events_test.go
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 97%
rename from putio/files.go
rename to files.go
index 079ab49..9962c04 100644
--- a/putio/files.go
+++ b/files.go
@@ -202,15 +202,15 @@ 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")
@@ -219,7 +219,7 @@ func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string,
 	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 {
@@ -242,7 +242,7 @@ func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string,
 		return Upload{}, err
 	}
 
-	req, err := f.client.NewRequest(ctx, "POST", "/v2/files/upload", &buf)
+	req, err := f.client.NewRequest(ctx, "POST", "$upload$", &buf)
 	if err != nil {
 		return Upload{}, err
 	}
@@ -322,7 +322,7 @@ func (f *FilesService) sharedWith(ctx context.Context, id int64) ([]share, error
 	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)
diff --git a/putio/files_test.go b/files_test.go
similarity index 100%
rename from putio/files_test.go
rename to files_test.go
diff --git a/putio/friends.go b/friends.go
similarity index 100%
rename from putio/friends.go
rename to friends.go
diff --git a/putio/friends_test.go b/friends_test.go
similarity index 100%
rename from putio/friends_test.go
rename to friends_test.go
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..5b5cf55
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/putdotio/go-putio
+
+go 1.13
+
+require golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..768e1bf
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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 100%
rename from putio/time.go
rename to time.go
diff --git a/putio/transfers.go b/transfers.go
similarity index 100%
rename from putio/transfers.go
rename to transfers.go
diff --git a/putio/transfers_test.go b/transfers_test.go
similarity index 99%
rename from putio/transfers_test.go
rename to transfers_test.go
index 53985cc..b01465a 100644
--- a/putio/transfers_test.go
+++ b/transfers_test.go
@@ -232,7 +232,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)
diff --git a/putio/types.go b/types.go
similarity index 100%
rename from putio/types.go
rename to types.go
diff --git a/upload.go b/upload.go
new file mode 100644
index 0000000..fb8b62a
--- /dev/null
+++ b/upload.go
@@ -0,0 +1,172 @@
+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 resp.Body.Close()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusCreated {
+		err = fmt.Errorf("unexpected status: %d", 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 resp.Body.Close()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusNoContent {
+		err = fmt.Errorf("unexpected status: %d", 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 resp.Body.Close()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusOK {
+		err = fmt.Errorf("unexpected status: %d", resp.StatusCode)
+		return
+	}
+	n, err = strconv.ParseInt(resp.Header.Get("upload-offset"), 10, 64)
+	u.log(fmt.Sprintln("uploadJob offset:", n))
+	return n, 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 resp.Body.Close()
+
+	u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+	if resp.StatusCode != http.StatusNoContent {
+		err = fmt.Errorf("unexpected status: %d", 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)
+}
diff --git a/putio/zips.go b/zips.go
similarity index 99%
rename from putio/zips.go
rename to zips.go
index 66e33cc..fced4dc 100644
--- a/putio/zips.go
+++ b/zips.go
@@ -26,7 +26,6 @@ func (z *ZipsService) Get(ctx context.Context, id int64) (Zip, error) {
 	}
 
 	return r, nil
-
 }
 
 // List lists active zip files.
diff --git a/putio/zips_test.go b/zips_test.go
similarity index 100%
rename from putio/zips_test.go
rename to zips_test.go