This guide shows the complete file-transfer workflow: upload a local file to a Cloud Phone, export a file from the Cloud Phone, poll asynchronous tasks, and recover safely from timeouts.
| Step | Open API | Local API |
|---|---|---|
| Base URL | https://api.morelogin.com | http://127.0.0.1:40000 |
| Authentication | Authorization: Bearer <token> | Local API token when enabled |
| Request upload URL | POST /cloudphone/uploadUrl | POST /api/cloudphone/upload/file/signedUrl |
| Register uploaded object | POST /cloudphone/uploadFile | POST /api/cloudphone/upload/file |
| Query upload result | POST /cloudphone/uploadFileResult | POST /api/cloudphone/upload/file/result |
| Request file export | POST /cloudphone/download | POST /api/cloudphone/download |
| Query export result | POST /cloudphone/download/result | POST /api/cloudphone/download/result |
The examples below use Open API. To use Local API, change the base URL and paths according to the table. Keep Cloud Phone IDs and task IDs as strings; JavaScript clients must not convert them through Number.
When the file is on the same machine as the MoreLogin client, Local API also provides POST /api/cloudphone/uploadFile with multipart/form-data:
curl --request POST "http://127.0.0.1:40000/api/cloudphone/uploadFile" \
--form 'id=1661515884160372' \
--form 'uploadDest=/Download' \
--form 'file=@./report.csv'Use the signed-URL workflow below when the caller runs remotely, when the file is large, or when you need a fileId for explicit status polling.
curl --request POST "https://api.morelogin.com/cloudphone/uploadUrl" \
--header "Authorization: Bearer $MORELOGIN_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"id": "1661515884160372",
"fileName": "report.csv"
}'The response contains data.presignedUrl. The temporary object is automatically deleted after 7 days; this does not delete a file that has already been delivered to the Cloud Phone.
Send the file directly to the returned URL. Do not add the MoreLogin Bearer token to this storage request.
curl --location --request PUT \
--upload-file "./report.csv" \
"$PRESIGNED_URL"Only continue after the storage service returns a successful HTTP status. If this request fails or times out, retry the PUT to the same URL while it remains valid.
Use the uploaded object URL. The query string on a pre-signed URL contains credentials, so remove it before storing or logging the URL when your object-store configuration permits it.
FILE_URL="${PRESIGNED_URL%%\?*}"
curl --request POST "https://api.morelogin.com/cloudphone/uploadFile" \
--header "Authorization: Bearer $MORELOGIN_TOKEN" \
--header "Content-Type: application/json" \
--data "$(jq -n \
--arg id '1661515884160372' \
--arg url "$FILE_URL" \
--arg dest '/Download' \
'{id: $id, url: $url, uploadDest: $dest, uploadType: 1}')"Use uploadType: 1 for regular files. Use uploadType: 2 only for supported MP4 live-streaming files. Save data.fileId from the response.
curl --request POST "https://api.morelogin.com/cloudphone/uploadFileResult" \
--header "Authorization: Bearer $MORELOGIN_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"id": "1661515884160372",
"fileId": "1661515884160111"
}'data / data.status | Meaning | Action |
|---|---|---|
null | The task is not visible yet | Wait and poll again |
0 | Uploading | Wait and poll again |
1 | Successful | Stop polling |
2 | Failed | Stop polling and report requestId |
Poll every 2 seconds and set a client-side deadline appropriate for the file size. Do not create another upload task merely because a status request timed out; query the existing fileId first.
“Download” is an export from the Cloud Phone to a temporary URL. It is a two-stage asynchronous workflow.
If polling returns 30 (failed) or 40 (cancelled), stop the flow and do not request downUrl.
Use the absolute Android file path, including the file name.
curl --request POST "https://api.morelogin.com/cloudphone/download" \
--header "Authorization: Bearer $MORELOGIN_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"id": "1661515884160372",
"filePath": "/sdcard/Download/report.csv"
}'Save data.downId. The request only creates the export task; it does not return the file bytes.
curl --request POST "https://api.morelogin.com/cloudphone/download/result" \
--header "Authorization: Bearer $MORELOGIN_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"id": "1661515884160372",
"downId": "1661515884160999"
}'data.status | Meaning | Action |
|---|---|---|
10 | Running | Wait and poll again |
20 | Successful | Download from data.downUrl |
30 | Failed | Stop and report requestId |
40 | Cancelled | Stop; create a new task only if still needed |
When the status is 20, download the file promptly:
curl --location "$DOWN_URL" --output "./report.csv"Do not send the MoreLogin Bearer token to downUrl. Treat both pre-signed upload URLs and download URLs as secrets, and never persist them in application logs.
This example requires requests. It checks both HTTP status and the MoreLogin business code, polls with a deadline, and reuses the returned task IDs.
import os
import time
from pathlib import Path
import requests
BASE_URL = "https://api.morelogin.com"
TOKEN = os.environ["MORELOGIN_TOKEN"]
PHONE_ID = "1661515884160372"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
def api_post(path, payload):
response = requests.post(
f"{BASE_URL}{path}", json=payload, headers=HEADERS, timeout=30
)
response.raise_for_status()
body = response.json()
if body.get("code") != 0:
raise RuntimeError(
f"API error code={body.get('code')} msg={body.get('msg')} "
f"requestId={body.get('requestId')}"
)
return body
def wait_for(path, payload, terminal, timeout=300):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
body = api_post(path, payload)
data = body.get("data")
if data is not None and data.get("status") in terminal:
return body
time.sleep(2)
raise TimeoutError(f"Timed out while polling {path}; keep the task ID")
def upload_to_phone(local_path, destination="/Download"):
path = Path(local_path)
signed = api_post(
"/cloudphone/uploadUrl", {"id": PHONE_ID, "fileName": path.name}
)
presigned_url = signed["data"]["presignedUrl"]
with path.open("rb") as stream:
put_response = requests.put(presigned_url, data=stream, timeout=120)
put_response.raise_for_status()
object_url = presigned_url.split("?", 1)[0]
created = api_post(
"/cloudphone/uploadFile",
{
"id": PHONE_ID,
"url": object_url,
"uploadDest": destination,
"uploadType": 1,
},
)
file_id = created["data"]["fileId"]
result = wait_for(
"/cloudphone/uploadFileResult",
{"id": PHONE_ID, "fileId": file_id},
terminal={1, 2},
)
if result["data"]["status"] != 1:
raise RuntimeError(f"Upload failed; requestId={result.get('requestId')}")
return file_id
def download_from_phone(remote_path, local_path):
created = api_post(
"/cloudphone/download", {"id": PHONE_ID, "filePath": remote_path}
)
down_id = created["data"]["downId"]
result = wait_for(
"/cloudphone/download/result",
{"id": PHONE_ID, "downId": down_id},
terminal={20, 30, 40},
)
if result["data"]["status"] != 20:
raise RuntimeError(f"Download export failed; requestId={result.get('requestId')}")
with requests.get(result["data"]["downUrl"], stream=True, timeout=120) as response:
response.raise_for_status()
with open(local_path, "wb") as output:
for chunk in response.iter_content(1024 * 1024):
if chunk:
output.write(chunk)
return down_id
upload_to_phone("./report.csv")
download_from_phone("/sdcard/Download/report.csv", "./downloaded-report.csv")- Confirm
code == 0; HTTP200alone does not mean the business operation succeeded. - Keep
id,fileId, anddownIdas strings. - Retry status queries with backoff. Before repeating a task-creation request after a timeout, query the task ID when one was received.
- Limit concurrent transfers and follow the documented rate limits.
- Validate the local file size and available Cloud Phone storage before large transfers.
- Record
requestId, endpoint, Cloud Phone ID, task ID, and terminal status for support, but redact tokens and signed URLs.
For field-level schemas, see the Cloud Phone Open API and Cloud Phone Local API. For general polling and retry rules, see Asynchronous Operations.