{"record":{"id":"924f8c791ce1313f","repo":"HKUDS/DeepTutor","slug":"mineru-api-did-not-return-an-upload-url-missing-b","errorCode":null,"errorMessage":"MinerU API did not return an upload URL (missing batch_id/file_urls).","messagePattern":"MinerU API did not return an upload URL \\(missing batch_id/file_urls\\)\\.","errorType":"exception","errorClass":"MinerUError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/parsing/engines/mineru/cloud.py","lineNumber":140,"sourceCode":"    client: httpx.Client, pdf_path: Path, config: MinerUConfig, key_pool: KeyPool\n) -> tuple[str, str]:\n    \"\"\"POST file-urls/batch → ``(batch_id, signed_upload_url)``.\"\"\"\n    file_entry: dict[str, object] = {\"name\": pdf_path.name, \"is_ocr\": config.is_ocr}\n    body: dict[str, object] = {\n        \"files\": [file_entry],\n        \"model_version\": config.model_version,\n        \"enable_formula\": config.enable_formula,\n        \"enable_table\": config.enable_table,\n    }\n    if config.api_language:\n        body[\"language\"] = config.api_language\n\n    payload = _post_json(client, \"/api/v4/file-urls/batch\", body, key_pool)\n    data = payload.get(\"data\") or {}\n    batch_id = str(data.get(\"batch_id\") or \"\").strip()\n    file_urls = data.get(\"file_urls\") or []\n    if not batch_id or not isinstance(file_urls, list) or not file_urls:\n        raise MinerUError(\"MinerU API did not return an upload URL (missing batch_id/file_urls).\")\n    return batch_id, str(file_urls[0])\n\n\ndef _upload_file(pdf_path: Path, upload_url: str) -> None:\n    \"\"\"PUT the PDF bytes to the signed URL.\n\n    The signed URL carries its own auth; per MinerU's docs we must NOT send an\n    ``Authorization`` or ``Content-Type`` header (a stray Content-Type breaks\n    the OSS signature).\n    \"\"\"\n    data = pdf_path.read_bytes()\n    try:\n        response = httpx.put(upload_url, content=data, timeout=_UPLOAD_TIMEOUT_SECONDS)\n        response.raise_for_status()\n    except httpx.HTTPError as exc:\n        raise MinerUError(f\"Failed to upload PDF to MinerU: {exc}\") from exc\n\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/parsing/engines/mineru/cloud.py#L122-L158","documentation":"The MinerU batch-creation endpoint responded, but its data payload lacked batch_id or file_urls, so there is no upload URL to PUT the PDF to.","triggerScenarios":"POST /api/v4/file-urls/batch returning 200 with malformed data — API contract change, empty file_urls list, or stripped batch_id.","commonSituations":"MinerU API version drift, backend incident returning partial payloads, or a region-specific endpoint behaving differently (api_base_url override).","solutions":["Log the full payload for that response and compare with MinerU API docs.","Confirm config.api_base_url points at a supported API version (v4).","Retry once — transient malformed responses happen.","Report to MinerU if consistently malformed."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    parse_cloud(...)\nexcept MinerUError as e:\n    if \"upload URL\" in str(e):\n        time.sleep(5); parse_cloud(...)  # fresh batch","preventionTips":["Pin a known-good api_base_url / API version.","Alert on malformed payloads to catch API drift early."],"tags":["mineru","cloud","api-contract"],"backgroundTag":"api-unexpected-response-shape","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}