Download social media videos in Go
The Go client is a request with one header and a typed decode. What this guide adds is the discipline around it — a context deadline so a slow source cannot hang a goroutine, a struct that makes the error code a field rather than a string search, and streaming the file with io.Copy rather than reading it into memory.
Updated
Types first
A struct with Success, Medias and a pointer Error makes the error code a field rather than a string search. Keep the type local and small — a new optional field in the response cannot break a decode that ignores it.
Gotype Media struct {
Type string `json:"type"`
URL string `json:"url"`
}
type Resolved struct {
Success bool `json:"success"`
Medias []Media `json:"medias"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}The request
http.NewRequestWithContext carries the deadline; url.QueryEscape encodes the link. One header, one decode. Nothing else is needed on your side — no cookies, no session, no browser.
Gofunc resolve(ctx context.Context, link string) (*Resolved, error) {
u := "https://api.saveapi.org/v1/download?url=" + url.QueryEscape(link)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
req.Header.Set("Authorization", "Bearer sk_live_xxxx")
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var out Resolved
return &out, json.NewDecoder(res.Body).Decode(&out)
}Stream the file with io.Copy
The media URL points straight at the platform's CDN. io.Copy from the response body to a file keeps memory flat whatever the file size; the API never proxies the bytes.
Goctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
data, err := resolve(ctx, "https://www.instagram.com/reel/ABC123/")
if err != nil || !data.Success {
log.Fatal(err, data.Error.Code)
}
res, _ := http.Get(data.Medias[0].URL) // to'g'ridan-to'g'ri CDN
defer res.Body.Close()
f, _ := os.Create("video.mp4")
defer f.Close()
io.Copy(f, res.Body) // oqim, xotiraga emasRetry only what a retry can fix
UPSTREAM_TIMEOUT and UPSTREAM_ERROR deserve a second attempt and cost nothing. PRIVATE_CONTENT and MEDIA_NOT_FOUND are final and cost 0.1 credits each time — a retry loop on those is a way to spend credits on the same answer. Keep the retryable set in one place.
Read the headers you already have
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and, on paid keys, X-Credits-Remaining. That is enough to back off before you are throttled without a separate balance call.
Questions before you build
- Is there an official Go SDK?
- No. The typed struct and the request in this guide are the SDK; a package would add a dependency for eight lines.
- How do I handle formats[]?
- Add a Formats []Format field to the struct for VK and Rutube; elsewhere the field is simply absent and stays empty.
- Can I resolve many links concurrently?
- Yes, within the key's per-minute limit. A semaphore sized to the limit and the X-RateLimit-Remaining header keep you from being throttled.
Platform references used here
Other guides
Run this guide with your own key
Key in under a minute. 1,000 free credits, no card, no expiry.