On this page
Golang Code Examples
Go needs nothing beyond the standard library for this API: net/http and encoding/json cover every endpoint, and there is no SDK to install. The two examples below go from a request you can paste into main and run, to a small client type with typed structs for the response and an error type that carries the API's code and retryable fields — the pieces you actually need once extraction runs inside a service.
🚀 Quick Start
Use this to prove your key and your network path work before writing anything larger; it prints the raw JSON so you can see exactly what the site you care about returns. Two things to fix before it goes near production: &http.Client{} has no timeout at all, so set a Timeout (60 seconds is a sane start) unless a stuck extraction holding a goroutine forever is acceptable, and the _ discards on json.Marshal and http.NewRequest are snippet shorthand rather than a pattern to copy. Closing the body with defer matters on the failure path too — a body that is never drained and closed keeps its connection out of the pool.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
apiURL := "https://api.meowload.net/openapi/v1/extract/post"
apiKey := "<your API key>"
// Build request
payload := map[string]string{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Accept-Language", "en")
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("❌ Network error: %v\n", err)
return
}
defer resp.Body.Close()
// Process response
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 200 {
fmt.Println("✅ Request successful!")
fmt.Println(string(body))
} else {
// Every non-200 body has the shape { message, code?, retryable? }
fmt.Printf("❌ Request failed (%d)\n", resp.StatusCode)
fmt.Println(string(body))
}
}📦 Full Client Wrapper Example
The structs are the point here. The API omits missing fields rather than sending null, so a plain string is enough for optional text (empty means absent) and only nested objects such as Author need to be pointers. APIError embeds the error body, which lets a caller recover the retry decision with errors.As instead of matching on error strings. When you pick a download, read Variants: Quality is the height in pixels (9999 marks the original), IsDefault marks the recommended entry, and a variant holding both VideoURL and AudioURL is a split stream you have to merge yourself. http.Client is safe for concurrent use, so keep one MeowloadClient for the lifetime of the program instead of building one per request — a new client each time means a new connection pool, so every call pays a fresh TLS handshake. That is a latency cost rather than a quota one: the limit of 1,200 requests per minute per key counts requests however the connections are pooled. Unlike the bare request in the quick start, NewClient gives its http.Client a 60-second Timeout — this client outlives any single call, which is exactly where a missing timeout bites hardest.
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
)
type MeowloadClient struct {
APIKey string
BaseURL string
Client *http.Client
}
type ExtractRequest struct {
URL string `json:"url"`
}
// Variant is one resolution or audio track of a media item.
// VideoURL and AudioURL both set means a split stream: download both and merge.
type Variant struct {
Quality int `json:"quality,omitempty"` // height in pixels; 9999 means original quality
QualityLabel string `json:"quality_label,omitempty"`
VideoURL string `json:"video_url,omitempty"`
VideoExt string `json:"video_ext,omitempty"`
VideoFilesize int64 `json:"video_filesize,omitempty"`
AudioURL string `json:"audio_url,omitempty"`
AudioExt string `json:"audio_ext,omitempty"`
AudioFilesize int64 `json:"audio_filesize,omitempty"`
IsDefault bool `json:"is_default,omitempty"`
LanguageTag string `json:"language_tag,omitempty"`
LanguageName string `json:"language_name,omitempty"`
}
type Media struct {
MediaType string `json:"media_type"`
ResourceURL string `json:"resource_url"`
PreviewURL string `json:"preview_url,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Duration float64 `json:"duration,omitempty"`
Variants []Variant `json:"variants,omitempty"`
}
type Profile struct {
Username string `json:"username"`
DisplayName string `json:"display_name,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
}
type ExtractResponse struct {
Site string `json:"site,omitempty"`
Title string `json:"title,omitempty"` // only on sites that have titles
Text string `json:"text,omitempty"` // body / caption
Medias []Media `json:"medias"`
ID string `json:"id,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
Author *Profile `json:"author,omitempty"`
PostURL string `json:"post_url,omitempty"`
}
// Caption returns the display caption: the title when present, otherwise the body
func (r *ExtractResponse) Caption() string {
if r.Title != "" {
return r.Title
}
return r.Text
}
// ErrorResponse is the shape of every non-200 response. Code and Retryable are always present on 400
type ErrorResponse struct {
Message string `json:"message"`
Code string `json:"code,omitempty"`
Retryable bool `json:"retryable,omitempty"`
}
// APIError carries the HTTP status together with the error body so the caller can decide whether to retry
type APIError struct {
StatusCode int
ErrorResponse
}
func (e *APIError) Error() string {
if e.Code != "" {
return fmt.Sprintf("request failed (%d): %s [code=%s retryable=%t]", e.StatusCode, e.Message, e.Code, e.Retryable)
}
return fmt.Sprintf("request failed (%d): %s", e.StatusCode, e.Message)
}
// NewClient creates a new API client
func NewClient(apiKey string) *MeowloadClient {
return &MeowloadClient{
APIKey: apiKey,
BaseURL: "https://api.meowload.net/openapi/v1",
// The client outlives any single request, and a zero-value http.Client has no timeout,
// so a stuck extraction would hold a goroutine forever.
Client: &http.Client{Timeout: 60 * time.Second},
}
}
// ExtractPost extracts a single post
func (c *MeowloadClient) ExtractPost(url string) (*ExtractResponse, error) {
endpoint := c.BaseURL + "/extract/post"
// Build request
reqBody := ExtractRequest{URL: url}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("JSON serialization failed: %w", err)
}
// Create HTTP request
req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
// Set request headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.APIKey)
req.Header.Set("Accept-Language", "en")
// Send request
resp, err := c.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
// Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
// Handle error response
if resp.StatusCode != 200 {
apiErr := &APIError{StatusCode: resp.StatusCode}
json.Unmarshal(body, &apiErr.ErrorResponse)
return nil, apiErr
}
// Parse success response
var result ExtractResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return &result, nil
}
func main() {
// Create client
client := NewClient("<your API key>")
// Extract post
result, err := client.ExtractPost("https://www.bilibili.com/video/BV1sG4y1p7TA/")
if err != nil {
fmt.Printf("❌ Error: %v\n", err)
var apiErr *APIError
if errors.As(err, &apiErr) && apiErr.Retryable {
fmt.Println(" Transient failure; retry later")
}
return
}
fmt.Println("✅ Extraction successful!")
fmt.Printf("Site: %s\n", result.Site)
fmt.Printf("Caption: %s\n", result.Caption())
if result.Author != nil {
fmt.Printf("Author: %s\n", result.Author.Username)
}
fmt.Printf("Media count: %d\n", len(result.Medias))
for idx, media := range result.Medias {
fmt.Printf("\nMedia %d:\n", idx+1)
fmt.Printf(" Type: %s\n", media.MediaType)
fmt.Printf(" URL: %s\n", media.ResourceURL)
for _, v := range media.Variants {
split := ""
if v.VideoURL != "" && v.AudioURL != "" {
split = " (needs merging)"
}
fmt.Printf(" - %s%s\n", v.QualityLabel, split)
}
}
}The quickstart covers key setup and the response layout in prose, and error codes lists every code the 400 branch above can carry, along with which of them retryable is true for. The API reference is the authority on request parameters and response fields, and generates a matching Go snippet for each endpoint. To fetch a whole channel rather than one video, keep the client and change the path to the playlist API, which pages with has_more and next_cursor; the credits API reports the balance that funds it. Coming from the unversioned endpoints, migrating from the legacy API lists what changed.