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

  1. Log the raw /uploads response body (before decoding) to confirm what MAX actually returned and compare against the documented {"url":"...","token":"..."} shape.
  2. 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: ...'.
  3. Check the kind query parameter: only image/video/audio/file are supported; an unexpected type can make MAX return a body without a url.
  4. 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.
  5. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/79df759764f62813. Report an issue: GitHub.