chenhg5/cc-connect · error
upload url: empty url in response
Error message
upload url: empty url in response
What it means
uploadAttachment in platform/max/max.go performs MAX's two-step upload: it first POSTs to /uploads?type=<kind> to obtain a presigned CDN upload URL plus token. If the API answers 200 with valid JSON but the decoded "url" field is an empty string, the platform cannot proceed with the CDN POST and throws this error. It signals that MAX's upload-URL endpoint returned a well-formed but incomplete response.
Source
Thrown at platform/max/max.go:568
urlResp, err := p.uploadClient.Do(urlReq)
if err != nil {
return "", fmt.Errorf("request upload url: %w", err)
}
defer urlResp.Body.Close()
if urlResp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(urlResp.Body, 512))
return "", fmt.Errorf("upload url: HTTP %d: %s", urlResp.StatusCode, body)
}
var urlInfo struct {
URL string `json:"url"`
Token string `json:"token"`
}
if err := json.NewDecoder(urlResp.Body).Decode(&urlInfo); err != nil {
return "", fmt.Errorf("decode upload url: %w", err)
}
if urlInfo.URL == "" {
return "", fmt.Errorf("upload url: empty url in response")
}
if filename == "" {
filename = defaultFilename(kind)
}
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
fw, err := mw.CreateFormFile("data", filename)
if err != nil {
return "", err
}
if _, err := fw.Write(data); err != nil {
return "", err
}
if err := mw.Close(); err != nil {
return "", err
}
View on GitHub (pinned to 4000b2338a)
Solutions
- Log the raw /uploads response body (before decoding) to confirm what MAX actually returned and compare against the documented {"url":"...","token":"..."} shape.
- Verify the bot token is valid and has upload/attachment permissions; re-test with a fresh token via curl -X POST 'https://botapi.max.ru/uploads?type=image' -H 'Authorization: ...'.
- Check the kind query parameter: only image/video/audio/file are supported; an unexpected type can make MAX return a body without a url.
- If MAX changed the response schema, update the urlInfo struct tags in platform/max/max.go (line 560-563) to match the new field name.
- Retry once — if transient upstream degradation, a repeat request may return a proper URL.
Example fix
// before (blind failure)
if urlInfo.URL == "" {
return "", fmt.Errorf("upload url: empty url in response")
}
// after (log the raw body for diagnosability)
raw, _ := io.ReadAll(io.LimitReader(urlResp.Body, 4096))
var urlInfo struct {
URL string `json:"url"`
Token string `json:"token"`
}
if err := json.Unmarshal(raw, &urlInfo); err != nil {
return "", fmt.Errorf("decode upload url: %w", err)
}
if urlInfo.URL == "" {
return "", fmt.Errorf("upload url: empty url in response (body=%s)", raw)
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-check before uploading: ensure non-empty payload and a supported kind
func validateUpload(kind string, data []byte) error {
if len(data) == 0 { return fmt.Errorf("empty attachment data") }
switch kind {
case "image", "video", "audio", "file":
return nil
}
return fmt.Errorf("unsupported upload kind: %q", kind)
} Type guard
func hasUploadURL(resp *struct{ URL string `json:"url"`; Token string `json:"token"` }) bool {
return resp != nil && resp.URL != ""
} Try / catch
token, err := p.uploadAttachment(ctx, kind, data, filename)
if err != nil {
if strings.Contains(err.Error(), "empty url in response") {
slog.Warn("max: /uploads returned no url; check token/API version", "kind", kind)
return fmt.Errorf("max: upload unavailable: %w", err)
}
return err
} Prevention
- Pin and monitor the MAX Bot API version; test /uploads in CI against the live API.
- Log the raw /uploads response body whenever the decoded url is empty.
- Keep the bot token valid and scoped for uploads; rotate before expiry.
- Only pass the documented kinds (image/video/audio/file) as ?type=.
When it happens
Trigger: Calling SendImage/SendFile/SendAudio when the MAX API returns HTTP 200 on POST /uploads with JSON that decodes successfully but lacks a non-empty "url" field (e.g. {"token":"..."} only, or {}), such as when MAX changes its upload-URL response schema or a proxy strips the field.
Common situations: MAX server-side API change or deprecation of the /uploads flow; a corporate proxy/gateway that mangles or rewrites the response body; testing against a stub/mock server that returns an incomplete upload-URL payload; expired or mis-scoped bot token causing MAX to return 200 with a degraded body instead of an error.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- upload url: HTTP %d: %s
- cdn upload: HTTP %d: %s
- upload returned status %d: %s
- max: edit message: HTTP %d: %s
- cdn upload: no token in response: %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/79df759764f62813.
Report an issue: GitHub.