An idiomatic Go client for the Yandex Disk REST API.
- Install
- Quick start
- Authentication
- Disk info
- Files and folders
- Upload
- Download
- Public resources
- Trash
- Pagination
- Batch operations
- Configuration
- Logging
- Error handling
- Async operations
- Examples
- Development
- License
go get github.com/ilyabrin/diskNote
Requires Go 1.23 or newer. The library has no runtime dependencies beyond the standard library.
package main
import (
"context""fmt""log""github.com/ilyabrin/disk"
)
funcmain() {
// Reads YANDEX_DISK_ACCESS_TOKEN when no token is passed explicitly.client, err:=disk.New()
iferr!=nil {
log.Fatal(err)
}
ctx:=context.Background()
info, err:=client.DiskInfo(ctx)
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Used %s of %s\n",
disk.FormatFileSize(int64(info.UsedSpace)),
disk.FormatFileSize(int64(info.TotalSpace)),
)
}The client authenticates with an OAuth token. Get one from the
Yandex OAuth console with the cloud_api:disk.* scopes.
client, err:=disk.New("y0_AgAAAA...") // explicit tokenclient, err:=disk.New() // from $YANDEX_DISK_ACCESS_TOKENWarning
Never commit tokens. The built-in logger redacts Authorization headers, but
your own logging is your responsibility.
info, err:=client.DiskInfo(ctx)
iferr!=nil {
log.Fatal(err)
}
fmt.Println("Total:", info.TotalSpace)
fmt.Println("Used:", info.UsedSpace)
fmt.Println("Trash:", info.TrashSize)
fmt.Println("Downloads folder:", info.SystemFolders.Downloads)Metadata
resource, errResp:=client.GetMetadata(ctx, "/Documents/report.pdf")
iferrResp!=nil {
log.Fatal(errResp.Error)
}
fmt.Println(resource.Name, resource.Size, resource.MimeType)Listing a folder returns its contents in _embedded. Use GetMetadataWithOptions
to paginate, sort, and trim the response:
folder, errResp:=client.GetMetadataWithOptions(ctx, "/Photos", &disk.ResourceOptions{
Limit: 100,
Offset: 0,
Sort: "-modified", // "-" reverses the orderPreviewSize: "M",
PreviewCrop: true,
Fields: []string{"name", "_embedded.items.name", "_embedded.items.size"},
})
for_, item:=rangefolder.Embedded.Items {
fmt.Println(item.Type, item.Name)
}Create, copy, move, delete
// Create a folderlink, errResp:=client.CreateDir(ctx, "/Reports")
// Copylink, errResp=client.CopyResource(ctx, "/a.txt", "/backup/a.txt")
// Move or renamelink, errResp=client.MoveResource(ctx, "/a.txt", "/archive/a-2026.txt")
// Delete (to trash)err:=client.DeleteResource(ctx, "/a.txt", false)
// Delete permanentlyerr=client.DeleteResource(ctx, "/a.txt", true)Custom properties
resource, errResp:=client.UpdateMetadata(ctx, "/report.pdf",
map[string]map[string]string{
"custom_properties": {
"project": "apollo",
"status": "final",
},
})Flat file list and recent uploads
// All files, newest first, images and video onlyfiles, errResp:=client.GetSortedFilesWithOptions(ctx,
&disk.PaginationOptions{Limit: 50},
&disk.FilesOptions{
MediaType: []string{"image", "video"},
Sort: "-created",
})
// Last uploaded resourcesrecent, errResp:=client.GetLastUploadedResources(ctx)| Method | Use for |
|---|---|
UploadFileFromPath | Any local file, with full option control |
UploadFileFromPathWithProgress | Small/medium files with a progress bar |
UploadLargeFileFromPath | Large files, chunked progress reporting |
UploadFile | Server-side fetch: Yandex downloads a URL for you |
resource, err:=client.UploadFileFromPath(ctx, "./report.pdf", "/Documents/report.pdf",
&disk.UploadOptions{Overwrite: true})With progress:
resource, err:=client.UploadFileFromPathWithProgress(ctx,
"./video.mp4", "/Videos/video.mp4", true,
func(p disk.UploadProgress) {
fmt.Printf("\r%.1f%% (%s / %s)",
p.Percentage,
disk.FormatFileSize(p.BytesUploaded),
disk.FormatFileSize(p.TotalBytes),
)
})Large files, reporting once per 10 MB chunk:
resource, err:=client.UploadLargeFileFromPath(ctx,
"./archive.zip", "/Backups/archive.zip", 10,
func(p disk.UploadProgress) {
log.Printf("uploaded %s", disk.FormatFileSize(p.BytesUploaded))
})Let Yandex fetch a remote URL directly, without routing bytes through your process:
link, errResp:=client.UploadFile(ctx, "/Downloads/image.jpg", "https://example.com/image.jpg")Tip
UploadFile is asynchronous — poll the returned link with
GetOperationStatus to find out when the file has landed.
err:=client.DownloadFileToPath(ctx, "/Photos/image.jpg", "./image.jpg",
&disk.DownloadOptions{Overwrite: true})With progress:
err:=client.DownloadFileToPathWithProgress(ctx,
"/Videos/video.mp4", "./video.mp4", true,
func(p disk.DownloadProgress) {
ifp.TotalBytes>0 {
fmt.Printf("\r%.1f%%", p.Percentage)
}
})Need the raw link instead (for a CDN, a browser redirect, or your own transfer code)?
link, errResp:=client.GetDownloadURL(ctx, "/Photos/image.jpg")
fmt.Println(link.Href) // short-lived, single-use// Publish and unpublishlink, errResp:=client.PublishResource(ctx, "/Photos/image.jpg")
link, errResp=client.UnpublishResource(ctx, "/Photos/image.jpg")
// Everything you have publishedlist, errResp:=client.GetPublicResources(ctx)Reading someone else's published resource by key or URL:
resource, errResp:=client.GetMetadataForPublicResource(ctx, "https://yadi.sk/d/abc123")
// Browse inside a published folderresource, errResp=client.GetMetadataForPublicResourceWithOptions(ctx, "https://yadi.sk/d/abc123",
&disk.PublicResourceOptions{
Path: "/subfolder",
Sort: "name",
Limit: 50,
})
// Download a specific file from a published folderlink, errResp:=client.GetDownloadURLForPublicResourceAt(ctx, "https://yadi.sk/d/abc123", "/subfolder/file.txt")
// Save it into your own Downloads folder under a new namelink, errResp=client.SavePublicResourceWithOptions(ctx, "https://yadi.sk/d/abc123",
&disk.SavePublicResourceOptions{Path: "/subfolder/file.txt", Name: "copy.txt"})// Browsetrash, err:=client.ListTrashResources(ctx, "", 100, 0)
// Metadata for one itemitem, err:=client.GetTrashResourceMetadata(ctx, "report.pdf", nil)
// Restore, optionally renaminglink, err:=client.RestoreFromTrash(ctx, "report.pdf", false, "report-restored.pdf")
// Empty a single path, or the whole trash with ""err=client.EmptyTrash(ctx, "", false)Note
Pass forceAsync: true to EmptyTrash to make the API always answer 202 with
an operation link instead of blocking on a large deletion.
Three styles are available, from lowest to highest level. See PAGINATION.md for the full guide.
1. Explicit limit/offset
files, errResp:=client.GetSortedFilesWithPagination(ctx, &disk.PaginationOptions{
Limit: 50,
Offset: 100,
})2. Paged results with metadata
page, errResp:=client.GetSortedFilesPaged(ctx, &disk.PaginationOptions{Limit: 50})
fmt.Println(page.Pagination.HasMore, page.Pagination.NextOffset)3. Iterator
it:=client.GetSortedFilesIterator(&disk.PaginationOptions{Limit: 100})
forit.HasNext() {
page, err:=it.Next(ctx)
iferr!=nil {
log.Fatal(err)
}
for_, file:=rangepage.Items {
fmt.Println(file.Name)
}
}Batch helpers run operations concurrently and collect per-item results instead of failing on the first error.
status, err:=client.BatchDeleteFiles(ctx,
[]string{"/tmp/a.txt", "/tmp/b.txt", "/tmp/c.txt"},
&disk.BatchDeleteOptions{
BatchOptions: disk.BatchOptions{
MaxConcurrency: 4,
ContinueOnError: true,
Progress: func(s disk.BatchOperationStatus) {
fmt.Printf("\r%d/%d", s.Completed, s.Total)
},
},
Permanently: false,
})
fmt.Println(status.GetSummary())
for_, failure:=rangestatus.GetFailedOperations() {
log.Printf("%s: %v", failure.Path, failure.Error)
}
// Retry only what failedstatus, err=client.RetryFailedOperations(ctx, status, 2)Available: BatchDeleteFiles, BatchCopyFiles, BatchMoveFiles,
BatchUpdateMetadata, plus the convenience wrappers BatchRenameFiles,
BatchMoveToDirectory, BatchCopyToDirectory and the *Simple variants.
client, err:=disk.NewWithConfig(&disk.ClientConfig{
DefaultTimeout: 60*time.Second,
MaxRetries: 3,
RetryBackoff: 200*time.Millisecond,
EnableDebugLogging: true,
Logger: disk.DefaultLoggerConfig(),
}, "your-token")| Field | Default | Meaning |
|---|---|---|
DefaultTimeout | 30s | Per-request timeout when the context carries no deadline |
MaxRetries | 3 | Extra attempts for retryable requests |
RetryBackoff | 200ms | Base delay between retries; doubles each attempt |
EnableDebugLogging | false | Switches the logger to DEBUG and verbose mode |
Logger | see below | Logger configuration |
BaseURL | Yandex API | Override the endpoint (tests, proxies) |
Important
Only requests without a body are retried — GET, DELETE, and the PUT/POST
calls that carry their parameters in the query string. A request whose body is an
io.Reader cannot be rewound, so it is sent exactly once. Retries fire on
connection errors, 429, and 5xx.
Timeouts can also be set per call through the context:
ctx, cancel:=disk.WithTimeout(10*time.Second)
defercancel()
info, err:=client.DiskInfo(ctx)client.SetLogLevel(disk.DEBUG) // DEBUG, INFO, WARN, ERROR, SILENTclient.SetVerbose(true) // include request/response detailsclient.SetLogOutput(os.Stderr) // any io.WriterSensitive header values (Authorization, tokens) are redacted before they reach
the log output.
The library uses two error conventions, and which one you get depends on the call:
| Return type | Where | How to handle |
|---|---|---|
*ErrorResponse | Resource, public and pagination calls | Non-nil means failure; read .Error and .Description |
error | Disk info, upload, download, trash, batch | Standard Go handling, wrapped with %w |
resource, errResp:=client.GetMetadata(ctx, "/missing.txt")
iferrResp!=nil {
log.Printf("%s: %s", errResp.Error, errResp.Description)
return
}
iferr:=client.DownloadFileToPath(ctx, "/a.txt", "./a.txt", nil); err!=nil {
log.Fatal(err)
}Copy, move, save-to-disk and trash operations may answer 202 Accepted with a link
to a background operation. Poll it until it reports success:
link, errResp:=client.CopyResource(ctx, "/big-folder", "/backup/big-folder")
iferrResp!=nil {
log.Fatal(errResp.Error)
}
for {
// Accepts either an operation ID or the full href from the response.operation, err:=client.GetOperationStatus(ctx, link.Href)
iferr!=nil {
log.Fatal(err)
}
ifoperation.Status!=disk.OperationInProgress {
fmt.Println("finished:", operation.Status)
break
}
time.Sleep(time.Second)
}For batch calls, WaitForBatchOperation does the polling for you:
err:=client.WaitForBatchOperation(ctx, status, time.Second)Runnable programs live in examples/:
| Example | Shows |
|---|---|
| demo | Disk info, metadata, folder and file operations |
| upload | Uploads with progress reporting |
| pagination | All three pagination styles |
export YANDEX_DISK_ACCESS_TOKEN=your-token
go run ./examples/demogo test ./... # run the suite
go test -race -cover ./... # with the race detector
go vet ./... # static checks
golangci-lint run # full lint (see .golangci.yml)CI runs tests, go vet, CodeQL, govulncheck and gosec on every push and pull
request; gosec findings are published as code scanning alerts.
Contributions are welcome — open an issue or a pull request.
MIT © Ilya Brin